@wayai/cli 0.3.150 → 0.3.152

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.
package/dist/index.js CHANGED
@@ -447,11 +447,11 @@ function captureException2(error, context) {
447
447
  Sentry.captureException(error);
448
448
  });
449
449
  }
450
- function addApiBreadcrumb(method, path36) {
450
+ function addApiBreadcrumb(method, path35) {
451
451
  if (!initialized) return;
452
452
  Sentry.addBreadcrumb({
453
453
  category: "http",
454
- message: `${method} ${path36}`,
454
+ message: `${method} ${path35}`,
455
455
  level: "info"
456
456
  });
457
457
  }
@@ -905,8 +905,8 @@ var init_parseUtil = __esm({
905
905
  init_errors();
906
906
  init_en();
907
907
  makeIssue = (params) => {
908
- const { data, path: path36, errorMaps, issueData } = params;
909
- const fullPath = [...path36, ...issueData.path || []];
908
+ const { data, path: path35, errorMaps, issueData } = params;
909
+ const fullPath = [...path35, ...issueData.path || []];
910
910
  const fullIssue = {
911
911
  ...issueData,
912
912
  path: fullPath
@@ -1217,11 +1217,11 @@ var init_types = __esm({
1217
1217
  init_parseUtil();
1218
1218
  init_util();
1219
1219
  ParseInputLazyPath = class {
1220
- constructor(parent, value, path36, key) {
1220
+ constructor(parent, value, path35, key) {
1221
1221
  this._cachedPath = [];
1222
1222
  this.parent = parent;
1223
1223
  this.data = value;
1224
- this._path = path36;
1224
+ this._path = path35;
1225
1225
  this._key = key;
1226
1226
  }
1227
1227
  get path() {
@@ -4634,12 +4634,12 @@ function validateFollowupLinks(followups, label, ctx) {
4634
4634
  const linkTargets = /* @__PURE__ */ new Set();
4635
4635
  followups.forEach((followup, i) => {
4636
4636
  const ref = followup?.after_followup_id;
4637
- const path36 = ["followups", i, "after_followup_id"];
4637
+ const path35 = ["followups", i, "after_followup_id"];
4638
4638
  if (followup?.type !== "inactivity_after_before_event") {
4639
4639
  if (ref !== void 0) {
4640
4640
  ctx.addIssue({
4641
4641
  code: external_exports.ZodIssueCode.custom,
4642
- path: path36,
4642
+ path: path35,
4643
4643
  message: `kanban status ${label}: after_followup_id is only valid on inactivity_after_before_event followups (this one is ${followup?.type})`
4644
4644
  });
4645
4645
  }
@@ -4648,7 +4648,7 @@ function validateFollowupLinks(followups, label, ctx) {
4648
4648
  if (ref === void 0) {
4649
4649
  ctx.addIssue({
4650
4650
  code: external_exports.ZodIssueCode.custom,
4651
- path: path36,
4651
+ path: path35,
4652
4652
  message: `kanban status ${label}: inactivity_after_before_event requires after_followup_id naming the id of a before_event followup in the same status`
4653
4653
  });
4654
4654
  return;
@@ -4658,7 +4658,7 @@ function validateFollowupLinks(followups, label, ctx) {
4658
4658
  if (matches.length > 1) {
4659
4659
  ctx.addIssue({
4660
4660
  code: external_exports.ZodIssueCode.custom,
4661
- path: path36,
4661
+ path: path35,
4662
4662
  message: `kanban status ${label}: after_followup_id "${ref}" matches ${matches.length} followups \u2014 a link must name exactly one`
4663
4663
  });
4664
4664
  return;
@@ -4670,7 +4670,7 @@ function validateFollowupLinks(followups, label, ctx) {
4670
4670
  );
4671
4671
  ctx.addIssue({
4672
4672
  code: external_exports.ZodIssueCode.custom,
4673
- path: path36,
4673
+ path: path35,
4674
4674
  message: hasIdlessBeforeEvent ? `kanban status ${label}: after_followup_id "${ref}" matches no followup \u2014 a link target must declare an explicit id, and this status has before_event followups without one` : `kanban status ${label}: after_followup_id "${ref}" matches no followup in this status`
4675
4675
  });
4676
4676
  return;
@@ -4678,7 +4678,7 @@ function validateFollowupLinks(followups, label, ctx) {
4678
4678
  if (target.type !== "before_event") {
4679
4679
  ctx.addIssue({
4680
4680
  code: external_exports.ZodIssueCode.custom,
4681
- path: path36,
4681
+ path: path35,
4682
4682
  message: `kanban status ${label}: after_followup_id "${ref}" points at a ${target.type} followup \u2014 only before_event followups can anchor a chain`
4683
4683
  });
4684
4684
  }
@@ -4754,12 +4754,12 @@ function typeMatches(typeField, allowed) {
4754
4754
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
4755
4755
  return false;
4756
4756
  }
4757
- function validateSchema(schema, path36, errors, opts = {}) {
4757
+ function validateSchema(schema, path35, errors, opts = {}) {
4758
4758
  if (typeof schema === "boolean") return;
4759
4759
  const depth = opts.depth ?? 0;
4760
4760
  if (depth > MAX_SCHEMA_DEPTH) {
4761
4761
  errors.push({
4762
- path: path36 || "<root>",
4762
+ path: path35 || "<root>",
4763
4763
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
4764
4764
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
4765
4765
  });
@@ -4767,7 +4767,7 @@ function validateSchema(schema, path36, errors, opts = {}) {
4767
4767
  }
4768
4768
  if (!isRecord(schema)) {
4769
4769
  errors.push({
4770
- path: path36,
4770
+ path: path35,
4771
4771
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
4772
4772
  });
4773
4773
  return;
@@ -4775,14 +4775,14 @@ function validateSchema(schema, path36, errors, opts = {}) {
4775
4775
  if (opts.isRoot) {
4776
4776
  if ("type" in schema && schema.type !== "object") {
4777
4777
  errors.push({
4778
- path: path36 ? `${path36}.type` : "type",
4778
+ path: path35 ? `${path35}.type` : "type",
4779
4779
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
4780
4780
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
4781
4781
  });
4782
4782
  }
4783
4783
  } else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
4784
4784
  errors.push({
4785
- path: `${path36}.type`,
4785
+ path: `${path35}.type`,
4786
4786
  message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
4787
4787
  });
4788
4788
  }
@@ -4792,25 +4792,25 @@ function validateSchema(schema, path36, errors, opts = {}) {
4792
4792
  const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
4793
4793
  const isOutcomePlaceholder = e === "[KANBAN_OUTCOME_VALUES]";
4794
4794
  errors.push({
4795
- path: `${path36}.enum`,
4795
+ path: `${path35}.enum`,
4796
4796
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
4797
4797
  suggestion: isOutcomePlaceholder ? "Declare `outcomes` on the hub's terminal kanban status so the platform can render the placeholder into a valid array; with none configured the platform drops the parameter instead." : isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
4798
4798
  });
4799
4799
  } else if (!Array.isArray(e)) {
4800
4800
  errors.push({
4801
- path: `${path36}.enum`,
4801
+ path: `${path35}.enum`,
4802
4802
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
4803
4803
  });
4804
4804
  } else if (e.length === 0) {
4805
4805
  errors.push({
4806
- path: `${path36}.enum`,
4806
+ path: `${path35}.enum`,
4807
4807
  message: "enum must not be empty"
4808
4808
  });
4809
4809
  } else {
4810
4810
  for (let i = 0; i < e.length; i++) {
4811
4811
  if (!isPrimitive(e[i])) {
4812
4812
  errors.push({
4813
- path: `${path36}.enum[${i}]`,
4813
+ path: `${path35}.enum[${i}]`,
4814
4814
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
4815
4815
  });
4816
4816
  }
@@ -4820,7 +4820,7 @@ function validateSchema(schema, path36, errors, opts = {}) {
4820
4820
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
4821
4821
  if (key in schema && typeof schema[key] === "boolean") {
4822
4822
  errors.push({
4823
- path: `${path36}.${key}`,
4823
+ path: `${path35}.${key}`,
4824
4824
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
4825
4825
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
4826
4826
  });
@@ -4829,45 +4829,45 @@ function validateSchema(schema, path36, errors, opts = {}) {
4829
4829
  if ("properties" in schema) {
4830
4830
  if (!isRecord(schema.properties)) {
4831
4831
  errors.push({
4832
- path: `${path36}.properties`,
4832
+ path: `${path35}.properties`,
4833
4833
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
4834
4834
  });
4835
4835
  } else {
4836
4836
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
4837
- validateSchema(propSchema, `${path36}.properties.${propName}`, errors, { depth: depth + 1 });
4837
+ validateSchema(propSchema, `${path35}.properties.${propName}`, errors, { depth: depth + 1 });
4838
4838
  }
4839
4839
  }
4840
4840
  }
4841
4841
  if (schemaTypeIncludes(schema, "array") && "items" in schema) {
4842
4842
  if (Array.isArray(schema.items)) {
4843
- schema.items.forEach((sub, i) => validateSchema(sub, `${path36}.items[${i}]`, errors, { depth: depth + 1 }));
4843
+ schema.items.forEach((sub, i) => validateSchema(sub, `${path35}.items[${i}]`, errors, { depth: depth + 1 }));
4844
4844
  } else {
4845
- validateSchema(schema.items, `${path36}.items`, errors, { depth: depth + 1 });
4845
+ validateSchema(schema.items, `${path35}.items`, errors, { depth: depth + 1 });
4846
4846
  }
4847
4847
  }
4848
4848
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
4849
4849
  if (key in schema && isRecord(schema[key])) {
4850
- validateSchema(schema[key], `${path36}.${key}`, errors, { depth: depth + 1 });
4850
+ validateSchema(schema[key], `${path35}.${key}`, errors, { depth: depth + 1 });
4851
4851
  }
4852
4852
  }
4853
4853
  for (const key of SUBSCHEMA_LIST_KEYWORDS) {
4854
4854
  const list = schema[key];
4855
4855
  if (Array.isArray(list)) {
4856
- list.forEach((sub, i) => validateSchema(sub, `${path36}.${key}[${i}]`, errors, { depth: depth + 1 }));
4856
+ list.forEach((sub, i) => validateSchema(sub, `${path35}.${key}[${i}]`, errors, { depth: depth + 1 }));
4857
4857
  }
4858
4858
  }
4859
4859
  for (const key of SUBSCHEMA_MAP_KEYWORDS) {
4860
4860
  const map = schema[key];
4861
4861
  if (isRecord(map)) {
4862
4862
  for (const [name, sub] of Object.entries(map)) {
4863
- validateSchema(sub, `${path36}.${key}.${name}`, errors, { depth: depth + 1 });
4863
+ validateSchema(sub, `${path35}.${key}.${name}`, errors, { depth: depth + 1 });
4864
4864
  }
4865
4865
  }
4866
4866
  }
4867
4867
  const reportedPaths = new Set(errors.map((e) => e.path));
4868
4868
  for (const [k, v] of Object.entries(schema)) {
4869
4869
  if (typeof v !== "string") continue;
4870
- const fieldPath = `${path36}.${k}`;
4870
+ const fieldPath = `${path35}.${k}`;
4871
4871
  if (reportedPaths.has(fieldPath)) continue;
4872
4872
  for (const token of PLACEHOLDER_TOKENS) {
4873
4873
  if (v === token) {
@@ -5012,8 +5012,8 @@ function evalInitialStateError(input) {
5012
5012
  const parsed = evalInitialStateEntry.safeParse(entries[i]);
5013
5013
  if (!parsed.success) {
5014
5014
  const issue = parsed.error.issues[0];
5015
- const path36 = issue?.path.join(".") || "?";
5016
- return `initial_state[${i}].${path36} is invalid: ${issue?.message ?? "malformed"}`;
5015
+ const path35 = issue?.path.join(".") || "?";
5016
+ return `initial_state[${i}].${path35} is invalid: ${issue?.message ?? "malformed"}`;
5017
5017
  }
5018
5018
  if (seenSlugs.has(parsed.data.slug)) {
5019
5019
  return `initial_state[${i}].slug "${parsed.data.slug}" is declared more than once`;
@@ -5201,10 +5201,10 @@ function refineHubAsCodeEvals(config, ctx) {
5201
5201
  function refineHubAsCodeEvalAttachments(config, ctx) {
5202
5202
  const cfg = config;
5203
5203
  const referencedHashes = /* @__PURE__ */ new Set();
5204
- const addTurnIssue = (path36, turn, name) => {
5204
+ const addTurnIssue = (path35, turn, name) => {
5205
5205
  const error = evalTurnAttachmentsError(turn);
5206
5206
  if (error) {
5207
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path36, message: `${name}: ${error}` });
5207
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path35, message: `${name}: ${error}` });
5208
5208
  return;
5209
5209
  }
5210
5210
  for (const hash of collectTurnAttachmentHashes(turn)) referencedHashes.add(hash);
@@ -5350,11 +5350,11 @@ function refineHubAsCodeDelegation(config, ctx) {
5350
5350
  }
5351
5351
  }
5352
5352
  if (del.context_boundary === void 0) return;
5353
- const path36 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
5353
+ const path35 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
5354
5354
  if (del.type !== "hub") {
5355
5355
  ctx.addIssue({
5356
5356
  code: external_exports.ZodIssueCode.custom,
5357
- path: path36,
5357
+ path: path35,
5358
5358
  message: `context_boundary applies only to \`type: hub\` delegations (got type "${String(del.type)}")`
5359
5359
  });
5360
5360
  return;
@@ -5362,7 +5362,7 @@ function refineHubAsCodeDelegation(config, ctx) {
5362
5362
  if (!CONTEXT_BOUNDARIES.includes(del.context_boundary)) {
5363
5363
  ctx.addIssue({
5364
5364
  code: external_exports.ZodIssueCode.custom,
5365
- path: path36,
5365
+ path: path35,
5366
5366
  message: `context_boundary must be one of: ${CONTEXT_BOUNDARIES.join(", ")}`
5367
5367
  });
5368
5368
  }
@@ -8915,10 +8915,10 @@ function toDataProxyPath(v1Path) {
8915
8915
  }
8916
8916
  return `${DATA_PROXY_PREFIX}${v1Path.slice(REKOR_V1_PREFIX.length)}`;
8917
8917
  }
8918
- function withOrgSelector(path36, orgId) {
8919
- if (!orgId) return path36;
8920
- const separator = path36.includes("?") ? "&" : "?";
8921
- return `${path36}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
8918
+ function withOrgSelector(path35, orgId) {
8919
+ if (!orgId) return path35;
8920
+ const separator = path35.includes("?") ? "&" : "?";
8921
+ return `${path35}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
8922
8922
  }
8923
8923
  function dataErrorMessage(err) {
8924
8924
  if (!(err instanceof ApiError)) return null;
@@ -8942,7 +8942,7 @@ var init_api_client = __esm({
8942
8942
  init_sentry();
8943
8943
  init_mask_secrets();
8944
8944
  RETRYABLE_BACKOFF_MS = [500, 1e3, 2e3];
8945
- delay = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
8945
+ delay = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
8946
8946
  ApiError = class extends Error {
8947
8947
  status;
8948
8948
  body;
@@ -8952,13 +8952,13 @@ var init_api_client = __esm({
8952
8952
  * telling them apart by body shape misreads one for the other.
8953
8953
  */
8954
8954
  path;
8955
- constructor(method, path36, status, body) {
8955
+ constructor(method, path35, status, body) {
8956
8956
  const safeBody = maskSecretsInMessage(body);
8957
- super(`API request failed: ${method} ${path36} (${status}): ${safeBody}`);
8957
+ super(`API request failed: ${method} ${path35} (${status}): ${safeBody}`);
8958
8958
  this.name = "ApiError";
8959
8959
  this.status = status;
8960
8960
  this.body = safeBody;
8961
- this.path = path36;
8961
+ this.path = path35;
8962
8962
  }
8963
8963
  /** True when the status code is a 4xx client error (expected user-facing condition, not a bug). */
8964
8964
  get isExpected() {
@@ -9060,8 +9060,8 @@ var init_api_client = __esm({
9060
9060
  ...opts?.check && { check: true }
9061
9061
  });
9062
9062
  }
9063
- async lookup(path36, opts) {
9064
- const params = new URLSearchParams({ path: path36 });
9063
+ async lookup(path35, opts) {
9064
+ const params = new URLSearchParams({ path: path35 });
9065
9065
  if (opts?.organizationId) params.set("organization_id", opts.organizationId);
9066
9066
  return this.request("GET", `/api/ci/lookup?${params.toString()}`);
9067
9067
  }
@@ -9504,9 +9504,9 @@ var init_api_client = __esm({
9504
9504
  * sandbox for this conversation, or the blob was purged).
9505
9505
  */
9506
9506
  async downloadArchiveSandboxFs(hubId, conversationId) {
9507
- const path36 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
9508
- addApiBreadcrumb("GET", path36);
9509
- const url = `${this.apiUrl}${path36}`;
9507
+ const path35 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
9508
+ addApiBreadcrumb("GET", path35);
9509
+ const url = `${this.apiUrl}${path35}`;
9510
9510
  let response = await this.send(url, "GET");
9511
9511
  if (response.status === 401 && this.onUnauthorized) {
9512
9512
  let refreshed;
@@ -9520,7 +9520,7 @@ var init_api_client = __esm({
9520
9520
  }
9521
9521
  }
9522
9522
  if (!response.ok) {
9523
- throw new ApiError("GET", path36, response.status, await response.text());
9523
+ throw new ApiError("GET", path35, response.status, await response.text());
9524
9524
  }
9525
9525
  return new Uint8Array(await response.arrayBuffer());
9526
9526
  }
@@ -9560,9 +9560,9 @@ var init_api_client = __esm({
9560
9560
  `/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
9561
9561
  );
9562
9562
  }
9563
- async request(method, path36, body, extraHeaders, contentType) {
9564
- addApiBreadcrumb(method, path36);
9565
- const url = `${this.apiUrl}${path36}`;
9563
+ async request(method, path35, body, extraHeaders, contentType) {
9564
+ addApiBreadcrumb(method, path35);
9565
+ const url = `${this.apiUrl}${path35}`;
9566
9566
  let refreshedOn401 = false;
9567
9567
  for (let retry = 0; ; retry++) {
9568
9568
  let response = await this.send(url, method, body, extraHeaders, contentType);
@@ -9587,7 +9587,7 @@ var init_api_client = __esm({
9587
9587
  await delay(RETRYABLE_BACKOFF_MS[retry]);
9588
9588
  continue;
9589
9589
  }
9590
- throw new ApiError(method, path36, response.status, errorBody);
9590
+ throw new ApiError(method, path35, response.status, errorBody);
9591
9591
  }
9592
9592
  }
9593
9593
  /**
@@ -9607,7 +9607,7 @@ var init_api_client = __esm({
9607
9607
  * `dataErrorMessage` recovers the envelope's own message for display.
9608
9608
  *
9609
9609
  * `orgId` is the caller's org SELECTOR (the `--org` flag, else the repo's
9610
- * `.wayai.yaml`), not an authorization claim: the backend validates it against
9610
+ * `wayai-ws/wayai.yaml`), not an authorization claim: the backend validates it against
9611
9611
  * the caller's grants and attests only the validated result. A multi-org user
9612
9612
  * needs it to pick; a single-org user can omit it.
9613
9613
  *
@@ -9649,9 +9649,9 @@ var init_api_client = __esm({
9649
9649
  * transient-retry loop — re-streaming a blob is not worth a cold-start probe.
9650
9650
  */
9651
9651
  async dataDownload(v1Path, orgId) {
9652
- const path36 = withOrgSelector(toDataProxyPath(v1Path), orgId);
9653
- addApiBreadcrumb("GET", path36);
9654
- const url = `${this.apiUrl}${path36}`;
9652
+ const path35 = withOrgSelector(toDataProxyPath(v1Path), orgId);
9653
+ addApiBreadcrumb("GET", path35);
9654
+ const url = `${this.apiUrl}${path35}`;
9655
9655
  let response = await this.send(url, "GET");
9656
9656
  if (response.status === 401 && this.onUnauthorized) {
9657
9657
  let refreshed;
@@ -9665,7 +9665,7 @@ var init_api_client = __esm({
9665
9665
  }
9666
9666
  }
9667
9667
  if (!response.ok) {
9668
- throw new ApiError("GET", path36, response.status, await response.text());
9668
+ throw new ApiError("GET", path35, response.status, await response.text());
9669
9669
  }
9670
9670
  return {
9671
9671
  bytes: new Uint8Array(await response.arrayBuffer()),
@@ -9817,12 +9817,12 @@ function validateFollowupLinks2(followups, label, ctx) {
9817
9817
  const linkTargets = /* @__PURE__ */ new Set();
9818
9818
  followups.forEach((followup, i) => {
9819
9819
  const ref = followup?.after_followup_id;
9820
- const path36 = ["followups", i, "after_followup_id"];
9820
+ const path35 = ["followups", i, "after_followup_id"];
9821
9821
  if (followup?.type !== "inactivity_after_before_event") {
9822
9822
  if (ref !== void 0) {
9823
9823
  ctx.addIssue({
9824
9824
  code: external_exports.ZodIssueCode.custom,
9825
- path: path36,
9825
+ path: path35,
9826
9826
  message: `kanban status ${label}: after_followup_id is only valid on inactivity_after_before_event followups (this one is ${followup?.type})`
9827
9827
  });
9828
9828
  }
@@ -9831,7 +9831,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9831
9831
  if (ref === void 0) {
9832
9832
  ctx.addIssue({
9833
9833
  code: external_exports.ZodIssueCode.custom,
9834
- path: path36,
9834
+ path: path35,
9835
9835
  message: `kanban status ${label}: inactivity_after_before_event requires after_followup_id naming the id of a before_event followup in the same status`
9836
9836
  });
9837
9837
  return;
@@ -9841,7 +9841,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9841
9841
  if (matches.length > 1) {
9842
9842
  ctx.addIssue({
9843
9843
  code: external_exports.ZodIssueCode.custom,
9844
- path: path36,
9844
+ path: path35,
9845
9845
  message: `kanban status ${label}: after_followup_id "${ref}" matches ${matches.length} followups \u2014 a link must name exactly one`
9846
9846
  });
9847
9847
  return;
@@ -9853,7 +9853,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9853
9853
  );
9854
9854
  ctx.addIssue({
9855
9855
  code: external_exports.ZodIssueCode.custom,
9856
- path: path36,
9856
+ path: path35,
9857
9857
  message: hasIdlessBeforeEvent ? `kanban status ${label}: after_followup_id "${ref}" matches no followup \u2014 a link target must declare an explicit id, and this status has before_event followups without one` : `kanban status ${label}: after_followup_id "${ref}" matches no followup in this status`
9858
9858
  });
9859
9859
  return;
@@ -9861,7 +9861,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9861
9861
  if (target.type !== "before_event") {
9862
9862
  ctx.addIssue({
9863
9863
  code: external_exports.ZodIssueCode.custom,
9864
- path: path36,
9864
+ path: path35,
9865
9865
  message: `kanban status ${label}: after_followup_id "${ref}" points at a ${target.type} followup \u2014 only before_event followups can anchor a chain`
9866
9866
  });
9867
9867
  }
@@ -9937,12 +9937,12 @@ function typeMatches2(typeField, allowed) {
9937
9937
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
9938
9938
  return false;
9939
9939
  }
9940
- function validateSchema2(schema, path36, errors, opts = {}) {
9940
+ function validateSchema2(schema, path35, errors, opts = {}) {
9941
9941
  if (typeof schema === "boolean") return;
9942
9942
  const depth = opts.depth ?? 0;
9943
9943
  if (depth > MAX_SCHEMA_DEPTH2) {
9944
9944
  errors.push({
9945
- path: path36 || "<root>",
9945
+ path: path35 || "<root>",
9946
9946
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
9947
9947
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
9948
9948
  });
@@ -9950,7 +9950,7 @@ function validateSchema2(schema, path36, errors, opts = {}) {
9950
9950
  }
9951
9951
  if (!isRecord2(schema)) {
9952
9952
  errors.push({
9953
- path: path36,
9953
+ path: path35,
9954
9954
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
9955
9955
  });
9956
9956
  return;
@@ -9958,14 +9958,14 @@ function validateSchema2(schema, path36, errors, opts = {}) {
9958
9958
  if (opts.isRoot) {
9959
9959
  if ("type" in schema && schema.type !== "object") {
9960
9960
  errors.push({
9961
- path: path36 ? `${path36}.type` : "type",
9961
+ path: path35 ? `${path35}.type` : "type",
9962
9962
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
9963
9963
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
9964
9964
  });
9965
9965
  }
9966
9966
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
9967
9967
  errors.push({
9968
- path: `${path36}.type`,
9968
+ path: `${path35}.type`,
9969
9969
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
9970
9970
  });
9971
9971
  }
@@ -9975,25 +9975,25 @@ function validateSchema2(schema, path36, errors, opts = {}) {
9975
9975
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
9976
9976
  const isOutcomePlaceholder = e === "[KANBAN_OUTCOME_VALUES]";
9977
9977
  errors.push({
9978
- path: `${path36}.enum`,
9978
+ path: `${path35}.enum`,
9979
9979
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
9980
9980
  suggestion: isOutcomePlaceholder ? "Declare `outcomes` on the hub's terminal kanban status so the platform can render the placeholder into a valid array; with none configured the platform drops the parameter instead." : isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
9981
9981
  });
9982
9982
  } else if (!Array.isArray(e)) {
9983
9983
  errors.push({
9984
- path: `${path36}.enum`,
9984
+ path: `${path35}.enum`,
9985
9985
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
9986
9986
  });
9987
9987
  } else if (e.length === 0) {
9988
9988
  errors.push({
9989
- path: `${path36}.enum`,
9989
+ path: `${path35}.enum`,
9990
9990
  message: "enum must not be empty"
9991
9991
  });
9992
9992
  } else {
9993
9993
  for (let i = 0; i < e.length; i++) {
9994
9994
  if (!isPrimitive2(e[i])) {
9995
9995
  errors.push({
9996
- path: `${path36}.enum[${i}]`,
9996
+ path: `${path35}.enum[${i}]`,
9997
9997
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
9998
9998
  });
9999
9999
  }
@@ -10003,7 +10003,7 @@ function validateSchema2(schema, path36, errors, opts = {}) {
10003
10003
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
10004
10004
  if (key in schema && typeof schema[key] === "boolean") {
10005
10005
  errors.push({
10006
- path: `${path36}.${key}`,
10006
+ path: `${path35}.${key}`,
10007
10007
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
10008
10008
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
10009
10009
  });
@@ -10012,45 +10012,45 @@ function validateSchema2(schema, path36, errors, opts = {}) {
10012
10012
  if ("properties" in schema) {
10013
10013
  if (!isRecord2(schema.properties)) {
10014
10014
  errors.push({
10015
- path: `${path36}.properties`,
10015
+ path: `${path35}.properties`,
10016
10016
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
10017
10017
  });
10018
10018
  } else {
10019
10019
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
10020
- validateSchema2(propSchema, `${path36}.properties.${propName}`, errors, { depth: depth + 1 });
10020
+ validateSchema2(propSchema, `${path35}.properties.${propName}`, errors, { depth: depth + 1 });
10021
10021
  }
10022
10022
  }
10023
10023
  }
10024
10024
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
10025
10025
  if (Array.isArray(schema.items)) {
10026
- schema.items.forEach((sub, i) => validateSchema2(sub, `${path36}.items[${i}]`, errors, { depth: depth + 1 }));
10026
+ schema.items.forEach((sub, i) => validateSchema2(sub, `${path35}.items[${i}]`, errors, { depth: depth + 1 }));
10027
10027
  } else {
10028
- validateSchema2(schema.items, `${path36}.items`, errors, { depth: depth + 1 });
10028
+ validateSchema2(schema.items, `${path35}.items`, errors, { depth: depth + 1 });
10029
10029
  }
10030
10030
  }
10031
10031
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
10032
10032
  if (key in schema && isRecord2(schema[key])) {
10033
- validateSchema2(schema[key], `${path36}.${key}`, errors, { depth: depth + 1 });
10033
+ validateSchema2(schema[key], `${path35}.${key}`, errors, { depth: depth + 1 });
10034
10034
  }
10035
10035
  }
10036
10036
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
10037
10037
  const list = schema[key];
10038
10038
  if (Array.isArray(list)) {
10039
- list.forEach((sub, i) => validateSchema2(sub, `${path36}.${key}[${i}]`, errors, { depth: depth + 1 }));
10039
+ list.forEach((sub, i) => validateSchema2(sub, `${path35}.${key}[${i}]`, errors, { depth: depth + 1 }));
10040
10040
  }
10041
10041
  }
10042
10042
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
10043
10043
  const map = schema[key];
10044
10044
  if (isRecord2(map)) {
10045
10045
  for (const [name, sub] of Object.entries(map)) {
10046
- validateSchema2(sub, `${path36}.${key}.${name}`, errors, { depth: depth + 1 });
10046
+ validateSchema2(sub, `${path35}.${key}.${name}`, errors, { depth: depth + 1 });
10047
10047
  }
10048
10048
  }
10049
10049
  }
10050
10050
  const reportedPaths = new Set(errors.map((e) => e.path));
10051
10051
  for (const [k, v] of Object.entries(schema)) {
10052
10052
  if (typeof v !== "string") continue;
10053
- const fieldPath = `${path36}.${k}`;
10053
+ const fieldPath = `${path35}.${k}`;
10054
10054
  if (reportedPaths.has(fieldPath)) continue;
10055
10055
  for (const token of PLACEHOLDER_TOKENS2) {
10056
10056
  if (v === token) {
@@ -10195,8 +10195,8 @@ function evalInitialStateError2(input) {
10195
10195
  const parsed = evalInitialStateEntry2.safeParse(entries[i]);
10196
10196
  if (!parsed.success) {
10197
10197
  const issue = parsed.error.issues[0];
10198
- const path36 = issue?.path.join(".") || "?";
10199
- return `initial_state[${i}].${path36} is invalid: ${issue?.message ?? "malformed"}`;
10198
+ const path35 = issue?.path.join(".") || "?";
10199
+ return `initial_state[${i}].${path35} is invalid: ${issue?.message ?? "malformed"}`;
10200
10200
  }
10201
10201
  if (seenSlugs.has(parsed.data.slug)) {
10202
10202
  return `initial_state[${i}].slug "${parsed.data.slug}" is declared more than once`;
@@ -10401,10 +10401,10 @@ function refineHubAsCodeEvals2(config, ctx) {
10401
10401
  function refineHubAsCodeEvalAttachments2(config, ctx) {
10402
10402
  const cfg = config;
10403
10403
  const referencedHashes = /* @__PURE__ */ new Set();
10404
- const addTurnIssue = (path36, turn, name) => {
10404
+ const addTurnIssue = (path35, turn, name) => {
10405
10405
  const error = evalTurnAttachmentsError2(turn);
10406
10406
  if (error) {
10407
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path36, message: `${name}: ${error}` });
10407
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path35, message: `${name}: ${error}` });
10408
10408
  return;
10409
10409
  }
10410
10410
  for (const hash of collectTurnAttachmentHashes2(turn)) referencedHashes.add(hash);
@@ -10550,11 +10550,11 @@ function refineHubAsCodeDelegation2(config, ctx) {
10550
10550
  }
10551
10551
  }
10552
10552
  if (del.context_boundary === void 0) return;
10553
- const path36 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
10553
+ const path35 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
10554
10554
  if (del.type !== "hub") {
10555
10555
  ctx.addIssue({
10556
10556
  code: external_exports.ZodIssueCode.custom,
10557
- path: path36,
10557
+ path: path35,
10558
10558
  message: `context_boundary applies only to \`type: hub\` delegations (got type "${String(del.type)}")`
10559
10559
  });
10560
10560
  return;
@@ -10562,7 +10562,7 @@ function refineHubAsCodeDelegation2(config, ctx) {
10562
10562
  if (!CONTEXT_BOUNDARIES2.includes(del.context_boundary)) {
10563
10563
  ctx.addIssue({
10564
10564
  code: external_exports.ZodIssueCode.custom,
10565
- path: path36,
10565
+ path: path35,
10566
10566
  message: `context_boundary must be one of: ${CONTEXT_BOUNDARIES2.join(", ")}`
10567
10567
  });
10568
10568
  }
@@ -14516,34 +14516,15 @@ function resolveLayout(gitRoot, layout = WAYAI_LAYOUT) {
14516
14516
  const newWs = path.join(gitRoot, layout.wsDir);
14517
14517
  const legacyWs = path.join(gitRoot, layout.legacy.wsDir);
14518
14518
  const legacyOrg = path.join(gitRoot, layout.legacy.orgAtRoot);
14519
- const newExists = isDirectory(newWs);
14520
14519
  const legacyExists = isDirectory(legacyWs) || isDirectory(legacyOrg);
14521
14520
  const basesDir = path.join(newWs, layout.basesSubdir);
14522
- if (newExists) {
14523
- return {
14524
- hubsDir: path.join(newWs, layout.hubsSubdir),
14525
- orgDir: path.join(newWs, layout.orgSubdir),
14526
- basesDir,
14527
- isLegacy: false,
14528
- legacyAlsoPresent: legacyExists
14529
- };
14521
+ const hubsDir = path.join(newWs, layout.hubsSubdir);
14522
+ const orgDir = path.join(newWs, layout.orgSubdir);
14523
+ const newDisplacesLegacy = () => isDirectory(hubsDir) || isDirectory(orgDir) || isDirectory(basesDir);
14524
+ if (legacyExists && !newDisplacesLegacy()) {
14525
+ return { hubsDir: legacyWs, orgDir: legacyOrg, basesDir, isLegacy: true, legacyAlsoPresent: false };
14530
14526
  }
14531
- if (legacyExists) {
14532
- return {
14533
- hubsDir: legacyWs,
14534
- orgDir: legacyOrg,
14535
- basesDir,
14536
- isLegacy: true,
14537
- legacyAlsoPresent: false
14538
- };
14539
- }
14540
- return {
14541
- hubsDir: path.join(newWs, layout.hubsSubdir),
14542
- orgDir: path.join(newWs, layout.orgSubdir),
14543
- basesDir,
14544
- isLegacy: false,
14545
- legacyAlsoPresent: false
14546
- };
14527
+ return { hubsDir, orgDir, basesDir, isLegacy: false, legacyAlsoPresent: legacyExists };
14547
14528
  }
14548
14529
  function resolveBasesDir(gitRoot) {
14549
14530
  return resolveLayout(gitRoot).basesDir;
@@ -14960,111 +14941,344 @@ var init_workspace = __esm({
14960
14941
  }
14961
14942
  });
14962
14943
 
14944
+ // src/lib/workspace-manifest.ts
14945
+ import * as fs3 from "fs";
14946
+ import * as path3 from "path";
14947
+ import * as yaml2 from "js-yaml";
14948
+ function workspaceManifestPath(gitRoot) {
14949
+ return path3.join(gitRoot, WAYAI_LAYOUT.wsDir, WORKSPACE_MANIFEST_FILE);
14950
+ }
14951
+ function rootConfigPath(gitRoot) {
14952
+ return path3.join(gitRoot, ROOT_CONFIG_FILE);
14953
+ }
14954
+ function loadYamlMapping(file) {
14955
+ let raw;
14956
+ try {
14957
+ raw = fs3.readFileSync(file, "utf-8");
14958
+ } catch (err) {
14959
+ if (err.code === "ENOENT") return { kind: "absent" };
14960
+ return { kind: "malformed", reason: err instanceof Error ? err.message : String(err) };
14961
+ }
14962
+ let doc;
14963
+ try {
14964
+ doc = yaml2.load(raw);
14965
+ } catch (err) {
14966
+ return { kind: "malformed", reason: err instanceof Error ? err.message : String(err) };
14967
+ }
14968
+ if (doc === void 0 || doc === null) return { kind: "ok", doc: {} };
14969
+ if (typeof doc !== "object" || Array.isArray(doc)) {
14970
+ return { kind: "malformed", reason: "expected a YAML mapping of settings" };
14971
+ }
14972
+ return { kind: "ok", doc };
14973
+ }
14974
+ function loadWorkspaceManifest(gitRoot) {
14975
+ return loadYamlMapping(workspaceManifestPath(gitRoot));
14976
+ }
14977
+ var WORKSPACE_MANIFEST_FILE, WORKSPACE_MANIFEST_LABEL, ROOT_CONFIG_FILE;
14978
+ var init_workspace_manifest = __esm({
14979
+ "src/lib/workspace-manifest.ts"() {
14980
+ "use strict";
14981
+ init_layout();
14982
+ WORKSPACE_MANIFEST_FILE = "wayai.yaml";
14983
+ WORKSPACE_MANIFEST_LABEL = `${WAYAI_LAYOUT.wsDir}/${WORKSPACE_MANIFEST_FILE}`;
14984
+ ROOT_CONFIG_FILE = ".wayai.yaml";
14985
+ }
14986
+ });
14987
+
14963
14988
  // src/lib/repo-config.ts
14964
14989
  var repo_config_exports = {};
14965
14990
  __export(repo_config_exports, {
14991
+ _resetRepoConfigNotices: () => _resetRepoConfigNotices,
14992
+ planOrgMigration: () => planOrgMigration,
14966
14993
  readRepoConfig: () => readRepoConfig,
14994
+ readRepoConfigUnlessBlocked: () => readRepoConfigUnlessBlocked,
14995
+ readRepoScopeBlocker: () => readRepoScopeBlocker,
14967
14996
  requireRepoConfig: () => requireRepoConfig,
14997
+ resolveRepoConfig: () => resolveRepoConfig,
14968
14998
  writeRepoConfig: () => writeRepoConfig
14969
14999
  });
14970
- import * as fs3 from "fs";
14971
- import * as path3 from "path";
14972
- import * as yaml2 from "js-yaml";
14973
- function warnAboutLegacyFields(found) {
14974
- if (legacyFieldsWarned || found.length === 0) return;
14975
- legacyFieldsWarned = true;
14976
- console.warn(
14977
- `Note: .wayai.yaml contains deprecated field(s) ${found.join(", ")}. These are ignored \u2014 only organization_id is used now. You can remove them.`
14978
- );
15000
+ import * as fs4 from "fs";
15001
+ import * as path4 from "path";
15002
+ import * as yaml3 from "js-yaml";
15003
+ function noticeOnce(key, emit) {
15004
+ if (noticed.has(key)) return;
15005
+ noticed.add(key);
15006
+ emit();
15007
+ }
15008
+ function _resetRepoConfigNotices() {
15009
+ noticed.clear();
15010
+ }
15011
+ function readOrgFields(doc) {
15012
+ const raw = doc.organization_id;
15013
+ if (raw === void 0 || raw === null) {
15014
+ if (typeof doc.organization === "string") {
15015
+ return {
15016
+ kind: "invalid",
15017
+ detail: "it uses the old format (organization: <name>) \u2014 replace it with organization_id: <uuid>"
15018
+ };
15019
+ }
15020
+ return { kind: "none" };
15021
+ }
15022
+ if (typeof raw !== "string" || !UUID_RE2.test(raw.trim())) {
15023
+ return { kind: "invalid", detail: "organization_id must be a UUID" };
15024
+ }
15025
+ const name = typeof doc.organization_name === "string" && doc.organization_name.trim() ? doc.organization_name.trim() : void 0;
15026
+ const config = { organization_id: raw.trim() };
15027
+ if (name) config.organization_name = name;
15028
+ return { kind: "declared", config };
15029
+ }
15030
+ function declarationFrom(load11) {
15031
+ if (load11.kind === "absent") return { kind: "none" };
15032
+ if (load11.kind === "malformed") return { kind: "invalid", detail: `it does not parse (${load11.reason})` };
15033
+ return readOrgFields(load11.doc);
15034
+ }
15035
+ function legacyFieldsIn(load11) {
15036
+ if (load11.kind !== "ok") return [];
15037
+ return LEGACY_FIELDS.filter((f) => load11.doc[f] !== void 0);
15038
+ }
15039
+ function resolve2(gitRoot) {
15040
+ const manifestFile = workspaceManifestPath(gitRoot);
15041
+ const manifest = declarationFrom(loadWorkspaceManifest(gitRoot));
15042
+ if (manifest.kind === "invalid") {
15043
+ return { kind: "invalid", source: "manifest", file: manifestFile, detail: manifest.detail };
15044
+ }
15045
+ const rootFile = rootConfigPath(gitRoot);
15046
+ const rootLoad = loadYamlMapping(rootFile);
15047
+ const root = declarationFrom(rootLoad);
15048
+ if (manifest.kind === "declared") {
15049
+ if (root.kind === "declared" && root.config.organization_id !== manifest.config.organization_id) {
15050
+ return {
15051
+ kind: "conflict",
15052
+ manifestOrg: manifest.config.organization_id,
15053
+ rootOrg: root.config.organization_id
15054
+ };
15055
+ }
15056
+ return {
15057
+ kind: "ok",
15058
+ resolved: { config: manifest.config, source: "manifest", path: manifestFile },
15059
+ legacyRootFields: []
15060
+ };
15061
+ }
15062
+ if (root.kind === "declared") {
15063
+ return {
15064
+ kind: "ok",
15065
+ resolved: { config: root.config, source: "root", path: rootFile },
15066
+ legacyRootFields: legacyFieldsIn(rootLoad)
15067
+ };
15068
+ }
15069
+ if (root.kind === "invalid") {
15070
+ return { kind: "invalid", source: "root", file: rootFile, detail: root.detail };
15071
+ }
15072
+ return { kind: "none" };
15073
+ }
15074
+ function conflictMessage(manifestOrg, rootOrg, remedy = RECONCILE_REMEDY) {
15075
+ return [
15076
+ "Refusing to guess the organization: two files in this repo name different ones.",
15077
+ ` ${WORKSPACE_MANIFEST_LABEL} -> ${manifestOrg} (the workspace manifest)`,
15078
+ ` ${ROOT_CONFIG_FILE} -> ${rootOrg} (deprecated fallback)`,
15079
+ "",
15080
+ ...remedy
15081
+ ].join("\n");
15082
+ }
15083
+ function rebindRefusal(requestedOrg, rootOrg) {
15084
+ return [
15085
+ `Refusing to re-bind the organization: ${ROOT_CONFIG_FILE} still names a different one, and it is read as a fallback.`,
15086
+ ` requested -> ${requestedOrg}`,
15087
+ ` ${ROOT_CONFIG_FILE} -> ${rootOrg} (deprecated fallback)`,
15088
+ "",
15089
+ `Delete ${ROOT_CONFIG_FILE} (or set its organization_id to ${requestedOrg}), then re-run.`,
15090
+ "Nothing was written \u2014 this repo still resolves to the organization it had."
15091
+ ].join("\n");
15092
+ }
15093
+ function invalidMessage(file, detail) {
15094
+ return [
15095
+ `Invalid organization binding in ${file}: ${detail}.`,
15096
+ "",
15097
+ " organization_id: <your-org-uuid>",
15098
+ "",
15099
+ "Run `wayai init` to set up the workspace."
15100
+ ].join("\n");
15101
+ }
15102
+ function missingMessage() {
15103
+ return [
15104
+ "This repository is not scoped to an organization.",
15105
+ `Declare it in ${WORKSPACE_MANIFEST_LABEL}:`,
15106
+ "",
15107
+ " organization_id: <your-org-uuid>",
15108
+ "",
15109
+ "Run `wayai init` to set up the workspace."
15110
+ ].join("\n");
15111
+ }
15112
+ function report(r, posture) {
15113
+ switch (r.kind) {
15114
+ case "ok":
15115
+ if (r.resolved.source !== "root") return;
15116
+ noticeOnce(
15117
+ "root-fallback",
15118
+ () => console.warn(
15119
+ `Note: this repo's organization binding is still in ${ROOT_CONFIG_FILE} at the repository root. It belongs in the workspace manifest ${WORKSPACE_MANIFEST_LABEL} \u2014 run \`wayai migrate\` to copy it there.`
15120
+ )
15121
+ );
15122
+ if (r.legacyRootFields.length > 0) {
15123
+ noticeOnce(
15124
+ "legacy-fields",
15125
+ () => console.warn(
15126
+ `Note: ${ROOT_CONFIG_FILE} contains deprecated field(s) ${r.legacyRootFields.join(", ")}. These are ignored \u2014 only organization_id is used now. You can remove them.`
15127
+ )
15128
+ );
15129
+ }
15130
+ return;
15131
+ case "invalid": {
15132
+ const emit = () => console.error(invalidMessage(r.file, r.detail));
15133
+ if (posture === "hard") emit();
15134
+ else noticeOnce(`invalid:${r.source}`, emit);
15135
+ return;
15136
+ }
15137
+ case "conflict":
15138
+ console.error(conflictMessage(r.manifestOrg, r.rootOrg));
15139
+ return;
15140
+ case "none":
15141
+ if (posture === "hard") console.error(missingMessage());
15142
+ return;
15143
+ }
15144
+ }
15145
+ function resolveRepoConfig(root) {
15146
+ const gitRoot = root ?? findGitRoot();
15147
+ if (!gitRoot) return null;
15148
+ const r = resolve2(gitRoot);
15149
+ report(r, "soft");
15150
+ return r.kind === "ok" ? r.resolved : null;
14979
15151
  }
14980
15152
  function readRepoConfig(root) {
15153
+ return resolveRepoConfig(root)?.config ?? null;
15154
+ }
15155
+ function readRepoConfigUnlessBlocked(root) {
14981
15156
  const gitRoot = root ?? findGitRoot();
14982
15157
  if (!gitRoot) return null;
14983
- const configPath3 = path3.join(gitRoot, ".wayai.yaml");
14984
- if (!fs3.existsSync(configPath3)) return null;
14985
- try {
14986
- const content = fs3.readFileSync(configPath3, "utf-8");
14987
- const config = yaml2.load(content);
14988
- if (!config?.organization_id || typeof config.organization_id !== "string") {
14989
- if (config?.organization && typeof config.organization === "string") {
14990
- console.error(
14991
- ".wayai.yaml uses the old format (organization: name). Replace it with organization_id: <uuid>."
14992
- );
14993
- }
15158
+ const r = resolve2(gitRoot);
15159
+ switch (r.kind) {
15160
+ case "ok":
15161
+ report(r, "soft");
15162
+ return r.resolved.config;
15163
+ case "none":
14994
15164
  return null;
14995
- }
14996
- if (!UUID_RE2.test(config.organization_id)) return null;
14997
- const presentLegacy = LEGACY_FIELDS.filter((f) => config[f] !== void 0);
14998
- warnAboutLegacyFields(presentLegacy);
14999
- const organizationName = typeof config.organization_name === "string" && config.organization_name.trim() ? config.organization_name.trim() : void 0;
15165
+ case "conflict":
15166
+ throw expected(conflictMessage(r.manifestOrg, r.rootOrg));
15167
+ case "invalid":
15168
+ throw expected(invalidMessage(r.file, r.detail));
15169
+ }
15170
+ }
15171
+ function requireRepoConfig(root) {
15172
+ const gitRoot = root ?? findGitRoot();
15173
+ const r = gitRoot ? resolve2(gitRoot) : { kind: "none" };
15174
+ report(r, "hard");
15175
+ if (r.kind === "ok") return r.resolved.config;
15176
+ return process.exit(1);
15177
+ }
15178
+ function readRepoScopeBlocker(root) {
15179
+ const gitRoot = root ?? findGitRoot();
15180
+ if (!gitRoot) return null;
15181
+ const r = resolve2(gitRoot);
15182
+ if (r.kind === "conflict") {
15000
15183
  return {
15001
- organization_id: config.organization_id,
15002
- organization_name: organizationName
15184
+ kind: "conflict",
15185
+ manifest_organization_id: r.manifestOrg,
15186
+ root_organization_id: r.rootOrg
15003
15187
  };
15004
- } catch {
15005
- return null;
15006
15188
  }
15189
+ if (r.kind === "invalid") return { kind: "invalid", path: r.file, detail: r.detail };
15190
+ return null;
15007
15191
  }
15008
- function requireRepoConfig(root) {
15009
- const config = readRepoConfig(root);
15010
- if (!config) {
15011
- console.error("Missing or invalid .wayai.yaml at the repository root.");
15012
- console.error("The file must contain organization_id:");
15013
- console.error("");
15014
- console.error(" organization_id: <your-org-uuid>");
15015
- console.error("");
15016
- console.error("Run `wayai init` to set up the workspace.");
15017
- process.exit(1);
15192
+ function planOrgMigration(gitRoot) {
15193
+ const manifest = declarationFrom(loadWorkspaceManifest(gitRoot));
15194
+ const root = declarationFrom(loadYamlMapping(rootConfigPath(gitRoot)));
15195
+ if (root.kind !== "declared") return { kind: "nothing" };
15196
+ if (manifest.kind === "declared") {
15197
+ if (manifest.config.organization_id === root.config.organization_id) return { kind: "nothing" };
15198
+ return {
15199
+ kind: "refuse",
15200
+ message: conflictMessage(manifest.config.organization_id, root.config.organization_id, [
15201
+ "Reconcile them by hand \u2014 migrate cannot know which one you meant."
15202
+ ])
15203
+ };
15018
15204
  }
15019
- return config;
15205
+ if (manifest.kind === "invalid") {
15206
+ return {
15207
+ kind: "refuse",
15208
+ message: `Cannot copy the organization binding: ${WORKSPACE_MANIFEST_LABEL} is unusable \u2014 ${manifest.detail}. Fix or delete it, then re-run \`wayai migrate\`.`
15209
+ };
15210
+ }
15211
+ return { kind: "copy", config: root.config };
15020
15212
  }
15021
15213
  function writeRepoConfig(config, root) {
15022
15214
  const gitRoot = root ?? findGitRoot();
15023
15215
  if (!gitRoot) {
15024
15216
  throw new Error("Not inside a git repository. Run `git init` first.");
15025
15217
  }
15026
- const configPath3 = path3.join(gitRoot, ".wayai.yaml");
15218
+ const stale = declarationFrom(loadYamlMapping(rootConfigPath(gitRoot)));
15219
+ if (stale.kind === "declared" && stale.config.organization_id !== config.organization_id) {
15220
+ throw expected(rebindRefusal(config.organization_id, stale.config.organization_id));
15221
+ }
15222
+ const load11 = loadWorkspaceManifest(gitRoot);
15223
+ if (load11.kind === "malformed") {
15224
+ throw expected(
15225
+ `Cannot write the organization binding: ${WORKSPACE_MANIFEST_LABEL} does not parse (${load11.reason}). Fix or delete it, then re-run.`
15226
+ );
15227
+ }
15228
+ const { organization_id: _id, organization_name: _name, ...rest } = load11.kind === "ok" ? load11.doc : {};
15027
15229
  const doc = { organization_id: config.organization_id };
15028
15230
  if (config.organization_name?.trim()) doc.organization_name = config.organization_name.trim();
15029
- const content = yaml2.dump(doc, { quotingType: '"', forceQuotes: false });
15030
- fs3.writeFileSync(configPath3, content, "utf-8");
15031
- return configPath3;
15231
+ Object.assign(doc, rest);
15232
+ const file = workspaceManifestPath(gitRoot);
15233
+ fs4.mkdirSync(path4.dirname(file), { recursive: true });
15234
+ fs4.writeFileSync(
15235
+ file,
15236
+ yaml3.dump(doc, { lineWidth: -1, quotingType: '"', forceQuotes: false }),
15237
+ "utf-8"
15238
+ );
15239
+ return file;
15032
15240
  }
15033
- var LEGACY_FIELDS, legacyFieldsWarned;
15241
+ var LEGACY_FIELDS, noticed, RECONCILE_REMEDY;
15034
15242
  var init_repo_config = __esm({
15035
15243
  "src/lib/repo-config.ts"() {
15036
15244
  "use strict";
15245
+ init_expected();
15037
15246
  init_workspace();
15038
15247
  init_utils();
15248
+ init_workspace_manifest();
15039
15249
  LEGACY_FIELDS = ["project_id", "project_name", "hub_id", "hub_name"];
15040
- legacyFieldsWarned = false;
15250
+ noticed = /* @__PURE__ */ new Set();
15251
+ RECONCILE_REMEDY = [
15252
+ "Keep the organization_id you actually work in and delete the other declaration.",
15253
+ `\`wayai init\` rewrites ${WORKSPACE_MANIFEST_LABEL}; the root file is yours to remove.`
15254
+ ];
15041
15255
  }
15042
15256
  });
15043
15257
 
15044
15258
  // src/lib/utils.ts
15045
- import * as fs4 from "fs";
15046
- import * as path4 from "path";
15259
+ import * as fs5 from "fs";
15260
+ import * as path5 from "path";
15047
15261
  import * as readline from "readline";
15048
15262
  function prompt(question) {
15049
15263
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
15050
- return new Promise((resolve7) => {
15264
+ return new Promise((resolve8) => {
15051
15265
  rl.question(question, (answer) => {
15052
15266
  rl.close();
15053
- resolve7(answer.trim());
15267
+ resolve8(answer.trim());
15054
15268
  });
15055
15269
  });
15056
15270
  }
15057
15271
  function confirm(question) {
15058
15272
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
15059
- return new Promise((resolve7) => {
15273
+ return new Promise((resolve8) => {
15060
15274
  rl.question(`${question} [y/N]: `, (answer) => {
15061
15275
  rl.close();
15062
- resolve7(answer.trim().toLowerCase() === "y");
15276
+ resolve8(answer.trim().toLowerCase() === "y");
15063
15277
  });
15064
15278
  });
15065
15279
  }
15066
15280
  function promptSecret(question) {
15067
- return new Promise((resolve7) => {
15281
+ return new Promise((resolve8) => {
15068
15282
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
15069
15283
  const originalWrite = rl._writeToOutput;
15070
15284
  let firstWrite = true;
@@ -15080,25 +15294,25 @@ function promptSecret(question) {
15080
15294
  rl._writeToOutput = originalWrite;
15081
15295
  process.stdout.write("\n");
15082
15296
  rl.close();
15083
- resolve7(answer);
15297
+ resolve8(answer);
15084
15298
  });
15085
15299
  });
15086
15300
  }
15087
15301
  function readStdin() {
15088
- return new Promise((resolve7, reject) => {
15302
+ return new Promise((resolve8, reject) => {
15089
15303
  let data = "";
15090
15304
  process.stdin.setEncoding("utf-8");
15091
15305
  process.stdin.on("data", (chunk) => {
15092
15306
  data += chunk;
15093
15307
  });
15094
- process.stdin.on("end", () => resolve7(data.trim()));
15308
+ process.stdin.on("end", () => resolve8(data.trim()));
15095
15309
  process.stdin.on("error", reject);
15096
15310
  });
15097
15311
  }
15098
15312
  function resolveHubYamlPath(hubFolder) {
15099
15313
  for (const filename of ["hub.yaml", "wayai.yaml"]) {
15100
- const yamlPath = path4.join(hubFolder, filename);
15101
- if (fs4.existsSync(yamlPath)) return yamlPath;
15314
+ const yamlPath = path5.join(hubFolder, filename);
15315
+ if (fs5.existsSync(yamlPath)) return yamlPath;
15102
15316
  }
15103
15317
  return null;
15104
15318
  }
@@ -15175,8 +15389,8 @@ async function resolveOrganizationId(client, orgIdFlag) {
15175
15389
  }
15176
15390
  return { organizationId: orgIdFlag };
15177
15391
  }
15178
- const { readRepoConfig: readRepoConfig2 } = await Promise.resolve().then(() => (init_repo_config(), repo_config_exports));
15179
- const repoConfig = readRepoConfig2();
15392
+ const { readRepoConfigUnlessBlocked: readRepoConfigUnlessBlocked2 } = await Promise.resolve().then(() => (init_repo_config(), repo_config_exports));
15393
+ const repoConfig = readRepoConfigUnlessBlocked2();
15180
15394
  if (repoConfig) {
15181
15395
  return { organizationId: repoConfig.organization_id, organizationName: repoConfig.organization_name };
15182
15396
  }
@@ -15312,7 +15526,7 @@ var init_registry = __esm({
15312
15526
  surviving: null,
15313
15527
  clause: 1,
15314
15528
  shipped: true,
15315
- reason: "One root org binding (`.wayai.yaml`), so one `init`."
15529
+ reason: "One org binding per repo (`wayai-ws/wayai.yaml`), so one `init`."
15316
15530
  },
15317
15531
  {
15318
15532
  original: "update",
@@ -15442,7 +15656,7 @@ var init_registry = __esm({
15442
15656
  surviving: "bases use",
15443
15657
  clause: 3,
15444
15658
  shipped: true,
15445
- reason: "WayAI's `use` binds a hub; this binds a base."
15659
+ reason: "Deprecated since the worktree scope merged into one set-valued file: `wayai use bases/<id>` covers this. Kept as an alias for its JSON output shape."
15446
15660
  },
15447
15661
  {
15448
15662
  original: "unbind",
@@ -15450,7 +15664,7 @@ var init_registry = __esm({
15450
15664
  surviving: "bases unbind",
15451
15665
  clause: 3,
15452
15666
  shipped: true,
15453
- reason: "WayAI's `unbind` clears the hub binding; this clears the base binding."
15667
+ reason: "Deprecated alongside `bases use`: `wayai unbind bases/<id>` covers this. It still clears only the bases axis of the worktree scope, never the hubs one."
15454
15668
  },
15455
15669
  {
15456
15670
  original: "bases promote",
@@ -15563,17 +15777,17 @@ var init_registry = __esm({
15563
15777
  });
15564
15778
 
15565
15779
  // src/lib/version-cache.ts
15566
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync } from "fs";
15567
- import { dirname as dirname3, join as join6 } from "path";
15780
+ import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
15781
+ import { dirname as dirname4, join as join6 } from "path";
15568
15782
  import { homedir as homedir2 } from "os";
15569
15783
  function getVersionCachePath(filename = CLI_CACHE_FILE) {
15570
15784
  return join6(homedir2(), ".wayai", filename);
15571
15785
  }
15572
15786
  function readVersionCache(filename = CLI_CACHE_FILE) {
15573
15787
  try {
15574
- const path36 = getVersionCachePath(filename);
15575
- if (!existsSync4(path36)) return null;
15576
- const parsed = JSON.parse(readFileSync5(path36, "utf-8"));
15788
+ const path35 = getVersionCachePath(filename);
15789
+ if (!existsSync3(path35)) return null;
15790
+ const parsed = JSON.parse(readFileSync5(path35, "utf-8"));
15577
15791
  if (typeof parsed.lastCheck !== "number") return null;
15578
15792
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
15579
15793
  return parsed;
@@ -15593,10 +15807,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
15593
15807
  return Date.now() - cache.lastCheck > maxAgeMs;
15594
15808
  }
15595
15809
  function writeVersionCache(filename, cache) {
15596
- const path36 = getVersionCachePath(filename);
15597
- const dir = dirname3(path36);
15598
- if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
15599
- writeFileSync2(path36, JSON.stringify(cache));
15810
+ const path35 = getVersionCachePath(filename);
15811
+ const dir = dirname4(path35);
15812
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
15813
+ writeFileSync2(path35, JSON.stringify(cache));
15600
15814
  }
15601
15815
  function touchVersionCache(filename) {
15602
15816
  writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
@@ -15613,9 +15827,9 @@ var init_version_cache = __esm({
15613
15827
  });
15614
15828
 
15615
15829
  // src/lib/skill-version.ts
15616
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
15830
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
15617
15831
  import { join as join7 } from "path";
15618
- import * as yaml3 from "js-yaml";
15832
+ import * as yaml4 from "js-yaml";
15619
15833
  function skillInstallPaths(skillName) {
15620
15834
  return HARNESS_SKILL_DIRS.map((dir) => `${dir}/skills/${skillName}/${SKILL_FILENAME}`);
15621
15835
  }
@@ -15624,7 +15838,7 @@ function parseFrontmatterVersion(content) {
15624
15838
  if (!match) return null;
15625
15839
  let parsed;
15626
15840
  try {
15627
- parsed = yaml3.load(match[1]);
15841
+ parsed = yaml4.load(match[1]);
15628
15842
  } catch {
15629
15843
  return null;
15630
15844
  }
@@ -15635,14 +15849,14 @@ function parseFrontmatterVersion(content) {
15635
15849
  function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
15636
15850
  const found = [];
15637
15851
  for (const rel of paths) {
15638
- const path36 = join7(projectRoot, rel);
15639
- if (!existsSync5(path36)) continue;
15852
+ const path35 = join7(projectRoot, rel);
15853
+ if (!existsSync4(path35)) continue;
15640
15854
  let version = null;
15641
15855
  try {
15642
- version = parseFrontmatterVersion(readFileSync6(path36, "utf-8"));
15856
+ version = parseFrontmatterVersion(readFileSync6(path35, "utf-8"));
15643
15857
  } catch {
15644
15858
  }
15645
- found.push({ path: path36, version });
15859
+ found.push({ path: path35, version });
15646
15860
  }
15647
15861
  return found;
15648
15862
  }
@@ -15673,22 +15887,22 @@ __export(config_exports, {
15673
15887
  readConfig: () => readConfig,
15674
15888
  writeConfig: () => writeConfig
15675
15889
  });
15676
- import * as fs5 from "fs";
15677
- import * as path5 from "path";
15890
+ import * as fs6 from "fs";
15891
+ import * as path6 from "path";
15678
15892
  import * as os from "os";
15679
15893
  function configDir() {
15680
- return path5.join(os.homedir(), ".wayai");
15894
+ return path6.join(os.homedir(), ".wayai");
15681
15895
  }
15682
15896
  function configPath() {
15683
- return path5.join(configDir(), "config.json");
15897
+ return path6.join(configDir(), "config.json");
15684
15898
  }
15685
15899
  function getConfigPath() {
15686
15900
  return configPath();
15687
15901
  }
15688
15902
  function readConfig() {
15689
- if (!fs5.existsSync(configPath())) return null;
15903
+ if (!fs6.existsSync(configPath())) return null;
15690
15904
  try {
15691
- const content = fs5.readFileSync(configPath(), "utf-8");
15905
+ const content = fs6.readFileSync(configPath(), "utf-8");
15692
15906
  const parsed = JSON.parse(content);
15693
15907
  if (!parsed.api_url) return null;
15694
15908
  return {
@@ -15701,13 +15915,13 @@ function readConfig() {
15701
15915
  }
15702
15916
  }
15703
15917
  function writeConfig(config) {
15704
- if (!fs5.existsSync(configDir())) {
15705
- fs5.mkdirSync(configDir(), { recursive: true });
15918
+ if (!fs6.existsSync(configDir())) {
15919
+ fs6.mkdirSync(configDir(), { recursive: true });
15706
15920
  }
15707
15921
  let existing = {};
15708
- if (fs5.existsSync(configPath())) {
15922
+ if (fs6.existsSync(configPath())) {
15709
15923
  try {
15710
- existing = JSON.parse(fs5.readFileSync(configPath(), "utf-8"));
15924
+ existing = JSON.parse(fs6.readFileSync(configPath(), "utf-8"));
15711
15925
  } catch {
15712
15926
  }
15713
15927
  }
@@ -15715,14 +15929,14 @@ function writeConfig(config) {
15715
15929
  delete existing.access_token;
15716
15930
  delete existing.refresh_token;
15717
15931
  Object.assign(existing, config);
15718
- fs5.writeFileSync(configPath(), JSON.stringify(existing, null, 2) + "\n", {
15932
+ fs6.writeFileSync(configPath(), JSON.stringify(existing, null, 2) + "\n", {
15719
15933
  mode: 384
15720
15934
  // owner-only read/write
15721
15935
  });
15722
15936
  }
15723
15937
  function deleteConfig() {
15724
- if (fs5.existsSync(configPath())) {
15725
- fs5.unlinkSync(configPath());
15938
+ if (fs6.existsSync(configPath())) {
15939
+ fs6.unlinkSync(configPath());
15726
15940
  }
15727
15941
  }
15728
15942
  var init_config = __esm({
@@ -15732,14 +15946,14 @@ var init_config = __esm({
15732
15946
  });
15733
15947
 
15734
15948
  // src/lib/token-store.ts
15735
- import * as fs6 from "fs";
15736
- import * as path6 from "path";
15949
+ import * as fs7 from "fs";
15950
+ import * as path7 from "path";
15737
15951
  import * as os2 from "os";
15738
15952
  function configDir2() {
15739
- return path6.join(os2.homedir(), ".wayai");
15953
+ return path7.join(os2.homedir(), ".wayai");
15740
15954
  }
15741
15955
  function configPath2() {
15742
- return path6.join(configDir2(), "config.json");
15956
+ return path7.join(configDir2(), "config.json");
15743
15957
  }
15744
15958
  function manualCleanupCommand(account) {
15745
15959
  if (process.platform === "darwin") {
@@ -15791,9 +16005,9 @@ async function getKeyring() {
15791
16005
  return keyringCache;
15792
16006
  }
15793
16007
  function readLegacyConfigFile() {
15794
- if (!fs6.existsSync(configPath2())) return null;
16008
+ if (!fs7.existsSync(configPath2())) return null;
15795
16009
  try {
15796
- return JSON.parse(fs6.readFileSync(configPath2(), "utf-8"));
16010
+ return JSON.parse(fs7.readFileSync(configPath2(), "utf-8"));
15797
16011
  } catch {
15798
16012
  return null;
15799
16013
  }
@@ -15806,7 +16020,7 @@ function stripTokensFromFile() {
15806
16020
  if (k === "token" || k === "access_token" || k === "refresh_token") continue;
15807
16021
  cleaned[k] = v;
15808
16022
  }
15809
- fs6.writeFileSync(configPath2(), JSON.stringify(cleaned, null, 2) + "\n", { mode: 384 });
16023
+ fs7.writeFileSync(configPath2(), JSON.stringify(cleaned, null, 2) + "\n", { mode: 384 });
15810
16024
  }
15811
16025
  async function migrateLegacyTokens() {
15812
16026
  const legacy = readLegacyConfigFile();
@@ -15957,8 +16171,8 @@ async function clearTokens() {
15957
16171
  }
15958
16172
  }
15959
16173
  }
15960
- if (fs6.existsSync(configPath2())) {
15961
- fs6.unlinkSync(configPath2());
16174
+ if (fs7.existsSync(configPath2())) {
16175
+ fs7.unlinkSync(configPath2());
15962
16176
  }
15963
16177
  }
15964
16178
  function readMetadataExpiresAt() {
@@ -15972,7 +16186,7 @@ function writeMetadataExpiresAt(expires_at) {
15972
16186
  delete existing.refresh_token;
15973
16187
  existing.token_expires_at = expires_at;
15974
16188
  ensureConfigDir();
15975
- fs6.writeFileSync(configPath2(), JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
16189
+ fs7.writeFileSync(configPath2(), JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
15976
16190
  }
15977
16191
  function clearMetadataExpiresAt() {
15978
16192
  const existing = readLegacyConfigFile();
@@ -15982,11 +16196,11 @@ function clearMetadataExpiresAt() {
15982
16196
  delete existing.refresh_token;
15983
16197
  delete existing.token_expires_at;
15984
16198
  ensureConfigDir();
15985
- fs6.writeFileSync(configPath2(), JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
16199
+ fs7.writeFileSync(configPath2(), JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
15986
16200
  }
15987
16201
  function ensureConfigDir() {
15988
- if (!fs6.existsSync(configDir2())) {
15989
- fs6.mkdirSync(configDir2(), { recursive: true });
16202
+ if (!fs7.existsSync(configDir2())) {
16203
+ fs7.mkdirSync(configDir2(), { recursive: true });
15990
16204
  }
15991
16205
  }
15992
16206
  function writeFileFallback(tokens) {
@@ -15997,7 +16211,7 @@ function writeFileFallback(tokens) {
15997
16211
  delete existing.token_expires_at;
15998
16212
  Object.assign(existing, tokens);
15999
16213
  ensureConfigDir();
16000
- fs6.writeFileSync(configPath2(), JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
16214
+ fs7.writeFileSync(configPath2(), JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
16001
16215
  }
16002
16216
  var KEYRING_SERVICE, KEY_WAY_TOKEN, KEY_OAUTH_ACCESS, KEY_OAUTH_REFRESH, keyringCache, StaleKeyringSlotError;
16003
16217
  var init_token_store = __esm({
@@ -16106,7 +16320,7 @@ async function validateToken(apiUrl, token) {
16106
16320
  }
16107
16321
  }
16108
16322
  function startCallbackServer(port, expectedState, timeoutMs = 12e4) {
16109
- return new Promise((resolve7, reject) => {
16323
+ return new Promise((resolve8, reject) => {
16110
16324
  const server = http.createServer((req, res) => {
16111
16325
  const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
16112
16326
  if (url.pathname === "/callback") {
@@ -16132,7 +16346,7 @@ function startCallbackServer(port, expectedState, timeoutMs = 12e4) {
16132
16346
  res.writeHead(200, { "Content-Type": "text/html" });
16133
16347
  res.end("<html><body><h2>Login successful!</h2><p>You can close this tab and return to your terminal.</p></body></html>");
16134
16348
  server.close();
16135
- resolve7({ code, port });
16349
+ resolve8({ code, port });
16136
16350
  } else {
16137
16351
  res.writeHead(400, { "Content-Type": "text/html" });
16138
16352
  res.end("<html><body><h2>Login failed</h2><p>No authorization code received</p></body></html>");
@@ -16239,14 +16453,14 @@ __export(skill_symlink_exports, {
16239
16453
  healClaudeSkillLink: () => healClaudeSkillLink,
16240
16454
  healSkillLinkForCommand: () => healSkillLinkForCommand
16241
16455
  });
16242
- import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync4, rmSync, symlinkSync } from "fs";
16456
+ import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, rmSync, symlinkSync } from "fs";
16243
16457
  import { join as join10 } from "path";
16244
16458
  function healClaudeSkillLink(root) {
16245
16459
  try {
16246
16460
  const source = join10(root, ".agents", "skills", SKILL_NAME);
16247
- if (!existsSync8(join10(source, SKILL_FILENAME))) return false;
16461
+ if (!existsSync7(join10(source, SKILL_FILENAME))) return false;
16248
16462
  const link = join10(root, ".claude", "skills", SKILL_NAME);
16249
- if (existsSync8(join10(link, SKILL_FILENAME))) return false;
16463
+ if (existsSync7(join10(link, SKILL_FILENAME))) return false;
16250
16464
  let entry = null;
16251
16465
  try {
16252
16466
  entry = lstatSync(link);
@@ -16257,7 +16471,7 @@ function healClaudeSkillLink(root) {
16257
16471
  if (!entry.isSymbolicLink()) return false;
16258
16472
  rmSync(link, { force: true });
16259
16473
  }
16260
- mkdirSync4(join10(root, ".claude", "skills"), { recursive: true });
16474
+ mkdirSync5(join10(root, ".claude", "skills"), { recursive: true });
16261
16475
  symlinkSync(join10("..", "..", ".agents", "skills", SKILL_NAME), link, "dir");
16262
16476
  return true;
16263
16477
  } catch {
@@ -16344,10 +16558,10 @@ import * as readline2 from "readline";
16344
16558
  function prompt2(question, defaultValue) {
16345
16559
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
16346
16560
  const display = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
16347
- return new Promise((resolve7) => {
16561
+ return new Promise((resolve8) => {
16348
16562
  rl.question(display, (answer) => {
16349
16563
  rl.close();
16350
- resolve7(answer.trim() || defaultValue || "");
16564
+ resolve8(answer.trim() || defaultValue || "");
16351
16565
  });
16352
16566
  });
16353
16567
  }
@@ -16475,8 +16689,8 @@ async function tryOpenBrowser(url) {
16475
16689
  cmd = "xdg-open";
16476
16690
  args2 = [url];
16477
16691
  }
16478
- return new Promise((resolve7) => {
16479
- execFile(cmd, args2, (err) => resolve7(!err));
16692
+ return new Promise((resolve8) => {
16693
+ execFile(cmd, args2, (err) => resolve8(!err));
16480
16694
  });
16481
16695
  } catch {
16482
16696
  return false;
@@ -16520,10 +16734,33 @@ var init_logout = __esm({
16520
16734
  }
16521
16735
  });
16522
16736
 
16523
- // src/lib/worktree-binding.ts
16737
+ // src/lib/base-id.ts
16738
+ function isValidBaseId(id) {
16739
+ return id.length > 0 && id.length <= BASE_ID_MAX_LENGTH && BASE_ID_RE.test(id);
16740
+ }
16741
+ function isPathSafeId(id) {
16742
+ return PATH_SAFE_ID_RE.test(id);
16743
+ }
16744
+ var BASE_ID_MAX_LENGTH, BASE_ID_RE, BASE_ID_RULE, PATH_SAFE_ID_RE, PATH_SAFE_ID_RULE;
16745
+ var init_base_id = __esm({
16746
+ "src/lib/base-id.ts"() {
16747
+ "use strict";
16748
+ BASE_ID_MAX_LENGTH = 128;
16749
+ BASE_ID_RE = /^[A-Za-z0-9_][A-Za-z0-9_-]*$/;
16750
+ BASE_ID_RULE = `letters, digits, \`-\` and \`_\`, starting with a letter, digit or \`_\`, ${BASE_ID_MAX_LENGTH} characters or fewer`;
16751
+ PATH_SAFE_ID_RE = /^(?!\.\.?$)[A-Za-z0-9._-]{1,256}$/;
16752
+ PATH_SAFE_ID_RULE = "letters, digits, dot, dash and underscore, with no path separators, not `.` or `..` alone, 256 characters or fewer";
16753
+ }
16754
+ });
16755
+
16756
+ // src/lib/worktree-scope.ts
16524
16757
  import { execFileSync as execFileSync2 } from "child_process";
16525
- import * as fs7 from "fs";
16526
- import * as path7 from "path";
16758
+ import * as fs8 from "fs";
16759
+ import * as path8 from "path";
16760
+ import * as yaml5 from "js-yaml";
16761
+ function axisNoun(axis) {
16762
+ return AXES[axis].noun;
16763
+ }
16527
16764
  function resolveGitDir() {
16528
16765
  const cwd = process.cwd();
16529
16766
  if (_gitDirCache && _gitDirCache.cwd === cwd) return _gitDirCache.gitDir;
@@ -16534,165 +16771,234 @@ function resolveGitDir() {
16534
16771
  encoding: "utf-8",
16535
16772
  stdio: ["pipe", "pipe", "pipe"]
16536
16773
  }).trim();
16537
- gitDir = raw ? path7.resolve(cwd, raw) : null;
16774
+ gitDir = raw ? path8.resolve(cwd, raw) : null;
16538
16775
  } catch {
16539
16776
  gitDir = null;
16540
16777
  }
16541
16778
  _gitDirCache = { cwd, gitDir };
16542
16779
  return gitDir;
16543
16780
  }
16544
- function createWorktreeBinding(descriptor) {
16545
- const {
16546
- filename,
16547
- legacyFilename,
16548
- isValidId,
16549
- noun,
16550
- idLabel,
16551
- unbindCommand: unbindCommand2,
16552
- useCommand: useCommand2
16553
- } = descriptor;
16554
- function getBindingPath2() {
16555
- const gitDir = resolveGitDir();
16556
- return gitDir ? path7.join(gitDir, filename) : null;
16557
- }
16558
- function getLegacyBindingPath() {
16559
- if (!legacyFilename) return null;
16560
- const gitDir = resolveGitDir();
16561
- return gitDir ? path7.join(gitDir, legacyFilename) : null;
16562
- }
16563
- function readBinding2() {
16564
- for (const bindingPath of [getBindingPath2(), getLegacyBindingPath()]) {
16565
- if (!bindingPath) continue;
16566
- let raw;
16567
- try {
16568
- raw = fs7.readFileSync(bindingPath, "utf-8");
16569
- } catch (err) {
16570
- if (err.code === "ENOENT") continue;
16571
- throw err;
16572
- }
16573
- const trimmed = raw.trim();
16574
- return isValidId(trimmed) ? trimmed : null;
16575
- }
16576
- return null;
16781
+ function getScopePath() {
16782
+ const gitDir = resolveGitDir();
16783
+ return gitDir ? path8.join(gitDir, SCOPE_FILE) : null;
16784
+ }
16785
+ function legacyPaths() {
16786
+ const gitDir = resolveGitDir();
16787
+ if (!gitDir) return [];
16788
+ return AXIS_ORDER.flatMap((axis) => AXES[axis].legacyFiles).map((f) => path8.join(gitDir, f));
16789
+ }
16790
+ function emptyScope() {
16791
+ return { hubs: [], bases: [] };
16792
+ }
16793
+ function parseAxis(raw, axis) {
16794
+ if (!Array.isArray(raw)) return [];
16795
+ const { isValidId } = AXES[axis];
16796
+ const out = [];
16797
+ for (const entry of raw) {
16798
+ if (typeof entry !== "string") continue;
16799
+ const id = entry.trim();
16800
+ if (!isValidId(id) || out.includes(id)) continue;
16801
+ out.push(id);
16577
16802
  }
16578
- function removeLegacyBinding() {
16579
- const legacyPath = getLegacyBindingPath();
16580
- if (!legacyPath) return;
16803
+ return out;
16804
+ }
16805
+ function readLegacyAxis(axis) {
16806
+ const gitDir = resolveGitDir();
16807
+ if (!gitDir) return [];
16808
+ const { isValidId, legacyFiles } = AXES[axis];
16809
+ for (const filename of legacyFiles) {
16810
+ let raw;
16581
16811
  try {
16582
- fs7.unlinkSync(legacyPath);
16583
- } catch {
16812
+ raw = fs8.readFileSync(path8.join(gitDir, filename), "utf-8");
16813
+ } catch (err) {
16814
+ if (err.code === "ENOENT") continue;
16815
+ throw err;
16584
16816
  }
16817
+ const trimmed = raw.trim();
16818
+ return isValidId(trimmed) ? [trimmed] : [];
16585
16819
  }
16586
- function writeBinding2(id) {
16587
- if (!isValidId(id)) {
16588
- throw new Error(`Invalid ${idLabel}: ${id}`);
16820
+ return [];
16821
+ }
16822
+ function readScope() {
16823
+ const scopePath = getScopePath();
16824
+ if (!scopePath) return emptyScope();
16825
+ let raw;
16826
+ try {
16827
+ raw = fs8.readFileSync(scopePath, "utf-8");
16828
+ } catch (err) {
16829
+ if (err.code === "ENOENT") {
16830
+ return { hubs: readLegacyAxis("hubs"), bases: readLegacyAxis("bases") };
16589
16831
  }
16590
- const bindingPath = getBindingPath2();
16591
- if (!bindingPath) {
16592
- throw new Error(`Not inside a git repository \u2014 cannot write ${noun} binding.`);
16832
+ throw err;
16833
+ }
16834
+ let doc;
16835
+ try {
16836
+ doc = yaml5.load(raw);
16837
+ } catch {
16838
+ return emptyScope();
16839
+ }
16840
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return emptyScope();
16841
+ const map = doc;
16842
+ return { hubs: parseAxis(map.hubs, "hubs"), bases: parseAxis(map.bases, "bases") };
16843
+ }
16844
+ function sweepLegacyFiles() {
16845
+ for (const legacyPath of legacyPaths()) {
16846
+ try {
16847
+ fs8.unlinkSync(legacyPath);
16848
+ } catch {
16593
16849
  }
16594
- fs7.writeFileSync(bindingPath, `${id}
16595
- `, "utf-8");
16596
- removeLegacyBinding();
16597
16850
  }
16598
- function clearBinding2() {
16599
- let removed = false;
16600
- for (const bindingPath of [getBindingPath2(), getLegacyBindingPath()]) {
16601
- if (!bindingPath) continue;
16602
- try {
16603
- fs7.unlinkSync(bindingPath);
16604
- removed = true;
16605
- } catch (err) {
16606
- if (err.code !== "ENOENT") throw err;
16607
- }
16851
+ }
16852
+ function writeScope(scope) {
16853
+ for (const axis of AXIS_ORDER) {
16854
+ const { isValidId, idLabel } = AXES[axis];
16855
+ for (const id of scope[axis]) {
16856
+ if (!isValidId(id)) throw expected(`Invalid ${idLabel}: ${id}`);
16608
16857
  }
16609
- return removed;
16610
16858
  }
16611
- function assertMatchesBinding(resolvedId, label) {
16612
- const bound = readBinding2();
16613
- if (!bound) return;
16614
- if (bound === resolvedId) return;
16615
- const target = label ? `${label} (${resolvedId})` : resolvedId;
16616
- console.error(
16617
- [
16618
- `This worktree is bound to ${noun} ${bound}, but the command resolved to ${target}.`,
16619
- "",
16620
- "This usually means a prompt was routed to the wrong worktree.",
16621
- "Confirm with the user before unbinding. To override:",
16622
- ` ${unbindCommand2} # clear the binding for this worktree`,
16623
- ` ${useCommand2} ${resolvedId} # rebind this worktree to the new ${noun}`
16624
- ].join("\n")
16625
- );
16626
- process.exit(1);
16859
+ const scopePath = getScopePath();
16860
+ if (!scopePath) {
16861
+ throw expected("Not inside a git repository \u2014 cannot write the worktree scope.");
16627
16862
  }
16628
- function assertNoBindingBlocksCreation(newName) {
16629
- const bound = readBinding2();
16630
- if (!bound) return;
16631
- console.error(
16632
- [
16633
- `This worktree is bound to ${noun} ${bound}, but you're about to create a new ${noun} "${newName}" here.`,
16634
- "",
16635
- "This usually means a prompt was routed to the wrong worktree.",
16636
- "Confirm with the user before unbinding. To override:",
16637
- ` ${unbindCommand2} # clear the binding for this worktree`
16638
- ].join("\n")
16639
- );
16640
- process.exit(1);
16863
+ const body = yaml5.dump({ hubs: scope.hubs, bases: scope.bases }, { flowLevel: 1 });
16864
+ const tmpPath = `${scopePath}.tmp-${process.pid}`;
16865
+ try {
16866
+ fs8.writeFileSync(tmpPath, body, "utf-8");
16867
+ fs8.renameSync(tmpPath, scopePath);
16868
+ } catch (err) {
16869
+ try {
16870
+ fs8.unlinkSync(tmpPath);
16871
+ } catch {
16872
+ }
16873
+ throw asExpectedEnvironmentError(err);
16874
+ }
16875
+ sweepLegacyFiles();
16876
+ }
16877
+ function asExpectedEnvironmentError(err) {
16878
+ if (err instanceof Error && typeof err.code === "string") {
16879
+ return expected(err.message);
16641
16880
  }
16642
- function autoBindIfUnbound2(id) {
16881
+ return err;
16882
+ }
16883
+ function setScopeAxis(axis, ids) {
16884
+ writeScope({ ...readScope(), [axis]: ids });
16885
+ return ids;
16886
+ }
16887
+ function addToScope(axis, id) {
16888
+ const scope = readScope();
16889
+ if (scope[axis].includes(id)) return null;
16890
+ const next = [...scope[axis], id];
16891
+ writeScope({ ...scope, [axis]: next });
16892
+ return next;
16893
+ }
16894
+ function removeFromScope(axis, id) {
16895
+ const scope = readScope();
16896
+ if (!scope[axis].includes(id)) return null;
16897
+ const next = scope[axis].filter((entry) => entry !== id);
16898
+ writeScope({ ...scope, [axis]: next });
16899
+ return next;
16900
+ }
16901
+ function clearScope() {
16902
+ const scopePath = getScopePath();
16903
+ let removed = false;
16904
+ for (const target of [...scopePath ? [scopePath] : [], ...legacyPaths()]) {
16643
16905
  try {
16644
- if (readBinding2()) return false;
16645
- writeBinding2(id);
16646
- console.log(`Worktree bound to ${noun} ${id} (run \`${unbindCommand2}\` to clear).`);
16647
- return true;
16906
+ fs8.unlinkSync(target);
16907
+ removed = true;
16648
16908
  } catch (err) {
16649
- console.warn(
16650
- ` Warning: could not auto-bind worktree (${err instanceof Error ? err.message : String(err)})`
16651
- );
16652
- return false;
16909
+ if (err.code !== "ENOENT") throw err;
16653
16910
  }
16654
16911
  }
16655
- return {
16656
- getBindingPath: getBindingPath2,
16657
- readBinding: readBinding2,
16658
- writeBinding: writeBinding2,
16659
- clearBinding: clearBinding2,
16660
- assertMatchesBinding,
16661
- assertNoBindingBlocksCreation,
16662
- autoBindIfUnbound: autoBindIfUnbound2
16663
- };
16912
+ return removed;
16664
16913
  }
16665
- var _gitDirCache;
16666
- var init_worktree_binding = __esm({
16667
- "src/lib/worktree-binding.ts"() {
16668
- "use strict";
16914
+ function describeAxis(axis, ids) {
16915
+ const { noun } = AXES[axis];
16916
+ return ids.length === 1 ? `${noun} ${ids[0]}` : `${noun}s ${ids.join(", ")}`;
16917
+ }
16918
+ function describeScope(scope) {
16919
+ return AXIS_ORDER.filter((axis) => scope[axis].length > 0).map((axis) => describeAxis(axis, scope[axis])).join("; ");
16920
+ }
16921
+ function assertInScope(axis, resolvedId, label) {
16922
+ const scoped = readScope()[axis];
16923
+ if (scoped.length === 0) return;
16924
+ if (scoped.includes(resolvedId)) return;
16925
+ const { noun, selectorFor } = AXES[axis];
16926
+ const selector = selectorFor(resolvedId);
16927
+ const target = label ? `${label} (${resolvedId})` : resolvedId;
16928
+ console.error(
16929
+ [
16930
+ `This worktree is scoped to ${describeAxis(axis, scoped)}, but the command resolved to ${target}.`,
16931
+ "",
16932
+ "This usually means a prompt was routed to the wrong worktree.",
16933
+ "Confirm with the user before changing this scope. To override:",
16934
+ ` ${REPLACE_COMMAND} ${selector} # scope this worktree to that ${noun} INSTEAD`,
16935
+ ` ${ADD_COMMAND} ${selector} # scope it to that ${noun} AS WELL`,
16936
+ ` ${CLEAR_COMMAND} # clear the whole worktree scope (both hubs and bases)`
16937
+ ].join("\n")
16938
+ );
16939
+ process.exit(1);
16940
+ }
16941
+ function assertScopeAllowsCreation(axis, newName) {
16942
+ const scoped = readScope()[axis];
16943
+ if (scoped.length === 0) return;
16944
+ const { noun } = AXES[axis];
16945
+ console.error(
16946
+ [
16947
+ `This worktree is scoped to ${describeAxis(axis, scoped)}, but you're about to create a new ${noun} "${newName}" here.`,
16948
+ "",
16949
+ "This usually means a prompt was routed to the wrong worktree.",
16950
+ "Confirm with the user before clearing this scope. To override:",
16951
+ ` ${CLEAR_COMMAND} # clear the whole worktree scope (both hubs and bases)`
16952
+ ].join("\n")
16953
+ );
16954
+ process.exit(1);
16955
+ }
16956
+ function seedScopeIfEmpty(axis, id) {
16957
+ try {
16958
+ if (readScope()[axis].length > 0) return false;
16959
+ setScopeAxis(axis, [id]);
16960
+ console.log(
16961
+ `Worktree bound to ${AXES[axis].noun} ${id} (run \`${CLEAR_COMMAND}\` to clear).`
16962
+ );
16963
+ return true;
16964
+ } catch (err) {
16965
+ console.warn(
16966
+ ` Warning: could not bind worktree (${err instanceof Error ? err.message : String(err)})`
16967
+ );
16968
+ return false;
16669
16969
  }
16670
- });
16671
-
16672
- // src/lib/hub-binding.ts
16673
- var binding, getBindingPath, readBinding, writeBinding, clearBinding, autoBindIfUnbound, assertHubMatchesBinding, assertNoBindingBlocksHubCreation;
16674
- var init_hub_binding = __esm({
16675
- "src/lib/hub-binding.ts"() {
16970
+ }
16971
+ var SCOPE_FILE, LEGACY_HUB_SCOPE_FILE, LEGACY_HUB_SCOPE_FILE_PRE_RENAME, LEGACY_BASE_SCOPE_FILE, AXES, AXIS_ORDER, ADD_COMMAND, REPLACE_COMMAND, CLEAR_COMMAND, _gitDirCache;
16972
+ var init_worktree_scope = __esm({
16973
+ "src/lib/worktree-scope.ts"() {
16676
16974
  "use strict";
16975
+ init_base_id();
16976
+ init_expected();
16677
16977
  init_utils();
16678
- init_worktree_binding();
16679
- binding = createWorktreeBinding({
16680
- filename: "wayai-binding",
16681
- // Pre-rename filename, from when this was called a "lock".
16682
- legacyFilename: "wayai-lock",
16683
- isValidId: (id) => UUID_RE2.test(id),
16684
- noun: "hub",
16685
- idLabel: "hub_id (must be a UUID)",
16686
- unbindCommand: "wayai unbind",
16687
- useCommand: "wayai use"
16688
- });
16689
- getBindingPath = binding.getBindingPath;
16690
- readBinding = binding.readBinding;
16691
- writeBinding = binding.writeBinding;
16692
- clearBinding = binding.clearBinding;
16693
- autoBindIfUnbound = binding.autoBindIfUnbound;
16694
- assertHubMatchesBinding = binding.assertMatchesBinding;
16695
- assertNoBindingBlocksHubCreation = binding.assertNoBindingBlocksCreation;
16978
+ SCOPE_FILE = "wayai-scope";
16979
+ LEGACY_HUB_SCOPE_FILE = "wayai-binding";
16980
+ LEGACY_HUB_SCOPE_FILE_PRE_RENAME = "wayai-lock";
16981
+ LEGACY_BASE_SCOPE_FILE = "wayai-base-binding";
16982
+ AXES = {
16983
+ hubs: {
16984
+ noun: "hub",
16985
+ idLabel: "hub_id (must be a UUID)",
16986
+ isValidId: (id) => UUID_RE2.test(id),
16987
+ selectorFor: (id) => id,
16988
+ legacyFiles: [LEGACY_HUB_SCOPE_FILE, LEGACY_HUB_SCOPE_FILE_PRE_RENAME]
16989
+ },
16990
+ bases: {
16991
+ noun: "base",
16992
+ idLabel: `base id (${PATH_SAFE_ID_RULE})`,
16993
+ isValidId: isPathSafeId,
16994
+ selectorFor: (id) => `bases/${id}`,
16995
+ legacyFiles: [LEGACY_BASE_SCOPE_FILE]
16996
+ }
16997
+ };
16998
+ AXIS_ORDER = ["hubs", "bases"];
16999
+ ADD_COMMAND = "wayai use --add";
17000
+ REPLACE_COMMAND = "wayai use";
17001
+ CLEAR_COMMAND = "wayai unbind";
16696
17002
  }
16697
17003
  });
16698
17004
 
@@ -16702,7 +17008,6 @@ __export(status_exports, {
16702
17008
  parseArgs: () => parseArgs,
16703
17009
  statusCommand: () => statusCommand
16704
17010
  });
16705
- import * as path8 from "path";
16706
17011
  function parseArgs(args2) {
16707
17012
  return { json: args2.includes("--json") };
16708
17013
  }
@@ -16722,28 +17027,29 @@ function buildSkillState() {
16722
17027
  paths: installs.map((i) => i.path)
16723
17028
  };
16724
17029
  }
16725
- function buildWorkspaceState(repoConfig) {
16726
- if (!repoConfig) return { scoped: false, path: null, hub_count: 0 };
17030
+ function buildWorkspaceState(resolved) {
17031
+ if (!resolved) return { scoped: false, path: null, hub_count: 0 };
16727
17032
  const ws = detectWorkspace();
16728
- const gitRoot = ws?.gitRoot ?? findGitRoot();
16729
17033
  const hubCount = ws ? scanWorkspaceHubs(ws.workspaceDir).length : 0;
16730
- const yamlPath = gitRoot ? path8.join(gitRoot, ".wayai.yaml") : null;
16731
- return { scoped: true, path: yamlPath, hub_count: hubCount };
17034
+ return { scoped: true, path: resolved.path, hub_count: hubCount };
16732
17035
  }
16733
17036
  async function statusCommand(args2, pkg2) {
16734
17037
  const { json } = parseArgs(args2);
16735
17038
  const config = readConfig();
16736
- const repoConfig = readRepoConfig();
16737
- const workspace = buildWorkspaceState(repoConfig);
17039
+ const resolvedRepo = resolveRepoConfig();
17040
+ const repoConfig = resolvedRepo?.config ?? null;
17041
+ const workspace = buildWorkspaceState(resolvedRepo);
17042
+ const repoScopeBlocker = resolvedRepo ? null : readRepoScopeBlocker();
16738
17043
  const cachedCliLatest = readCachedLatest(CLI_CACHE_FILE);
16739
17044
  const cliLatest = cachedCliLatest && isNewerVersion(cachedCliLatest, pkg2.version) ? cachedCliLatest : null;
16740
17045
  const skill = buildSkillState();
16741
- let boundHubId = null;
17046
+ let scope;
16742
17047
  try {
16743
- boundHubId = readBinding();
17048
+ scope = readScope();
16744
17049
  } catch {
16745
- boundHubId = null;
17050
+ scope = emptyScope();
16746
17051
  }
17052
+ const boundHubId = scope.hubs[0] ?? null;
16747
17053
  if (!config) {
16748
17054
  const snapshot2 = {
16749
17055
  logged_in: false,
@@ -16756,6 +17062,8 @@ async function statusCommand(args2, pkg2) {
16756
17062
  orgs: [],
16757
17063
  active_org: null,
16758
17064
  workspace,
17065
+ repo_scope_blocker: repoScopeBlocker,
17066
+ worktree_scope: scope,
16759
17067
  worktree_binding: { hub_id: boundHubId },
16760
17068
  worktree_lock: { hub_id: boundHubId }
16761
17069
  };
@@ -16811,6 +17119,8 @@ async function statusCommand(args2, pkg2) {
16811
17119
  orgs,
16812
17120
  active_org: activeOrg,
16813
17121
  workspace,
17122
+ repo_scope_blocker: repoScopeBlocker,
17123
+ worktree_scope: scope,
16814
17124
  worktree_binding: { hub_id: boundHubId },
16815
17125
  worktree_lock: { hub_id: boundHubId }
16816
17126
  };
@@ -16839,11 +17149,22 @@ async function statusCommand(args2, pkg2) {
16839
17149
  console.log(`Repository scope:`);
16840
17150
  console.log(` Organization: ${orgLine}`);
16841
17151
  console.log(` Hubs: ${workspace.hub_count}`);
17152
+ } else if (repoScopeBlocker?.kind === "conflict") {
17153
+ console.log("Repository scope: UNRESOLVED \u2014 two files name different organizations:");
17154
+ console.log(` ${WORKSPACE_MANIFEST_LABEL} -> ${repoScopeBlocker.manifest_organization_id}`);
17155
+ console.log(` ${ROOT_CONFIG_FILE} -> ${repoScopeBlocker.root_organization_id}`);
17156
+ console.log(` Delete or correct ${ROOT_CONFIG_FILE}; until then every command refuses.`);
17157
+ } else if (repoScopeBlocker?.kind === "invalid") {
17158
+ console.log(`Repository scope: UNRESOLVED \u2014 ${repoScopeBlocker.path} is unusable (${repoScopeBlocker.detail}).`);
16842
17159
  } else {
16843
- console.log("No .wayai.yaml found \u2014 run `wayai init` to scope this repo to an organization.");
17160
+ console.log(
17161
+ `No organization binding found in ${WORKSPACE_MANIFEST_LABEL} \u2014 run \`wayai init\` to scope this repo to an organization.`
17162
+ );
16844
17163
  }
16845
- if (boundHubId) {
16846
- console.log(`Worktree binding: ${boundHubId} (run \`wayai unbind\` to clear)`);
17164
+ if (scope.hubs.length > 0 || scope.bases.length > 0) {
17165
+ console.log("Worktree scope: (run `wayai unbind` to clear)");
17166
+ if (scope.hubs.length > 0) console.log(` Hubs: ${scope.hubs.join(", ")}`);
17167
+ if (scope.bases.length > 0) console.log(` Bases: ${scope.bases.join(", ")}`);
16847
17168
  }
16848
17169
  if (tokenValid === true && accessToken) {
16849
17170
  await nudgeReportActions(config.api_url, accessToken);
@@ -16855,8 +17176,9 @@ var init_status = __esm({
16855
17176
  init_config();
16856
17177
  init_auth();
16857
17178
  init_repo_config();
17179
+ init_workspace_manifest();
16858
17180
  init_workspace();
16859
- init_hub_binding();
17181
+ init_worktree_scope();
16860
17182
  init_api_client();
16861
17183
  init_version_cache();
16862
17184
  init_skill_version();
@@ -16938,7 +17260,7 @@ var init_constants = __esm({
16938
17260
  });
16939
17261
 
16940
17262
  // src/lib/workspace-files.ts
16941
- import * as fs8 from "fs";
17263
+ import * as fs9 from "fs";
16942
17264
  import * as path9 from "path";
16943
17265
  function perHubAgentsMd(hubFolderName) {
16944
17266
  return [
@@ -16955,12 +17277,13 @@ function perHubAgentsMd(hubFolderName) {
16955
17277
  ].join("\n");
16956
17278
  }
16957
17279
  function isWorkspace(gitRoot) {
16958
- if (fs8.existsSync(path9.join(gitRoot, ".wayai.yaml"))) return true;
17280
+ if (fs9.existsSync(workspaceManifestPath(gitRoot))) return true;
17281
+ if (fs9.existsSync(rootConfigPath(gitRoot))) return true;
16959
17282
  return isDirectory(resolveLayout(gitRoot).hubsDir);
16960
17283
  }
16961
17284
  function writeIfAbsent(filePath, content) {
16962
- if (fs8.existsSync(filePath)) return null;
16963
- fs8.writeFileSync(filePath, content, "utf-8");
17285
+ if (fs9.existsSync(filePath)) return null;
17286
+ fs9.writeFileSync(filePath, content, "utf-8");
16964
17287
  return path9.basename(filePath);
16965
17288
  }
16966
17289
  function ensureWorkspaceFiles(gitRoot) {
@@ -16978,6 +17301,7 @@ var init_workspace_files = __esm({
16978
17301
  "use strict";
16979
17302
  init_constants();
16980
17303
  init_layout();
17304
+ init_workspace_manifest();
16981
17305
  CLAUDE_MD_SHIM = "@AGENTS.md\n";
16982
17306
  ROOT_AGENTS_MD = [
16983
17307
  "# Working with WayAI",
@@ -17015,12 +17339,12 @@ __export(init_exports, {
17015
17339
  initCommand: () => initCommand,
17016
17340
  parseArgs: () => parseArgs3
17017
17341
  });
17018
- import { mkdirSync as mkdirSync5 } from "fs";
17342
+ import { mkdirSync as mkdirSync6 } from "fs";
17019
17343
  import path10 from "path";
17020
17344
  function ensureHubsDir(gitRoot) {
17021
17345
  const hubsDir = resolveLayout(gitRoot).hubsDir;
17022
17346
  if (isDirectory(hubsDir)) return null;
17023
- mkdirSync5(hubsDir, { recursive: true });
17347
+ mkdirSync6(hubsDir, { recursive: true });
17024
17348
  return `${path10.relative(gitRoot, hubsDir)}/`;
17025
17349
  }
17026
17350
  function parseArgs3(args2) {
@@ -17155,18 +17479,18 @@ var init_init = __esm({
17155
17479
  });
17156
17480
 
17157
17481
  // src/lib/fs-safety.ts
17158
- import * as fs9 from "fs";
17482
+ import * as fs10 from "fs";
17159
17483
  import * as path11 from "path";
17160
17484
  function fileHasBytes(abs, data) {
17161
17485
  let st;
17162
17486
  try {
17163
- st = fs9.lstatSync(abs);
17487
+ st = fs10.lstatSync(abs);
17164
17488
  } catch {
17165
17489
  return false;
17166
17490
  }
17167
17491
  if (!st.isFile() || st.isSymbolicLink()) return false;
17168
17492
  try {
17169
- return fs9.readFileSync(abs).equals(data);
17493
+ return fs10.readFileSync(abs).equals(data);
17170
17494
  } catch {
17171
17495
  return false;
17172
17496
  }
@@ -17175,8 +17499,8 @@ function realpathContained(root, abs) {
17175
17499
  let realRoot;
17176
17500
  let realAbs;
17177
17501
  try {
17178
- realRoot = fs9.realpathSync(root);
17179
- realAbs = fs9.realpathSync(abs);
17502
+ realRoot = fs10.realpathSync(root);
17503
+ realAbs = fs10.realpathSync(abs);
17180
17504
  } catch {
17181
17505
  return false;
17182
17506
  }
@@ -17184,13 +17508,13 @@ function realpathContained(root, abs) {
17184
17508
  }
17185
17509
  function mkdirTolerateRace(dir) {
17186
17510
  try {
17187
- fs9.mkdirSync(dir);
17511
+ fs10.mkdirSync(dir);
17188
17512
  return true;
17189
17513
  } catch (err) {
17190
17514
  if (err.code !== "EEXIST") throw err;
17191
17515
  let st;
17192
17516
  try {
17193
- st = fs9.lstatSync(dir);
17517
+ st = fs10.lstatSync(dir);
17194
17518
  } catch {
17195
17519
  st = void 0;
17196
17520
  }
@@ -17202,7 +17526,7 @@ function ensureRealSubdirNoSymlink(root, target, create) {
17202
17526
  const targetResolved = path11.resolve(target);
17203
17527
  if (targetResolved !== rootResolved && !targetResolved.startsWith(rootResolved + path11.sep)) return false;
17204
17528
  try {
17205
- if (!fs9.lstatSync(rootResolved).isDirectory()) return false;
17529
+ if (!fs10.lstatSync(rootResolved).isDirectory()) return false;
17206
17530
  } catch {
17207
17531
  return false;
17208
17532
  }
@@ -17213,7 +17537,7 @@ function ensureRealSubdirNoSymlink(root, target, create) {
17213
17537
  cur = path11.join(cur, segment);
17214
17538
  let st;
17215
17539
  try {
17216
- st = fs9.lstatSync(cur);
17540
+ st = fs10.lstatSync(cur);
17217
17541
  } catch {
17218
17542
  st = void 0;
17219
17543
  }
@@ -17230,8 +17554,8 @@ function ensureRealSubdirNoSymlink(root, target, create) {
17230
17554
  function readFileNoFollow(root, abs) {
17231
17555
  if (!ensureRealSubdirNoSymlink(root, path11.dirname(abs), false)) return null;
17232
17556
  try {
17233
- if (!fs9.lstatSync(abs).isFile()) return null;
17234
- return fs9.readFileSync(abs);
17557
+ if (!fs10.lstatSync(abs).isFile()) return null;
17558
+ return fs10.readFileSync(abs);
17235
17559
  } catch {
17236
17560
  return null;
17237
17561
  }
@@ -17241,11 +17565,11 @@ function writeFileNoFollow(root, abs, data) {
17241
17565
  if (!ensureRealSubdirNoSymlink(root, parent, true)) return false;
17242
17566
  let st;
17243
17567
  try {
17244
- st = fs9.lstatSync(abs);
17568
+ st = fs10.lstatSync(abs);
17245
17569
  } catch {
17246
17570
  }
17247
- if (st?.isSymbolicLink()) fs9.rmSync(abs);
17248
- fs9.writeFileSync(abs, data);
17571
+ if (st?.isSymbolicLink()) fs10.rmSync(abs);
17572
+ fs10.writeFileSync(abs, data);
17249
17573
  return true;
17250
17574
  }
17251
17575
  var init_fs_safety = __esm({
@@ -17255,7 +17579,7 @@ var init_fs_safety = __esm({
17255
17579
  });
17256
17580
 
17257
17581
  // src/lib/resource-files.ts
17258
- import * as fs10 from "fs";
17582
+ import * as fs11 from "fs";
17259
17583
  import * as path12 from "path";
17260
17584
  import * as crypto3 from "crypto";
17261
17585
  function isBinaryFile(filename) {
@@ -17302,14 +17626,14 @@ function computeHash(data) {
17302
17626
  }
17303
17627
  function scanResourceFiles(dir, prefix = "") {
17304
17628
  const files = [];
17305
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
17629
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
17306
17630
  for (const entry of entries) {
17307
17631
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
17308
17632
  if (entry.isDirectory()) {
17309
17633
  files.push(...scanResourceFiles(path12.join(dir, entry.name), relPath));
17310
17634
  } else if (entry.isFile()) {
17311
17635
  const fullPath = path12.join(dir, entry.name);
17312
- const stat2 = fs10.statSync(fullPath);
17636
+ const stat2 = fs11.statSync(fullPath);
17313
17637
  if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
17314
17638
  console.warn(` Warning: skipping ${relPath} (${(stat2.size / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit)`);
17315
17639
  continue;
@@ -17319,7 +17643,7 @@ function scanResourceFiles(dir, prefix = "") {
17319
17643
  mime_type: guessMimeType(entry.name),
17320
17644
  file_size: stat2.size
17321
17645
  };
17322
- const data = fs10.readFileSync(fullPath);
17646
+ const data = fs11.readFileSync(fullPath);
17323
17647
  fileEntry.hash = computeHash(data);
17324
17648
  if (isBinaryFile(entry.name)) {
17325
17649
  fileEntry.content_base64 = data.toString("base64");
@@ -17379,7 +17703,7 @@ function writeResourceFileTree(resDir, files, root, log) {
17379
17703
  return;
17380
17704
  }
17381
17705
  if (files.length === 0) {
17382
- if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set(), root, log);
17706
+ if (fs11.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set(), root, log);
17383
17707
  return;
17384
17708
  }
17385
17709
  const writtenPaths = /* @__PURE__ */ new Set();
@@ -17400,18 +17724,18 @@ function writeResourceFileTree(resDir, files, root, log) {
17400
17724
  writtenPaths.delete(file.path);
17401
17725
  }
17402
17726
  }
17403
- if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths, root, log);
17727
+ if (fs11.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths, root, log);
17404
17728
  }
17405
17729
  function cleanOrphanFiles(dir, prefix, writtenPaths, root, log) {
17406
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
17730
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
17407
17731
  for (const entry of entries) {
17408
17732
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
17409
17733
  const fullPath = path12.join(dir, entry.name);
17410
17734
  if (entry.isDirectory()) {
17411
17735
  cleanOrphanFiles(fullPath, relPath, writtenPaths, root, log);
17412
- if (fs10.readdirSync(fullPath).length === 0) fs10.rmdirSync(fullPath);
17736
+ if (fs11.readdirSync(fullPath).length === 0) fs11.rmdirSync(fullPath);
17413
17737
  } else if (!writtenPaths.has(relPath)) {
17414
- fs10.unlinkSync(fullPath);
17738
+ fs11.unlinkSync(fullPath);
17415
17739
  if (log && root) log.removed.push(path12.relative(root, fullPath));
17416
17740
  }
17417
17741
  }
@@ -17432,8 +17756,8 @@ async function downloadBinaryFiles(resDir, files, root, log) {
17432
17756
  }
17433
17757
  if (file.hash) {
17434
17758
  try {
17435
- const st = fs10.lstatSync(filePath);
17436
- if (st.isFile() && !st.isSymbolicLink() && computeHash(fs10.readFileSync(filePath)) === file.hash) continue;
17759
+ const st = fs11.lstatSync(filePath);
17760
+ if (st.isFile() && !st.isSymbolicLink() && computeHash(fs11.readFileSync(filePath)) === file.hash) continue;
17437
17761
  } catch {
17438
17762
  }
17439
17763
  }
@@ -17487,7 +17811,7 @@ var init_resource_files = __esm({
17487
17811
  });
17488
17812
 
17489
17813
  // src/lib/eval-attachments.ts
17490
- import * as fs11 from "fs";
17814
+ import * as fs12 from "fs";
17491
17815
  import * as path13 from "path";
17492
17816
  function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
17493
17817
  const raw = turn.attachments;
@@ -17511,7 +17835,7 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
17511
17835
  }
17512
17836
  let stat2;
17513
17837
  try {
17514
- stat2 = fs11.statSync(abs);
17838
+ stat2 = fs12.statSync(abs);
17515
17839
  } catch {
17516
17840
  }
17517
17841
  if (!stat2?.isFile()) {
@@ -17525,7 +17849,7 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
17525
17849
  `${label}: attachment "${entry}" is ${(stat2.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
17526
17850
  );
17527
17851
  }
17528
- const data = fs11.readFileSync(abs);
17852
+ const data = fs12.readFileSync(abs);
17529
17853
  const hash = computeHash(data);
17530
17854
  const fileName = path13.basename(abs);
17531
17855
  const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
@@ -17633,16 +17957,16 @@ async function downloadEvalAttachments(hubFolder, downloads) {
17633
17957
  console.warn(` Warning: ${ATTACHMENTS_DIR}/ is not reachable inside the hub folder without a symlink; skipping attachment sync.`);
17634
17958
  return result;
17635
17959
  }
17636
- if (!fs11.existsSync(attachDir)) return result;
17960
+ if (!fs12.existsSync(attachDir)) return result;
17637
17961
  for (const dl of downloads) {
17638
17962
  const abs = path13.resolve(hubFolder, dl.relPath);
17639
17963
  if (abs !== attachDir && !abs.startsWith(attachDir + path13.sep)) continue;
17640
17964
  let existing;
17641
17965
  try {
17642
- existing = fs11.lstatSync(abs);
17966
+ existing = fs12.lstatSync(abs);
17643
17967
  } catch {
17644
17968
  }
17645
- if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs11.readFileSync(abs)) === dl.hash) {
17969
+ if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs12.readFileSync(abs)) === dl.hash) {
17646
17970
  continue;
17647
17971
  }
17648
17972
  try {
@@ -17660,11 +17984,11 @@ async function downloadEvalAttachments(hubFolder, downloads) {
17660
17984
  }
17661
17985
  }
17662
17986
  const keep = new Set(downloads.map((d) => path13.resolve(hubFolder, d.relPath)));
17663
- for (const name of fs11.readdirSync(attachDir)) {
17987
+ for (const name of fs12.readdirSync(attachDir)) {
17664
17988
  const abs = path13.join(attachDir, name);
17665
- const st = fs11.lstatSync(abs);
17989
+ const st = fs12.lstatSync(abs);
17666
17990
  if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) {
17667
- fs11.rmSync(abs);
17991
+ fs12.rmSync(abs);
17668
17992
  result.removed.push(path13.relative(hubFolder, abs));
17669
17993
  }
17670
17994
  }
@@ -17682,20 +18006,20 @@ var init_eval_attachments = __esm({
17682
18006
  });
17683
18007
 
17684
18008
  // src/lib/parser.ts
17685
- import * as fs12 from "fs";
18009
+ import * as fs13 from "fs";
17686
18010
  import * as path14 from "path";
17687
- import * as yaml4 from "js-yaml";
18011
+ import * as yaml6 from "js-yaml";
17688
18012
  function parseHubFolder(hubFolder, opts) {
17689
18013
  const yamlPath = resolveHubYamlPath(hubFolder);
17690
18014
  if (!yamlPath) {
17691
18015
  throw expected(`hub.yaml not found in ${hubFolder}`);
17692
18016
  }
17693
- const yamlContent = fs12.readFileSync(yamlPath, "utf-8");
18017
+ const yamlContent = fs13.readFileSync(yamlPath, "utf-8");
17694
18018
  let config;
17695
18019
  try {
17696
- config = yaml4.load(yamlContent);
18020
+ config = yaml6.load(yamlContent);
17697
18021
  } catch (err) {
17698
- if (err instanceof yaml4.YAMLException) {
18022
+ if (err instanceof yaml6.YAMLException) {
17699
18023
  throw expected(`Invalid YAML in ${yamlPath}: ${err.message}`);
17700
18024
  }
17701
18025
  throw err;
@@ -17718,8 +18042,8 @@ function parseHubFolder(hubFolder, opts) {
17718
18042
  if (typeof resolved.instructions === "string" && resolved.instructions.endsWith(".md")) {
17719
18043
  const instrValue = resolved.instructions;
17720
18044
  const instructionsPath = instrValue.startsWith("agents/") ? path14.join(hubFolder, instrValue) : path14.join(hubFolder, "agents", instrValue);
17721
- if (fs12.existsSync(instructionsPath)) {
17722
- resolved.instructions = fs12.readFileSync(instructionsPath, "utf-8");
18045
+ if (fs13.existsSync(instructionsPath)) {
18046
+ resolved.instructions = fs13.readFileSync(instructionsPath, "utf-8");
17723
18047
  } else {
17724
18048
  throw expected(
17725
18049
  `Agent instructions file not found: ${instructionsPath} (referenced by agent "${agent.name}")`
@@ -17727,8 +18051,8 @@ function parseHubFolder(hubFolder, opts) {
17727
18051
  }
17728
18052
  } else if (resolved.instructions === void 0 && typeof agent.name === "string") {
17729
18053
  const conventionPath = path14.join(hubFolder, "agents", `${slugify(agent.name)}.md`);
17730
- if (fs12.existsSync(conventionPath)) {
17731
- resolved.instructions = fs12.readFileSync(conventionPath, "utf-8");
18054
+ if (fs13.existsSync(conventionPath)) {
18055
+ resolved.instructions = fs13.readFileSync(conventionPath, "utf-8");
17732
18056
  }
17733
18057
  }
17734
18058
  return resolved;
@@ -17783,17 +18107,17 @@ function parseHubFolder(hubFolder, opts) {
17783
18107
  }
17784
18108
  function scanEvalYamlFiles(hubFolder, bytesByHash) {
17785
18109
  const evalsDir = path14.join(hubFolder, "evals");
17786
- if (!fs12.existsSync(evalsDir)) return [];
18110
+ if (!fs13.existsSync(evalsDir)) return [];
17787
18111
  const evals = [];
17788
18112
  const seen = /* @__PURE__ */ new Set();
17789
- const topEntries = fs12.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
18113
+ const topEntries = fs13.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
17790
18114
  for (const entry of topEntries) {
17791
18115
  if (entry.isFile() && entry.name.endsWith(".yaml")) {
17792
18116
  collectEval(evals, seen, path14.join(evalsDir, entry.name), `evals/${entry.name}`, null, hubFolder, bytesByHash);
17793
18117
  } else if (entry.isDirectory()) {
17794
18118
  const setName = entry.name;
17795
18119
  const setDir = path14.join(evalsDir, setName);
17796
- const setEntries = fs12.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
18120
+ const setEntries = fs13.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
17797
18121
  for (const sub of setEntries) {
17798
18122
  if (sub.isDirectory()) {
17799
18123
  throw expected(
@@ -17822,12 +18146,12 @@ function collectEval(evals, seen, filePath, relPath, setName, hubFolder, bytesBy
17822
18146
  evals.push(evalEntry);
17823
18147
  }
17824
18148
  function parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash) {
17825
- const content = fs12.readFileSync(filePath, "utf-8");
18149
+ const content = fs13.readFileSync(filePath, "utf-8");
17826
18150
  let raw;
17827
18151
  try {
17828
- raw = yaml4.load(content);
18152
+ raw = yaml6.load(content);
17829
18153
  } catch (err) {
17830
- if (err instanceof yaml4.YAMLException) {
18154
+ if (err instanceof yaml6.YAMLException) {
17831
18155
  throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
17832
18156
  }
17833
18157
  throw err;
@@ -17925,10 +18249,10 @@ function parseFixtureField(raw, label, key) {
17925
18249
  }
17926
18250
  function scanJourneyYamlFiles(hubFolder, bytesByHash) {
17927
18251
  const journeysDir = path14.join(hubFolder, "journeys");
17928
- if (!fs12.existsSync(journeysDir)) return [];
18252
+ if (!fs13.existsSync(journeysDir)) return [];
17929
18253
  const journeys = [];
17930
18254
  const seen = /* @__PURE__ */ new Set();
17931
- const entries = fs12.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
18255
+ const entries = fs13.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
17932
18256
  for (const entry of entries) {
17933
18257
  if (entry.isDirectory()) {
17934
18258
  throw expected(
@@ -17949,12 +18273,12 @@ function scanJourneyYamlFiles(hubFolder, bytesByHash) {
17949
18273
  return journeys;
17950
18274
  }
17951
18275
  function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
17952
- const content = fs12.readFileSync(filePath, "utf-8");
18276
+ const content = fs13.readFileSync(filePath, "utf-8");
17953
18277
  let raw;
17954
18278
  try {
17955
- raw = yaml4.load(content);
18279
+ raw = yaml6.load(content);
17956
18280
  } catch (err) {
17957
- if (err instanceof yaml4.YAMLException) {
18281
+ if (err instanceof yaml6.YAMLException) {
17958
18282
  throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
17959
18283
  }
17960
18284
  throw err;
@@ -18005,18 +18329,18 @@ function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
18005
18329
  }
18006
18330
  function scanAgentYamlFiles(hubFolder) {
18007
18331
  const agentsDir = path14.join(hubFolder, "agents");
18008
- if (!fs12.existsSync(agentsDir)) return [];
18009
- const yamlFiles = fs12.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
18332
+ if (!fs13.existsSync(agentsDir)) return [];
18333
+ const yamlFiles = fs13.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
18010
18334
  if (yamlFiles.length === 0) return [];
18011
18335
  const agents = [];
18012
18336
  for (const file of yamlFiles) {
18013
18337
  const filePath = path14.join(agentsDir, file);
18014
- const content = fs12.readFileSync(filePath, "utf-8");
18338
+ const content = fs13.readFileSync(filePath, "utf-8");
18015
18339
  let agent;
18016
18340
  try {
18017
- agent = yaml4.load(content);
18341
+ agent = yaml6.load(content);
18018
18342
  } catch (err) {
18019
- if (err instanceof yaml4.YAMLException) {
18343
+ if (err instanceof yaml6.YAMLException) {
18020
18344
  throw expected(`Invalid YAML in agents/${file}: ${err.message}`);
18021
18345
  }
18022
18346
  throw err;
@@ -18046,7 +18370,7 @@ function parseResources(hubFolder, configResources) {
18046
18370
  if (res.skill_name) resource.skill_name = res.skill_name;
18047
18371
  const resSlug = slugify(resource.name);
18048
18372
  const resDir = path14.join(resourcesDir, resSlug);
18049
- if (fs12.existsSync(resDir)) {
18373
+ if (fs13.existsSync(resDir)) {
18050
18374
  const files = scanResourceFiles(resDir, "");
18051
18375
  if (files.length > 0) {
18052
18376
  resource.files = files;
@@ -18094,9 +18418,9 @@ var init_diff_display = __esm({
18094
18418
  });
18095
18419
 
18096
18420
  // src/lib/yaml-writer.ts
18097
- import * as fs13 from "fs";
18421
+ import * as fs14 from "fs";
18098
18422
  import * as path15 from "path";
18099
- import * as yaml5 from "js-yaml";
18423
+ import * as yaml7 from "js-yaml";
18100
18424
  function writeFileIfChanged(hubFolder, absPath, content, log) {
18101
18425
  const buf = Buffer.from(content, "utf-8");
18102
18426
  if (fileHasBytes(absPath, buf)) return;
@@ -18145,15 +18469,15 @@ function buildEvalYamlObject(evalEntry, slug) {
18145
18469
  function writeHubFolder(hubFolder, payload, options = {}) {
18146
18470
  const log = { changed: [], removed: [] };
18147
18471
  const agentsDir = path15.join(hubFolder, "agents");
18148
- if (!fs13.existsSync(hubFolder)) {
18149
- fs13.mkdirSync(hubFolder, { recursive: true });
18472
+ if (!fs14.existsSync(hubFolder)) {
18473
+ fs14.mkdirSync(hubFolder, { recursive: true });
18150
18474
  }
18151
- if (!fs13.existsSync(agentsDir)) {
18152
- fs13.mkdirSync(agentsDir, { recursive: true });
18475
+ if (!fs14.existsSync(agentsDir)) {
18476
+ fs14.mkdirSync(agentsDir, { recursive: true });
18153
18477
  }
18154
18478
  const yamlPayload = buildYamlPayload(payload);
18155
18479
  const agentFiles = extractAgentFiles(payload);
18156
- const yamlContent = yaml5.dump(yamlPayload, YAML_DUMP_OPTIONS);
18480
+ const yamlContent = yaml7.dump(yamlPayload, YAML_DUMP_OPTIONS);
18157
18481
  writeFileIfChanged(hubFolder, path15.join(hubFolder, "hub.yaml"), yamlContent, log);
18158
18482
  if (options.seedAgentContext ?? true) {
18159
18483
  for (const seeded of [
@@ -18164,8 +18488,8 @@ function writeHubFolder(hubFolder, payload, options = {}) {
18164
18488
  }
18165
18489
  }
18166
18490
  const oldYamlPath = path15.join(hubFolder, "wayai.yaml");
18167
- if (fs13.existsSync(oldYamlPath)) {
18168
- fs13.unlinkSync(oldYamlPath);
18491
+ if (fs14.existsSync(oldYamlPath)) {
18492
+ fs14.unlinkSync(oldYamlPath);
18169
18493
  log.removed.push(path15.relative(hubFolder, oldYamlPath));
18170
18494
  }
18171
18495
  const yamlSlugs = writeAgentYamlFiles(hubFolder, agentsDir, payload.agents || [], log);
@@ -18174,20 +18498,20 @@ function writeHubFolder(hubFolder, payload, options = {}) {
18174
18498
  mdSlugs.add(slug);
18175
18499
  writeFileIfChanged(hubFolder, path15.join(agentsDir, `${slug}.md`), content, log);
18176
18500
  }
18177
- const existingFiles = fs13.readdirSync(agentsDir);
18501
+ const existingFiles = fs14.readdirSync(agentsDir);
18178
18502
  for (const file of existingFiles) {
18179
18503
  if (file.endsWith(".yaml")) {
18180
18504
  const slug = file.slice(0, -5);
18181
18505
  if (!yamlSlugs.has(slug)) {
18182
18506
  const orphan = path15.join(agentsDir, file);
18183
- fs13.unlinkSync(orphan);
18507
+ fs14.unlinkSync(orphan);
18184
18508
  log.removed.push(path15.relative(hubFolder, orphan));
18185
18509
  }
18186
18510
  } else if (file.endsWith(".md")) {
18187
18511
  const slug = file.slice(0, -3);
18188
18512
  if (!mdSlugs.has(slug)) {
18189
18513
  const orphan = path15.join(agentsDir, file);
18190
- fs13.unlinkSync(orphan);
18514
+ fs14.unlinkSync(orphan);
18191
18515
  log.removed.push(path15.relative(hubFolder, orphan));
18192
18516
  }
18193
18517
  }
@@ -18200,10 +18524,10 @@ function writeHubFolder(hubFolder, payload, options = {}) {
18200
18524
  function setPreviewLabelInHubYaml(hubFolder, label) {
18201
18525
  const yamlPath = resolveHubYamlPath(hubFolder);
18202
18526
  if (!yamlPath) return;
18203
- const obj = yaml5.load(fs13.readFileSync(yamlPath, "utf-8")) ?? {};
18527
+ const obj = yaml7.load(fs14.readFileSync(yamlPath, "utf-8")) ?? {};
18204
18528
  if (label) obj.preview_label = label;
18205
18529
  else delete obj.preview_label;
18206
- fs13.writeFileSync(yamlPath, yaml5.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
18530
+ fs14.writeFileSync(yamlPath, yaml7.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
18207
18531
  }
18208
18532
  function buildYamlPayload(payload) {
18209
18533
  const result = {
@@ -18248,7 +18572,7 @@ function writeAgentYamlFiles(hubFolder, agentsDir, agents, log) {
18248
18572
  slugs.add(slug);
18249
18573
  const entry = { ...agent };
18250
18574
  delete entry.instructions;
18251
- const yamlContent = yaml5.dump(entry, YAML_DUMP_OPTIONS);
18575
+ const yamlContent = yaml7.dump(entry, YAML_DUMP_OPTIONS);
18252
18576
  writeFileIfChanged(hubFolder, path15.join(agentsDir, `${slug}.yaml`), yamlContent, log);
18253
18577
  }
18254
18578
  return slugs;
@@ -18268,17 +18592,17 @@ function extractAgentFiles(payload) {
18268
18592
  function writeEvalYamlFiles(hubFolder, evals, log) {
18269
18593
  const evalsDir = path15.join(hubFolder, "evals");
18270
18594
  if (evals.length === 0) {
18271
- if (fs13.existsSync(evalsDir)) {
18595
+ if (fs14.existsSync(evalsDir)) {
18272
18596
  cleanEvalOrphans(hubFolder, evalsDir, /* @__PURE__ */ new Set(), log);
18273
18597
  try {
18274
- if (fs13.readdirSync(evalsDir).length === 0) fs13.rmdirSync(evalsDir);
18598
+ if (fs14.readdirSync(evalsDir).length === 0) fs14.rmdirSync(evalsDir);
18275
18599
  } catch {
18276
18600
  }
18277
18601
  }
18278
18602
  return;
18279
18603
  }
18280
- if (!fs13.existsSync(evalsDir)) {
18281
- fs13.mkdirSync(evalsDir, { recursive: true });
18604
+ if (!fs14.existsSync(evalsDir)) {
18605
+ fs14.mkdirSync(evalsDir, { recursive: true });
18282
18606
  }
18283
18607
  const writtenRelPaths = /* @__PURE__ */ new Set();
18284
18608
  for (const evalEntry of evals) {
@@ -18292,42 +18616,42 @@ function writeEvalYamlFiles(hubFolder, evals, log) {
18292
18616
  }
18293
18617
  if (setName) {
18294
18618
  const setDir = path15.join(evalsDir, setName);
18295
- if (!fs13.existsSync(setDir)) {
18296
- fs13.mkdirSync(setDir, { recursive: true });
18619
+ if (!fs14.existsSync(setDir)) {
18620
+ fs14.mkdirSync(setDir, { recursive: true });
18297
18621
  }
18298
18622
  }
18299
- const yamlContent = yaml5.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
18623
+ const yamlContent = yaml7.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
18300
18624
  writeFileIfChanged(hubFolder, targetPath, yamlContent, log);
18301
18625
  writtenRelPaths.add(relPath);
18302
18626
  }
18303
18627
  cleanEvalOrphans(hubFolder, evalsDir, writtenRelPaths, log);
18304
18628
  }
18305
18629
  function cleanEvalOrphans(hubFolder, evalsDir, writtenRelPaths, log) {
18306
- const entries = fs13.readdirSync(evalsDir, { withFileTypes: true });
18630
+ const entries = fs14.readdirSync(evalsDir, { withFileTypes: true });
18307
18631
  for (const entry of entries) {
18308
18632
  const fullPath = path15.join(evalsDir, entry.name);
18309
18633
  if (entry.isFile()) {
18310
18634
  if (entry.name.endsWith(".yaml") && !writtenRelPaths.has(entry.name)) {
18311
- fs13.unlinkSync(fullPath);
18635
+ fs14.unlinkSync(fullPath);
18312
18636
  log.removed.push(path15.relative(hubFolder, fullPath));
18313
18637
  }
18314
18638
  continue;
18315
18639
  }
18316
18640
  if (entry.isDirectory()) {
18317
18641
  const setName = entry.name;
18318
- const subEntries = fs13.readdirSync(fullPath, { withFileTypes: true });
18642
+ const subEntries = fs14.readdirSync(fullPath, { withFileTypes: true });
18319
18643
  for (const sub of subEntries) {
18320
18644
  if (sub.isFile() && sub.name.endsWith(".yaml")) {
18321
18645
  const relPath = `${setName}/${sub.name}`;
18322
18646
  if (!writtenRelPaths.has(relPath)) {
18323
18647
  const orphan = path15.join(fullPath, sub.name);
18324
- fs13.unlinkSync(orphan);
18648
+ fs14.unlinkSync(orphan);
18325
18649
  log.removed.push(path15.relative(hubFolder, orphan));
18326
18650
  }
18327
18651
  }
18328
18652
  }
18329
18653
  try {
18330
- if (fs13.readdirSync(fullPath).length === 0) fs13.rmdirSync(fullPath);
18654
+ if (fs14.readdirSync(fullPath).length === 0) fs14.rmdirSync(fullPath);
18331
18655
  } catch {
18332
18656
  }
18333
18657
  }
@@ -18366,35 +18690,35 @@ function buildJourneyYamlObject(journeyEntry, slug) {
18366
18690
  function writeJourneyYamlFiles(hubFolder, journeys, log) {
18367
18691
  const journeysDir = path15.join(hubFolder, "journeys");
18368
18692
  if (journeys.length === 0) {
18369
- if (fs13.existsSync(journeysDir)) {
18693
+ if (fs14.existsSync(journeysDir)) {
18370
18694
  cleanJourneyOrphans(hubFolder, journeysDir, /* @__PURE__ */ new Set(), log);
18371
18695
  try {
18372
- if (fs13.readdirSync(journeysDir).length === 0) fs13.rmdirSync(journeysDir);
18696
+ if (fs14.readdirSync(journeysDir).length === 0) fs14.rmdirSync(journeysDir);
18373
18697
  } catch {
18374
18698
  }
18375
18699
  }
18376
18700
  return;
18377
18701
  }
18378
- if (!fs13.existsSync(journeysDir)) {
18379
- fs13.mkdirSync(journeysDir, { recursive: true });
18702
+ if (!fs14.existsSync(journeysDir)) {
18703
+ fs14.mkdirSync(journeysDir, { recursive: true });
18380
18704
  }
18381
18705
  const writtenFiles = /* @__PURE__ */ new Set();
18382
18706
  const usedSlugs = /* @__PURE__ */ new Set();
18383
18707
  for (const journeyEntry of journeys) {
18384
18708
  const slug = ensureUniqueSlug(slugify(journeyEntry.name), usedSlugs);
18385
18709
  const fileName = `${slug}.yaml`;
18386
- const yamlContent = yaml5.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
18710
+ const yamlContent = yaml7.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
18387
18711
  writeFileIfChanged(hubFolder, path15.join(journeysDir, fileName), yamlContent, log);
18388
18712
  writtenFiles.add(fileName);
18389
18713
  }
18390
18714
  cleanJourneyOrphans(hubFolder, journeysDir, writtenFiles, log);
18391
18715
  }
18392
18716
  function cleanJourneyOrphans(hubFolder, journeysDir, writtenFiles, log) {
18393
- const entries = fs13.readdirSync(journeysDir, { withFileTypes: true });
18717
+ const entries = fs14.readdirSync(journeysDir, { withFileTypes: true });
18394
18718
  for (const entry of entries) {
18395
18719
  if (entry.isFile() && entry.name.endsWith(".yaml") && !writtenFiles.has(entry.name)) {
18396
18720
  const orphan = path15.join(journeysDir, entry.name);
18397
- fs13.unlinkSync(orphan);
18721
+ fs14.unlinkSync(orphan);
18398
18722
  log.removed.push(path15.relative(hubFolder, orphan));
18399
18723
  }
18400
18724
  }
@@ -18408,12 +18732,12 @@ function writeResourceFiles(hubFolder, resources, log) {
18408
18732
  const resDir = path15.join(resourcesDir, resSlug);
18409
18733
  writeResourceFileTree(resDir, resource.files || [], hubFolder, log);
18410
18734
  }
18411
- if (fs13.existsSync(resourcesDir)) {
18412
- const existingDirs = fs13.readdirSync(resourcesDir, { withFileTypes: true });
18735
+ if (fs14.existsSync(resourcesDir)) {
18736
+ const existingDirs = fs14.readdirSync(resourcesDir, { withFileTypes: true });
18413
18737
  for (const entry of existingDirs) {
18414
18738
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
18415
18739
  const orphanDir = path15.join(resourcesDir, entry.name);
18416
- fs13.rmSync(orphanDir, { recursive: true, force: true });
18740
+ fs14.rmSync(orphanDir, { recursive: true, force: true });
18417
18741
  log.removed.push(`${path15.relative(hubFolder, orphanDir)}/`);
18418
18742
  }
18419
18743
  }
@@ -18501,35 +18825,16 @@ var init_terminal_output = __esm({
18501
18825
  }
18502
18826
  });
18503
18827
 
18504
- // src/lib/base-id.ts
18505
- function isValidBaseId(id) {
18506
- return id.length > 0 && id.length <= BASE_ID_MAX_LENGTH && BASE_ID_RE.test(id);
18507
- }
18508
- function isPathSafeId(id) {
18509
- return PATH_SAFE_ID_RE.test(id);
18510
- }
18511
- var BASE_ID_MAX_LENGTH, BASE_ID_RE, BASE_ID_RULE, PATH_SAFE_ID_RE, PATH_SAFE_ID_RULE;
18512
- var init_base_id = __esm({
18513
- "src/lib/base-id.ts"() {
18514
- "use strict";
18515
- BASE_ID_MAX_LENGTH = 128;
18516
- BASE_ID_RE = /^[A-Za-z0-9_][A-Za-z0-9_-]*$/;
18517
- BASE_ID_RULE = `letters, digits, \`-\` and \`_\`, starting with a letter, digit or \`_\`, ${BASE_ID_MAX_LENGTH} characters or fewer`;
18518
- PATH_SAFE_ID_RE = /^(?!\.\.?$)[A-Za-z0-9._-]{1,256}$/;
18519
- PATH_SAFE_ID_RULE = "letters, digits, dot, dash and underscore, with no path separators, not `.` or `..` alone, 256 characters or fewer";
18520
- }
18521
- });
18522
-
18523
18828
  // src/lib/base-workspace.ts
18524
- import * as fs14 from "fs";
18829
+ import * as fs15 from "fs";
18525
18830
  import * as path17 from "path";
18526
- import * as yaml6 from "js-yaml";
18831
+ import * as yaml8 from "js-yaml";
18527
18832
  function readBaseMeta(folder) {
18528
18833
  const bytes = readFileNoFollow(folder, path17.join(folder, BASE_META_FILE));
18529
18834
  if (bytes === null) return null;
18530
18835
  let doc;
18531
18836
  try {
18532
- doc = yaml6.load(bytes.toString("utf-8"));
18837
+ doc = yaml8.load(bytes.toString("utf-8"));
18533
18838
  } catch {
18534
18839
  return null;
18535
18840
  }
@@ -18539,7 +18844,7 @@ function readBaseMeta(folder) {
18539
18844
  function listBaseFolders(basesDir) {
18540
18845
  let entries;
18541
18846
  try {
18542
- entries = fs14.readdirSync(basesDir).filter((entry) => !entry.startsWith("."));
18847
+ entries = fs15.readdirSync(basesDir).filter((entry) => !entry.startsWith("."));
18543
18848
  } catch {
18544
18849
  return [];
18545
18850
  }
@@ -18618,9 +18923,15 @@ function targetBaseId(target, selector) {
18618
18923
  if (selector && !selector.includes("/") && !selector.includes(path17.sep)) return selector;
18619
18924
  return target.exists ? null : path17.basename(target.folder);
18620
18925
  }
18926
+ function resolveBaseSelectorToId(gitRoot, selector) {
18927
+ if (!gitRoot || selector.includes("/") || selector.includes(path17.sep)) return selector;
18928
+ if (!isPathSafeId(selector)) return selector;
18929
+ const id = readBaseMeta(path17.join(resolveBasesDir(gitRoot), selector))?.base_id;
18930
+ return typeof id === "string" && isPathSafeId(id) ? id : selector;
18931
+ }
18621
18932
  function hasBaseMetaFile(folder) {
18622
18933
  try {
18623
- return fs14.lstatSync(path17.join(folder, BASE_META_FILE)).isFile();
18934
+ return fs15.lstatSync(path17.join(folder, BASE_META_FILE)).isFile();
18624
18935
  } catch {
18625
18936
  return false;
18626
18937
  }
@@ -18638,25 +18949,17 @@ var init_base_workspace = __esm({
18638
18949
  });
18639
18950
 
18640
18951
  // src/lib/subtree-routing.ts
18641
- import * as fs15 from "fs";
18642
18952
  import * as path18 from "path";
18643
- import * as yaml7 from "js-yaml";
18644
18953
  function readRepoDefaults(gitRoot) {
18645
- const file = path18.join(gitRoot, WAYAI_LAYOUT.wsDir, REPO_DEFAULTS_FILE);
18646
- let doc;
18647
- try {
18648
- doc = yaml7.load(fs15.readFileSync(file, "utf-8"));
18649
- } catch {
18650
- return {};
18651
- }
18652
- if (!doc || typeof doc !== "object") return {};
18653
- const raw = doc;
18954
+ const load11 = loadWorkspaceManifest(gitRoot);
18955
+ if (load11.kind !== "ok") return {};
18956
+ const raw = load11.doc;
18654
18957
  const str = (v) => {
18655
18958
  if (typeof v !== "string" || !v.trim()) return void 0;
18656
18959
  const value = v.trim();
18657
18960
  if (value.includes("/") || value.includes(path18.sep) || value === "." || value === "..") {
18658
18961
  console.warn(
18659
- `Warning: ignoring ${JSON.stringify(value)} in ${WAYAI_LAYOUT.wsDir}/${REPO_DEFAULTS_FILE} \u2014 a default names a hub or base, not a path.`
18962
+ `Warning: ignoring ${JSON.stringify(value)} in ${WORKSPACE_MANIFEST_LABEL} \u2014 a default names a hub or base, not a path.`
18660
18963
  );
18661
18964
  return void 0;
18662
18965
  }
@@ -18690,7 +18993,7 @@ function firstSegment(p) {
18690
18993
  const [head] = normalized.split("/").filter(Boolean);
18691
18994
  return head ?? p;
18692
18995
  }
18693
- function selectorSubtree(layout, selector, cwd) {
18996
+ function resolveSelectorSubtree(layout, selector, cwd) {
18694
18997
  const parts = selector.split(path18.sep).join("/").split("/").filter(Boolean);
18695
18998
  const qualified = parts[0] === WAYAI_LAYOUT.wsDir ? parts.slice(1) : parts;
18696
18999
  if (qualified.length > 1) {
@@ -18744,11 +19047,11 @@ function routeSubtree(options) {
18744
19047
  return {
18745
19048
  ok: false,
18746
19049
  refusal: [
18747
- `\`wayai ${verb}\` does not take --org: it operates on the organization named by .wayai.yaml at the repo root.`,
19050
+ `\`wayai ${verb}\` does not take --org: it operates on the organization named by ${WORKSPACE_MANIFEST_LABEL}.`,
18748
19051
  "",
18749
19052
  "To act on another organization:",
18750
19053
  " wayai bases <command> --org <uuid> # the Data surface honors it",
18751
- " # or run from a checkout whose .wayai.yaml names that organization"
19054
+ " # or run from a checkout whose workspace manifest names that organization"
18752
19055
  ].join("\n")
18753
19056
  };
18754
19057
  }
@@ -18756,7 +19059,7 @@ function routeSubtree(options) {
18756
19059
  if (hubFlag !== void 0 && baseFlag !== void 0) {
18757
19060
  return { ok: false, refusal: mixedInvocationRefusal(verb, hubFlag, baseFlag) };
18758
19061
  }
18759
- const fromPositional = positional !== void 0 ? selectorSubtree(layout, positional, cwd) : void 0;
19062
+ const fromPositional = positional !== void 0 ? resolveSelectorSubtree(layout, positional, cwd) : void 0;
18760
19063
  if (fromPositional?.kind === "both") {
18761
19064
  return {
18762
19065
  ok: false,
@@ -18815,15 +19118,15 @@ function requireSubtree(verb, args2, gitRoot) {
18815
19118
  }
18816
19119
  return { subtree: result.subtree, selector: result.selector };
18817
19120
  }
18818
- var REPO_DEFAULTS_FILE, VALUE_FLAGS;
19121
+ var VALUE_FLAGS;
18819
19122
  var init_subtree_routing = __esm({
18820
19123
  "src/lib/subtree-routing.ts"() {
18821
19124
  "use strict";
18822
19125
  init_layout();
18823
19126
  init_terminal_output();
19127
+ init_workspace_manifest();
18824
19128
  init_workspace();
18825
19129
  init_base_workspace();
18826
- REPO_DEFAULTS_FILE = "wayai.yaml";
18827
19130
  VALUE_FLAGS = /* @__PURE__ */ new Set(["--hub", "--base", "--label", "--org"]);
18828
19131
  }
18829
19132
  });
@@ -18850,23 +19153,23 @@ async function createDataClient(orgId) {
18850
19153
  if (selected !== void 0 && !UUID_RE2.test(selected)) {
18851
19154
  throw expected(`Invalid --org: ${JSON.stringify(selected)}. Expected an organization UUID.`);
18852
19155
  }
18853
- const org = selected ?? readRepoConfig()?.organization_id;
19156
+ const org = selected ?? readRepoConfigUnlessBlocked()?.organization_id;
18854
19157
  async function requireOrgForSetupRoute(what) {
18855
19158
  const orgId2 = org ?? (await api.me()).org_id;
18856
19159
  if (!orgId2) {
18857
19160
  throw expected(
18858
- `No organization to ${what} against. Pass --org <uuid>, or run this from a repo with .wayai.yaml.`
19161
+ `No organization to ${what} against. Pass --org <uuid>, or run this from a repo scoped by \`wayai init\`.`
18859
19162
  );
18860
19163
  }
18861
19164
  return orgId2;
18862
19165
  }
18863
19166
  return {
18864
- async request(method, path36, body) {
18865
- const envelope = await api.dataRequest(method, path36, body, org);
19167
+ async request(method, path35, body) {
19168
+ const envelope = await api.dataRequest(method, path35, body, org);
18866
19169
  return envelope.data;
18867
19170
  },
18868
- async requestRaw(method, path36, body) {
18869
- const body_ = await api.dataRequest(method, path36, body, org);
19171
+ async requestRaw(method, path35, body) {
19172
+ const body_ = await api.dataRequest(method, path35, body, org);
18870
19173
  return body_;
18871
19174
  },
18872
19175
  async collectPages(makePath) {
@@ -18940,40 +19243,8 @@ var init_client = __esm({
18940
19243
  }
18941
19244
  });
18942
19245
 
18943
- // src/lib/base-binding.ts
18944
- var binding2, getBaseBindingPath, readBaseBinding, writeBaseBinding, clearBaseBinding, autoBindBaseIfUnbound, assertBaseMatchesBinding, assertNoBindingBlocksBaseCreation;
18945
- var init_base_binding = __esm({
18946
- "src/lib/base-binding.ts"() {
18947
- "use strict";
18948
- init_base_id();
18949
- init_worktree_binding();
18950
- binding2 = createWorktreeBinding({
18951
- filename: "wayai-base-binding",
18952
- // `isPathSafeId`, not the API's base-id rule. The tripwire's duty is that a
18953
- // garbled or multi-line file read as unbound, which this grammar delivers;
18954
- // applying the mint rule instead would refuse to bind a base whose stored id
18955
- // predates it — a base the API still serves — and so lock the CLI out of the
18956
- // one workflow that could migrate off it.
18957
- isValidId: isPathSafeId,
18958
- noun: "base",
18959
- // States what is actually checked. "must be a slug" described no rule the
18960
- // reader could act on, and naming the API's rule here would name the wrong one.
18961
- idLabel: `base id (${PATH_SAFE_ID_RULE})`,
18962
- unbindCommand: "wayai bases unbind",
18963
- useCommand: "wayai bases use"
18964
- });
18965
- getBaseBindingPath = binding2.getBindingPath;
18966
- readBaseBinding = binding2.readBinding;
18967
- writeBaseBinding = binding2.writeBinding;
18968
- clearBaseBinding = binding2.clearBinding;
18969
- autoBindBaseIfUnbound = binding2.autoBindIfUnbound;
18970
- assertBaseMatchesBinding = binding2.assertMatchesBinding;
18971
- assertNoBindingBlocksBaseCreation = binding2.assertNoBindingBlocksCreation;
18972
- }
18973
- });
18974
-
18975
19246
  // src/data/helpers.ts
18976
- import { readFileSync as readFileSync16 } from "fs";
19247
+ import { readFileSync as readFileSync15 } from "fs";
18977
19248
  function pathSegment(id, label = "id") {
18978
19249
  if (!isPathSafeId(id)) {
18979
19250
  throw expected(`Invalid ${label}: ${JSON.stringify(id)}. An id may use ${PATH_SAFE_ID_RULE}.`);
@@ -19001,7 +19272,7 @@ function parseData(data, flag) {
19001
19272
  let text = data;
19002
19273
  if (source !== void 0) {
19003
19274
  try {
19004
- text = readFileSync16(source, "utf-8");
19275
+ text = readFileSync15(source, "utf-8");
19005
19276
  } catch (e) {
19006
19277
  throw expected(`${prefix}${source}: ${e instanceof Error ? e.message : "could not be read"}`);
19007
19278
  }
@@ -19128,9 +19399,9 @@ var init_types2 = __esm({
19128
19399
  // src/data/config-as-code/config-writer.ts
19129
19400
  import * as fs16 from "fs";
19130
19401
  import * as path19 from "path";
19131
- import * as yaml8 from "js-yaml";
19132
- function dump4(value) {
19133
- return yaml8.dump(value, YAML_DUMP_OPTIONS);
19402
+ import * as yaml9 from "js-yaml";
19403
+ function dump5(value) {
19404
+ return yaml9.dump(value, YAML_DUMP_OPTIONS);
19134
19405
  }
19135
19406
  function omitUndefined(obj) {
19136
19407
  const out = {};
@@ -19188,7 +19459,7 @@ function writeBaseFolder(folder, meta, config) {
19188
19459
  `Refusing to write ${folder}: the path crosses a symlink. Remove it and pull again.`
19189
19460
  );
19190
19461
  }
19191
- writeFileIfChanged(folder, path19.join(folder, BASE_META_FILE), dump4(metaFileObject(meta)), delta);
19462
+ writeFileIfChanged(folder, path19.join(folder, BASE_META_FILE), dump5(metaFileObject(meta)), delta);
19192
19463
  for (const kind of ENTITY_KINDS) {
19193
19464
  const dir = path19.join(folder, ENTITY_DIRS[kind]);
19194
19465
  const entities = config[kind] ?? [];
@@ -19202,7 +19473,7 @@ function writeBaseFolder(folder, meta, config) {
19202
19473
  }
19203
19474
  const file = `${id}.yaml`;
19204
19475
  keep.add(file);
19205
- writeFileIfChanged(folder, path19.join(dir, file), dump4(toFileObject(kind, entity)), delta);
19476
+ writeFileIfChanged(folder, path19.join(dir, file), dump5(toFileObject(kind, entity)), delta);
19206
19477
  }
19207
19478
  pruneOrphans(folder, dir, keep, delta);
19208
19479
  }
@@ -19363,7 +19634,7 @@ var init_api = __esm({
19363
19634
  // src/data/config-as-code/config-parser.ts
19364
19635
  import * as fs17 from "fs";
19365
19636
  import * as path20 from "path";
19366
- import * as yaml9 from "js-yaml";
19637
+ import * as yaml10 from "js-yaml";
19367
19638
  function readEntityDir(folder, dir) {
19368
19639
  if (!ensureRealSubdirNoSymlink(folder, dir, false)) {
19369
19640
  throw expected(`Refusing to read ${dir}: it is a symlink, not a config directory.`);
@@ -19379,7 +19650,7 @@ function readEntityDir(folder, dir) {
19379
19650
  }
19380
19651
  let parsed;
19381
19652
  try {
19382
- parsed = yaml9.load(bytes.toString("utf-8"));
19653
+ parsed = yaml10.load(bytes.toString("utf-8"));
19383
19654
  } catch (err) {
19384
19655
  throw expected(`Failed to parse ${abs}: ${err instanceof Error ? err.message : String(err)}`);
19385
19656
  }
@@ -19561,7 +19832,7 @@ async function pullBase(gitRootOrNull, selector, args2) {
19561
19832
  console.log(`Production base mirrored (read-only) \u2192 ${folder}`);
19562
19833
  return;
19563
19834
  }
19564
- assertBaseMatchesBinding(baseId);
19835
+ assertInScope("bases", baseId);
19565
19836
  if (target.exists && !autoConfirm) {
19566
19837
  const ok = await confirm(
19567
19838
  `Overwrite local files in ${path21.relative(gitRoot, target.folder)} with the server config for "${sanitizeTerminalText(baseId)}"?`
@@ -19580,7 +19851,7 @@ async function pullBase(gitRootOrNull, selector, args2) {
19580
19851
  );
19581
19852
  if (!isFirstPull) printLocalFileChanges(delta);
19582
19853
  console.log(" Credential values are not written. Manage them with `wayai bases credentials`.");
19583
- autoBindBaseIfUnbound(baseId);
19854
+ seedScopeIfEmpty("bases", baseId);
19584
19855
  if (record.origin_base_id) {
19585
19856
  await mirrorLinkedProduction(client, basesDir, record.origin_base_id);
19586
19857
  }
@@ -19605,7 +19876,7 @@ async function createDeclaredPreview(client, target, parsed, autoConfirm) {
19605
19876
  );
19606
19877
  }
19607
19878
  const name = meta.name ?? path21.basename(target.folder);
19608
- assertNoBindingBlocksBaseCreation(name);
19879
+ assertScopeAllowsCreation("bases", name);
19609
19880
  if (!autoConfirm) {
19610
19881
  const ok = await confirm(
19611
19882
  `Create a new preview "${sanitizeTerminalText(name)}" from "${sanitizeTerminalText(meta.origin_base_id)}"?`
@@ -19624,7 +19895,7 @@ async function createDeclaredPreview(client, target, parsed, autoConfirm) {
19624
19895
  writeBaseFolder(target.folder, { ...meta, ...metaFromRecord(record, id) }, config);
19625
19896
  console.log(`Created preview "${sanitizeTerminalText(id)}".`);
19626
19897
  printWarnings((record.warnings ?? []).map((w) => sanitizeTerminalText(w.message)));
19627
- autoBindBaseIfUnbound(id);
19898
+ seedScopeIfEmpty("bases", id);
19628
19899
  return id;
19629
19900
  }
19630
19901
  async function pushBase(gitRootOrNull, selector, args2) {
@@ -19662,13 +19933,13 @@ async function pushBase(gitRootOrNull, selector, args2) {
19662
19933
  `"${sanitizeTerminalText(baseId)}" is a ${sanitizeTerminalText(record.environment)} base. Push operates on previews \u2014 promote with \`wayai bases promote\`.`
19663
19934
  );
19664
19935
  }
19665
- assertBaseMatchesBinding(baseId);
19936
+ assertInScope("bases", baseId);
19666
19937
  }
19667
19938
  const diff = await diffConfig(client, baseId, config);
19668
19939
  printLintWarnings(diff.warnings);
19669
19940
  if (isDiffEmpty(diff)) {
19670
19941
  console.log(`No changes \u2014 preview "${sanitizeTerminalText(baseId)}" already matches your files.`);
19671
- if (!dryRun) autoBindBaseIfUnbound(baseId);
19942
+ if (!dryRun) seedScopeIfEmpty("bases", baseId);
19672
19943
  return;
19673
19944
  }
19674
19945
  console.log(renderDiff(diff));
@@ -19720,7 +19991,7 @@ Delete ${removals.length} server ${word} not in your files? Record types cascade
19720
19991
  }
19721
19992
  console.log(`
19722
19993
  Pushed to preview "${sanitizeTerminalText(baseId)}".`);
19723
- autoBindBaseIfUnbound(baseId);
19994
+ seedScopeIfEmpty("bases", baseId);
19724
19995
  }
19725
19996
  var init_sync = __esm({
19726
19997
  "src/data/config-as-code/sync.ts"() {
@@ -19732,7 +20003,7 @@ var init_sync = __esm({
19732
20003
  init_terminal_output();
19733
20004
  init_base_id();
19734
20005
  init_push();
19735
- init_base_binding();
20006
+ init_worktree_scope();
19736
20007
  init_api();
19737
20008
  init_config_parser();
19738
20009
  init_config_writer();
@@ -19759,7 +20030,7 @@ __export(push_exports, {
19759
20030
  });
19760
20031
  import * as fs18 from "fs";
19761
20032
  import * as path22 from "path";
19762
- import * as yaml10 from "js-yaml";
20033
+ import * as yaml11 from "js-yaml";
19763
20034
  function parseArgs5(args2) {
19764
20035
  let autoConfirm = false;
19765
20036
  let label;
@@ -19873,7 +20144,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
19873
20144
  for (const file of yamlFiles) {
19874
20145
  try {
19875
20146
  const content = fs18.readFileSync(path22.join(agentsDir, file), "utf-8");
19876
- const agent = yaml10.load(content);
20147
+ const agent = yaml11.load(content);
19877
20148
  if (agent?.id && agent.name) {
19878
20149
  agentsWithIds.push({ id: agent.id, name: agent.name });
19879
20150
  }
@@ -19886,7 +20157,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
19886
20157
  const yamlPath = resolveHubYamlPath(hubFolder);
19887
20158
  if (!yamlPath) return;
19888
20159
  const yamlContent = fs18.readFileSync(yamlPath, "utf-8");
19889
- const config = yaml10.load(yamlContent);
20160
+ const config = yaml11.load(yamlContent);
19890
20161
  agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
19891
20162
  }
19892
20163
  if (agentsWithIds.length === 0) return;
@@ -20036,7 +20307,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
20036
20307
  } else {
20037
20308
  console.log("No changes to push; folder renamed.");
20038
20309
  }
20039
- autoBindIfUnbound(hubId);
20310
+ seedScopeIfEmpty("hubs", hubId);
20040
20311
  return true;
20041
20312
  }
20042
20313
  console.log("\nChanges to push:");
@@ -20052,7 +20323,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
20052
20323
  const result = await client.push(hubId, wireConfig, organizationId);
20053
20324
  printSyncResult(result);
20054
20325
  await syncAfterPush(client, hubId, hubFolder, organizationId);
20055
- autoBindIfUnbound(hubId);
20326
+ seedScopeIfEmpty("hubs", hubId);
20056
20327
  return true;
20057
20328
  }
20058
20329
  function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
@@ -20118,7 +20389,7 @@ hub_environment: preview
20118
20389
  ${content}`;
20119
20390
  }
20120
20391
  fs18.writeFileSync(hubYamlPath, updated, "utf-8");
20121
- autoBindIfUnbound(hubId);
20392
+ seedScopeIfEmpty("hubs", hubId);
20122
20393
  await pushSingleHub(client, hubId, newHub.hubFolder, opts.autoConfirm, opts.organizationId, { skipAgentRename: true });
20123
20394
  }
20124
20395
  function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsLabel) {
@@ -20188,11 +20459,11 @@ async function pushCommand(args2) {
20188
20459
  console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
20189
20460
  }
20190
20461
  console.log(`Target hub: ${path22.basename(existing.hubFolder)} (${existing.hubId})`);
20191
- assertHubMatchesBinding(existing.hubId, path22.basename(existing.hubFolder));
20462
+ assertInScope("hubs", existing.hubId, path22.basename(existing.hubFolder));
20192
20463
  await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
20193
20464
  return;
20194
20465
  }
20195
- assertNoBindingBlocksHubCreation(resolution.target.hub.hubName);
20466
+ assertScopeAllowsCreation("hubs", resolution.target.hub.hubName);
20196
20467
  await createAndPushNewHub(client, resolution.target.hub, { autoConfirm, label, organizationId });
20197
20468
  }
20198
20469
  var LOCAL_CHANGE_LIST_CAP;
@@ -20211,7 +20482,7 @@ var init_push = __esm({
20211
20482
  init_layout();
20212
20483
  init_workspace_files();
20213
20484
  init_repo_config();
20214
- init_hub_binding();
20485
+ init_worktree_scope();
20215
20486
  init_subtree_routing();
20216
20487
  LOCAL_CHANGE_LIST_CAP = 20;
20217
20488
  }
@@ -20285,7 +20556,7 @@ async function pullCommand(args2) {
20285
20556
  console.log(`Production hub mirrored (read-only) \u2192 ${folder}`);
20286
20557
  return;
20287
20558
  }
20288
- assertHubMatchesBinding(hubId);
20559
+ assertInScope("hubs", hubId);
20289
20560
  let hubFolder = existingFolder;
20290
20561
  let wroteFiles = false;
20291
20562
  if (!hubFolder) {
@@ -20344,7 +20615,7 @@ async function pullCommand(args2) {
20344
20615
  printLocalFileChanges(delta);
20345
20616
  }
20346
20617
  }
20347
- if (wroteFiles) autoBindIfUnbound(hubId);
20618
+ if (wroteFiles) seedScopeIfEmpty("hubs", hubId);
20348
20619
  if (payload.production_hub_id) {
20349
20620
  await mirrorLinkedProduction2(client, workspaceDir, payload.production_hub_id, organizationId);
20350
20621
  }
@@ -20406,7 +20677,7 @@ var init_pull = __esm({
20406
20677
  init_layout();
20407
20678
  init_workspace_files();
20408
20679
  init_repo_config();
20409
- init_hub_binding();
20680
+ init_worktree_scope();
20410
20681
  init_subtree_routing();
20411
20682
  MIRROR_MARKER_PREFIX2 = "# Read-only mirror of production hub";
20412
20683
  }
@@ -20472,7 +20743,7 @@ async function createCommand(args2) {
20472
20743
  }
20473
20744
  process.exit(1);
20474
20745
  }
20475
- assertNoBindingBlocksHubCreation(resolution.hub.hubName);
20746
+ assertScopeAllowsCreation("hubs", resolution.hub.hubName);
20476
20747
  await createAndPushNewHub(client, resolution.hub, { autoConfirm, label, organizationId });
20477
20748
  }
20478
20749
  var init_create = __esm({
@@ -20484,7 +20755,7 @@ var init_create = __esm({
20484
20755
  init_workspace();
20485
20756
  init_layout();
20486
20757
  init_repo_config();
20487
- init_hub_binding();
20758
+ init_worktree_scope();
20488
20759
  }
20489
20760
  });
20490
20761
 
@@ -20637,7 +20908,7 @@ async function replicateCommand(args2) {
20637
20908
  payload.preview_label,
20638
20909
  payload.branch_name
20639
20910
  );
20640
- autoBindIfUnbound(previewHubId);
20911
+ seedScopeIfEmpty("hubs", previewHubId);
20641
20912
  if (folderPreExisted) printLocalFileChanges(delta);
20642
20913
  console.log(`Preview written to ${path25.basename(hubFolder)}`);
20643
20914
  console.log(` Switch to it with: wayai use ${path25.basename(hubFolder)}`);
@@ -20653,7 +20924,7 @@ var init_replicate = __esm({
20653
20924
  init_workspace();
20654
20925
  init_layout();
20655
20926
  init_repo_config();
20656
- init_hub_binding();
20927
+ init_worktree_scope();
20657
20928
  init_contracts();
20658
20929
  }
20659
20930
  });
@@ -20716,7 +20987,7 @@ async function relabelCommand(args2) {
20716
20987
  console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
20717
20988
  return;
20718
20989
  }
20719
- assertHubMatchesBinding(target.hubId, path26.basename(target.hubFolder));
20990
+ assertInScope("hubs", target.hubId, path26.basename(target.hubFolder));
20720
20991
  console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
20721
20992
  const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
20722
20993
  const row = data[0];
@@ -20745,7 +21016,7 @@ var init_relabel = __esm({
20745
21016
  init_workspace();
20746
21017
  init_layout();
20747
21018
  init_repo_config();
20748
- init_hub_binding();
21019
+ init_worktree_scope();
20749
21020
  init_contracts();
20750
21021
  }
20751
21022
  });
@@ -20872,7 +21143,7 @@ async function publishCommand(args2) {
20872
21143
  return;
20873
21144
  }
20874
21145
  }
20875
- assertHubMatchesBinding(hubId, hubFolder ? path27.basename(hubFolder) : void 0);
21146
+ assertInScope("hubs", hubId, hubFolder ? path27.basename(hubFolder) : void 0);
20876
21147
  await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
20877
21148
  }
20878
21149
  var STATUS_STYLE;
@@ -20888,7 +21159,7 @@ var init_publish = __esm({
20888
21159
  init_layout();
20889
21160
  init_resource_files();
20890
21161
  init_repo_config();
20891
- init_hub_binding();
21162
+ init_worktree_scope();
20892
21163
  STATUS_STYLE = {
20893
21164
  added: { sym: "+", color: 32 },
20894
21165
  // green
@@ -20900,77 +21171,151 @@ var init_publish = __esm({
20900
21171
  }
20901
21172
  });
20902
21173
 
21174
+ // src/lib/scope-selector.ts
21175
+ import * as path28 from "path";
21176
+ function findAddFlag(args2) {
21177
+ return args2.find((arg) => arg === "--add" || arg.startsWith("--add="));
21178
+ }
21179
+ function isValuedFlag(token) {
21180
+ return token.includes("=");
21181
+ }
21182
+ function resolveScopeSelector(verb, args2) {
21183
+ const { hubFlag, baseFlag, positional } = parseRoutingTokens(args2);
21184
+ if (hubFlag !== void 0 && baseFlag !== void 0) {
21185
+ throw expected(mixedInvocationRefusal(verb, hubFlag, baseFlag));
21186
+ }
21187
+ assertValueIsNotAFlag(verb, "--hub", hubFlag);
21188
+ assertValueIsNotAFlag(verb, "--base", baseFlag);
21189
+ assertFlagWasGivenAValue(verb, "--hub", args2, hubFlag);
21190
+ assertFlagWasGivenAValue(verb, "--base", args2, baseFlag);
21191
+ if (hubFlag !== void 0) return { axis: "hubs", target: hubFlag, gitRoot: findGitRoot() };
21192
+ if (baseFlag !== void 0) return { axis: "bases", target: baseFlag, gitRoot: findGitRoot() };
21193
+ if (positional === void 0) return null;
21194
+ const gitRoot = findGitRoot();
21195
+ const layout = gitRoot ? resolveLayout(gitRoot) : null;
21196
+ const resolved = resolveSelectorSubtree(layout, positional, process.cwd());
21197
+ if (resolved.kind === "both") {
21198
+ throw expected(mixedInvocationRefusal(verb, resolved.target, resolved.target));
21199
+ }
21200
+ if (resolved.kind === "hubs" || resolved.kind === "bases") {
21201
+ return { axis: resolved.kind, target: resolved.target, gitRoot };
21202
+ }
21203
+ return { axis: "hubs", target: positional, gitRoot };
21204
+ }
21205
+ function assertValueIsNotAFlag(verb, flag, captured) {
21206
+ if (captured === void 0 || !captured.startsWith("-")) return;
21207
+ throw refusal(verb, flag, `but got ${JSON.stringify(captured)} \u2014 which is a flag, not a hub or base`);
21208
+ }
21209
+ function assertFlagWasGivenAValue(verb, flag, args2, captured) {
21210
+ if (captured !== void 0 && captured !== "") return;
21211
+ if (!args2.some((arg) => arg === flag || arg.startsWith(`${flag}=`))) return;
21212
+ throw refusal(verb, flag, "but no target followed it");
21213
+ }
21214
+ function refusal(verb, flag, problem) {
21215
+ return expected(
21216
+ `\`wayai ${verb} ${flag}\` needs a target, ${problem}.
21217
+ wayai ${verb} ${flag} <hub-or-base>`
21218
+ );
21219
+ }
21220
+ function resolveHubSelectorToId(gitRoot, selector) {
21221
+ if (UUID_RE2.test(selector)) return selector;
21222
+ const workspaceDir = detectWorkspace()?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
21223
+ if (!workspaceDir) {
21224
+ throw expected("Not inside a git repository, and no UUID provided.");
21225
+ }
21226
+ const match = findHubByFolderName(workspaceDir, selector);
21227
+ if (match) return match.hubId;
21228
+ const newHubs = scanNewHubs(workspaceDir);
21229
+ const pending = newHubs.find((h) => path28.basename(h.hubFolder) === selector);
21230
+ if (pending) {
21231
+ throw expected(
21232
+ `"${selector}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
21233
+ Create it first \u2014 the worktree scope picks it up automatically afterward:
21234
+ wayai create ${selector}`
21235
+ );
21236
+ }
21237
+ throw expected(
21238
+ [
21239
+ `No hub matching "${selector}" found in ${hubsDirLabel(gitRoot)}/. Pass a UUID or a folder name from:`,
21240
+ ...scanWorkspaceHubs(workspaceDir).map((h) => ` ${path28.basename(h.hubFolder)} (${h.hubId})`),
21241
+ ...newHubs.map(
21242
+ (h) => ` ${path28.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path28.basename(h.hubFolder)}\`)`
21243
+ )
21244
+ ].join("\n")
21245
+ );
21246
+ }
21247
+ function resolveScopeTarget(selector) {
21248
+ const { axis, target, gitRoot } = selector;
21249
+ if (axis === "hubs") {
21250
+ return { axis, id: resolveHubSelectorToId(gitRoot, target) };
21251
+ }
21252
+ const id = resolveBaseSelectorToId(gitRoot, target);
21253
+ assertValidBaseSelector(id, "base id");
21254
+ return { axis, id };
21255
+ }
21256
+ var init_scope_selector = __esm({
21257
+ "src/lib/scope-selector.ts"() {
21258
+ "use strict";
21259
+ init_expected();
21260
+ init_base_workspace();
21261
+ init_layout();
21262
+ init_subtree_routing();
21263
+ init_utils();
21264
+ init_workspace();
21265
+ }
21266
+ });
21267
+
20903
21268
  // src/commands/use.ts
20904
21269
  var use_exports = {};
20905
21270
  __export(use_exports, {
20906
21271
  useCommand: () => useCommand
20907
21272
  });
20908
- import * as path28 from "path";
20909
21273
  async function useCommand(args2) {
20910
- const target = args2[0];
20911
- if (!target || target.startsWith("-")) {
20912
- console.error("Usage: wayai use <hub_uuid|folder-name>");
20913
- process.exit(1);
20914
- }
20915
- let hubId;
20916
- if (UUID_RE2.test(target)) {
20917
- hubId = target;
20918
- } else {
20919
- const workspace = detectWorkspace();
20920
- const gitRoot = findGitRoot();
20921
- const workspaceDir = workspace?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
20922
- if (!workspaceDir) {
20923
- console.error("Not inside a git repository, and no UUID provided.");
20924
- process.exit(1);
20925
- }
20926
- const wsLabel = hubsDirLabel(gitRoot);
20927
- const match = findHubByFolderName(workspaceDir, target);
20928
- if (!match) {
20929
- const newHubs = scanNewHubs(workspaceDir);
20930
- const pending = newHubs.find((h) => path28.basename(h.hubFolder) === target);
20931
- if (pending) {
20932
- console.error(
20933
- `"${target}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
20934
- Create it first \u2014 the worktree binds automatically afterward:
20935
- wayai create ${target}`
20936
- );
20937
- process.exit(1);
20938
- }
20939
- console.error(`No hub matching "${target}" found in ${wsLabel}/. Pass a UUID or a folder name from:`);
20940
- for (const h of scanWorkspaceHubs(workspaceDir)) {
20941
- console.error(` ${path28.basename(h.hubFolder)} (${h.hubId})`);
20942
- }
20943
- for (const h of newHubs) {
20944
- console.error(` ${path28.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path28.basename(h.hubFolder)}\`)`);
20945
- }
20946
- process.exit(1);
20947
- }
20948
- hubId = match.hubId;
21274
+ const addFlag = findAddFlag(args2);
21275
+ if (addFlag !== void 0 && isValuedFlag(addFlag)) {
21276
+ throw expected(
21277
+ [
21278
+ `\`--add\` is a boolean flag \u2014 write it bare, not ${JSON.stringify(addFlag)}.`,
21279
+ " wayai use --add <selector> # widen the worktree scope",
21280
+ " wayai use <selector> # replace that axis instead"
21281
+ ].join("\n")
21282
+ );
20949
21283
  }
20950
- const previous = readBinding();
20951
- if (previous === hubId) {
20952
- console.log(`Worktree already bound to ${hubId}.`);
21284
+ const add = addFlag !== void 0;
21285
+ const selector = resolveScopeSelector("use", args2);
21286
+ if (!selector) throw expected(USAGE);
21287
+ const { axis, id } = resolveScopeTarget(selector);
21288
+ const noun = axisNoun(axis);
21289
+ if (add) {
21290
+ const widened = addToScope(axis, id);
21291
+ console.log(
21292
+ widened ? `Worktree scope widened \u2014 now ${describeAxis(axis, widened)}.` : `Worktree scope already includes ${noun} ${id}.`
21293
+ );
20953
21294
  return;
20954
21295
  }
20955
- try {
20956
- writeBinding(hubId);
20957
- } catch (err) {
20958
- console.error(err instanceof Error ? err.message : String(err));
20959
- process.exit(1);
20960
- }
20961
- if (previous) {
20962
- console.log(`Worktree rebound: ${previous} \u2192 ${hubId}`);
20963
- } else {
20964
- console.log(`Worktree bound to hub ${hubId}.`);
21296
+ const previous = readScope()[axis];
21297
+ if (previous.length === 1 && previous[0] === id) {
21298
+ console.log(`Worktree already bound to ${noun} ${id}.`);
21299
+ return;
20965
21300
  }
21301
+ setScopeAxis(axis, [id]);
21302
+ console.log(
21303
+ previous.length > 0 ? `Worktree rebound: ${previous.join(", ")} \u2192 ${id}.` : `Worktree bound to ${noun} ${id}.`
21304
+ );
20966
21305
  }
21306
+ var USAGE;
20967
21307
  var init_use = __esm({
20968
21308
  "src/commands/use.ts"() {
20969
21309
  "use strict";
20970
- init_hub_binding();
20971
- init_workspace();
20972
- init_layout();
20973
- init_utils();
21310
+ init_expected();
21311
+ init_scope_selector();
21312
+ init_worktree_scope();
21313
+ USAGE = [
21314
+ "Usage: wayai use [--add] <hub_uuid|folder-name|hubs/<name>|bases/<name>>",
21315
+ " wayai use [--add] --hub <hub> | --base <base>",
21316
+ "",
21317
+ " --add widen the worktree scope instead of replacing that axis"
21318
+ ].join("\n");
20974
21319
  }
20975
21320
  });
20976
21321
 
@@ -21022,9 +21367,10 @@ async function migrateCommand(_args) {
21022
21367
  const newOrg = path29.join(newWs, WAYAI_LAYOUT.orgSubdir);
21023
21368
  const hasLegacyWs = isDirectory(legacyWs);
21024
21369
  const hasLegacyOrg = isDirectory(legacyOrg);
21025
- if (!hasLegacyWs && !hasLegacyOrg) {
21026
- console.log(`Already migrated \u2014 no legacy ${WAYAI_LAYOUT.legacy.wsDir}/ or ${WAYAI_LAYOUT.legacy.orgAtRoot}/ to move.`);
21027
- return;
21370
+ const orgPlan = planOrgMigration(gitRoot);
21371
+ if (orgPlan.kind === "refuse") {
21372
+ console.error(orgPlan.message);
21373
+ process.exit(1);
21028
21374
  }
21029
21375
  if (hasLegacyWs && isDirectory(newHubs)) {
21030
21376
  console.error(
@@ -21050,10 +21396,30 @@ async function migrateCommand(_args) {
21050
21396
  usedFsFallback ||= how === "fs";
21051
21397
  moved.push(`${WAYAI_LAYOUT.legacy.orgAtRoot}/ \u2192 ${rel(newOrg)}/`);
21052
21398
  }
21053
- console.log(`Migrated to the ${WAYAI_LAYOUT.wsDir}/ layout:`);
21054
- for (const m of moved) console.log(` ${m}`);
21055
- if (usedFsFallback) {
21056
- console.log("\n(Some dirs were moved on disk, not via git \u2014 they will show as untracked.)");
21399
+ if (moved.length > 0) {
21400
+ console.log(`Migrated to the ${WAYAI_LAYOUT.wsDir}/ layout:`);
21401
+ for (const m of moved) console.log(` ${m}`);
21402
+ if (usedFsFallback) {
21403
+ console.log("\n(Some dirs were moved on disk, not via git \u2014 they will show as untracked.)");
21404
+ }
21405
+ }
21406
+ const copiedOrg = orgPlan.kind === "copy";
21407
+ if (copiedOrg) {
21408
+ writeRepoConfig(orgPlan.config, gitRoot);
21409
+ console.log(`${moved.length > 0 ? "\n" : ""}Copied the organization binding into ${WORKSPACE_MANIFEST_LABEL}.`);
21410
+ console.log(
21411
+ ` ${ROOT_CONFIG_FILE} is KEPT on purpose \u2014 an older CLI in a teammate's environment or in CI still requires it.`
21412
+ );
21413
+ console.log(
21414
+ " It stays a supported fallback until the next major, which removes both the fallback and the file."
21415
+ );
21416
+ console.log(" Delete it once every environment that runs this repo reads the manifest.");
21417
+ }
21418
+ if (moved.length === 0 && !copiedOrg) {
21419
+ console.log(
21420
+ `Already migrated \u2014 no legacy ${WAYAI_LAYOUT.legacy.wsDir}/ or ${WAYAI_LAYOUT.legacy.orgAtRoot}/ to move, and no organization binding to copy into ${WORKSPACE_MANIFEST_LABEL}.`
21421
+ );
21422
+ return;
21057
21423
  }
21058
21424
  console.log("\nNext steps:");
21059
21425
  console.log(' git add -A && git commit -m "chore: migrate to wayai-ws layout"');
@@ -21064,6 +21430,8 @@ var init_migrate = __esm({
21064
21430
  "use strict";
21065
21431
  init_workspace();
21066
21432
  init_layout();
21433
+ init_repo_config();
21434
+ init_workspace_manifest();
21067
21435
  }
21068
21436
  });
21069
21437
 
@@ -21072,19 +21440,71 @@ var unbind_exports = {};
21072
21440
  __export(unbind_exports, {
21073
21441
  unbindCommand: () => unbindCommand
21074
21442
  });
21075
- async function unbindCommand(_args) {
21076
- const previous = readBinding();
21077
- if (!previous) {
21078
- console.log("No worktree binding to clear.");
21443
+ async function unbindCommand(args2) {
21444
+ const addFlag = findAddFlag(args2);
21445
+ if (addFlag !== void 0) {
21446
+ throw expected(
21447
+ [
21448
+ `\`wayai unbind\` does not take ${JSON.stringify(addFlag)}.`,
21449
+ " wayai unbind <selector> # remove one entry from the worktree scope",
21450
+ " wayai unbind # clear the whole scope (both hubs and bases)",
21451
+ " wayai use --add <selector> # WIDEN the scope instead (the opposite change)"
21452
+ ].join("\n")
21453
+ );
21454
+ }
21455
+ const selector = resolveScopeSelector("unbind", args2);
21456
+ if (!selector) {
21457
+ if (args2.length > 0) {
21458
+ throw expected(
21459
+ [
21460
+ `\`wayai unbind ${args2.join(" ")}\` does not name anything to remove.`,
21461
+ "",
21462
+ "Refusing rather than clearing the whole scope \u2014 that is what a BARE",
21463
+ "`wayai unbind` does, and it is not what this invocation asked for.",
21464
+ " wayai unbind <selector> # remove one entry",
21465
+ " wayai unbind # clear the whole scope (both hubs and bases)"
21466
+ ].join("\n")
21467
+ );
21468
+ }
21469
+ const described = describeScope(readScope());
21470
+ clearScope();
21471
+ console.log(
21472
+ described ? `Worktree scope cleared (was ${described}).` : "No worktree scope to clear."
21473
+ );
21079
21474
  return;
21080
21475
  }
21081
- clearBinding();
21082
- console.log(`Worktree unbound (was: ${previous}).`);
21476
+ const scope = readScope();
21477
+ const literal = AXIS_ORDER.filter((axis2) => scope[axis2].includes(selector.target));
21478
+ const literalAxis = literal.includes(selector.axis) ? selector.axis : literal.length === 1 ? literal[0] : null;
21479
+ if (literalAxis) {
21480
+ announce(literalAxis, selector.target, removeFromScope(literalAxis, selector.target) ?? []);
21481
+ return;
21482
+ }
21483
+ const { axis, id } = resolveScopeTarget(selector);
21484
+ const remaining = removeFromScope(axis, id);
21485
+ if (!remaining) {
21486
+ const described = describeScope(scope);
21487
+ throw expected(
21488
+ [
21489
+ `${axisNoun(axis)} ${id} is not in this worktree's scope.`,
21490
+ described ? `This worktree is scoped to ${described}.` : "This worktree has no scope."
21491
+ ].join("\n")
21492
+ );
21493
+ }
21494
+ announce(axis, id, remaining);
21495
+ }
21496
+ function announce(axis, id, remaining) {
21497
+ const noun = axisNoun(axis);
21498
+ console.log(
21499
+ remaining.length > 0 ? `Removed ${noun} ${id} from the worktree scope (${axis}: ${remaining.join(", ")}).` : `Removed ${noun} ${id} \u2014 this worktree is no longer scoped to any ${noun}.`
21500
+ );
21083
21501
  }
21084
21502
  var init_unbind = __esm({
21085
21503
  "src/commands/unbind.ts"() {
21086
21504
  "use strict";
21087
- init_hub_binding();
21505
+ init_expected();
21506
+ init_scope_selector();
21507
+ init_worktree_scope();
21088
21508
  }
21089
21509
  });
21090
21510
 
@@ -22405,8 +22825,8 @@ function installEvalSignalHandlers(input) {
22405
22825
  let signalCount = 0;
22406
22826
  let disposed = false;
22407
22827
  let settle;
22408
- const settled = new Promise((resolve7) => {
22409
- settle = resolve7;
22828
+ const settled = new Promise((resolve8) => {
22829
+ settle = resolve8;
22410
22830
  });
22411
22831
  const listeners = /* @__PURE__ */ new Map();
22412
22832
  const dispose = () => {
@@ -22756,7 +23176,7 @@ Timeout after ${timeoutSeconds}s${queuedSeconds > 0 ? ` (${queuedSeconds}s of it
22756
23176
  process.exit(1);
22757
23177
  }
22758
23178
  function sleep(ms) {
22759
- return new Promise((resolve7) => setTimeout(resolve7, ms));
23179
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
22760
23180
  }
22761
23181
  function parseRunNumbers(raw) {
22762
23182
  if (!raw || raw.startsWith("--")) {
@@ -23028,7 +23448,7 @@ __export(eval_capture_exports, {
23028
23448
  });
23029
23449
  import * as fs24 from "fs";
23030
23450
  import * as path31 from "path";
23031
- import * as yaml11 from "js-yaml";
23451
+ import * as yaml12 from "js-yaml";
23032
23452
  function isValidSetName(name) {
23033
23453
  if (name.length === 0 || name === "." || name === "..") return false;
23034
23454
  if (name.startsWith(".")) return false;
@@ -23134,7 +23554,7 @@ async function evalCaptureCommand(args2) {
23134
23554
  };
23135
23555
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
23136
23556
  fs24.mkdirSync(targetDir, { recursive: true });
23137
- fs24.writeFileSync(targetPath, yaml11.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
23557
+ fs24.writeFileSync(targetPath, yaml12.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
23138
23558
  const relPath = path31.relative(process.cwd(), targetPath);
23139
23559
  console.log(`
23140
23560
  Wrote ${relPath}`);
@@ -24507,7 +24927,7 @@ var init_set_connection_credential = __esm({
24507
24927
  // src/lib/org-workspace.ts
24508
24928
  import * as fs25 from "fs";
24509
24929
  import * as path32 from "path";
24510
- import * as yaml12 from "js-yaml";
24930
+ import * as yaml13 from "js-yaml";
24511
24931
  function getOrgDir(gitRoot) {
24512
24932
  return resolveLayout(gitRoot).orgDir;
24513
24933
  }
@@ -24518,7 +24938,7 @@ function parseOrgResources(orgDir) {
24518
24938
  const manifestPath = path32.join(orgDir, ORG_MANIFEST_NAME);
24519
24939
  let manifest = {};
24520
24940
  if (fs25.existsSync(manifestPath)) {
24521
- manifest = yaml12.load(fs25.readFileSync(manifestPath, "utf-8")) ?? {};
24941
+ manifest = yaml13.load(fs25.readFileSync(manifestPath, "utf-8")) ?? {};
24522
24942
  }
24523
24943
  const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
24524
24944
  const resourcesDir = path32.join(orgDir, "resources");
@@ -24551,7 +24971,7 @@ function writeOrgResources(orgDir, payload) {
24551
24971
  });
24552
24972
  fs25.writeFileSync(
24553
24973
  path32.join(orgDir, ORG_MANIFEST_NAME),
24554
- yaml12.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
24974
+ yaml13.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
24555
24975
  "utf-8"
24556
24976
  );
24557
24977
  const resourcesDir = path32.join(orgDir, "resources");
@@ -25373,7 +25793,7 @@ async function runSkillInstall(positional) {
25373
25793
  console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
25374
25794
  }
25375
25795
  async function runSandboxExec(positional, flagArgs) {
25376
- const USAGE = 'wayai admin sandbox exec [--cmd "<command>" | <command>] --hub <hub_id> --connection <connection_id> [--egress open|allowlist|isolated] [--allow <host> ...] [--timeout <ms>] [--json]';
25796
+ const USAGE2 = 'wayai admin sandbox exec [--cmd "<command>" | <command>] --hub <hub_id> --connection <connection_id> [--egress open|allowlist|isolated] [--allow <host> ...] [--timeout <ms>] [--json]';
25377
25797
  let hubId;
25378
25798
  let connectionId;
25379
25799
  let egress;
@@ -25427,7 +25847,7 @@ async function runSandboxExec(positional, flagArgs) {
25427
25847
  const positionalCmd = positional[0];
25428
25848
  if (positional.length > 1) {
25429
25849
  console.error("Provide the command as a single quoted argument (or use --cmd).");
25430
- console.error(USAGE);
25850
+ console.error(USAGE2);
25431
25851
  process.exit(1);
25432
25852
  }
25433
25853
  if (positionalCmd && cmdFlag) {
@@ -25436,7 +25856,7 @@ async function runSandboxExec(positional, flagArgs) {
25436
25856
  }
25437
25857
  const cmd = cmdFlag ?? positionalCmd;
25438
25858
  if (!cmd || !hubId || !connectionId) {
25439
- console.error(USAGE);
25859
+ console.error(USAGE2);
25440
25860
  process.exit(1);
25441
25861
  }
25442
25862
  const body = {
@@ -25533,8 +25953,8 @@ function parseScopedTargetFlags(flagArgs, usage3, opts = {}) {
25533
25953
  return { body: { scope: "hub", hub_id: hubId, dry_run: dryRun }, targetLabel: `hub ${hubId}`, dryRun, yes, jsonOutput };
25534
25954
  }
25535
25955
  async function runRepairTeamOrphans(flagArgs) {
25536
- const USAGE = "wayai admin hubs repair-team-orphans --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25537
- const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE);
25956
+ const USAGE2 = "wayai admin hubs repair-team-orphans --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25957
+ const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE2);
25538
25958
  if (!dryRun && !yes && !jsonOutput) {
25539
25959
  console.log(`About to PURGE dangling team references in: ${targetLabel}.`);
25540
25960
  console.log("Memberships pointing at a deleted team are removed (HubDO + UserDO mirror); conversations pinned to one are unassigned.");
@@ -25580,8 +26000,8 @@ async function runRepairTeamOrphans(flagArgs) {
25580
26000
  }
25581
26001
  }
25582
26002
  async function runRepairLegacyOrgGrants(flagArgs) {
25583
- const USAGE = "wayai admin orgs repair-legacy-grants --all | --org <org_id> [--dry-run] [--yes] [--json]";
25584
- const scoped = parseScopedTargetFlags(flagArgs, USAGE, {
26003
+ const USAGE2 = "wayai admin orgs repair-legacy-grants --all | --org <org_id> [--dry-run] [--yes] [--json]";
26004
+ const scoped = parseScopedTargetFlags(flagArgs, USAGE2, {
25585
26005
  scopes: ["all", "org"],
25586
26006
  allLabel: "ALL organizations (platform-wide)"
25587
26007
  });
@@ -25633,8 +26053,8 @@ async function runRepairLegacyOrgGrants(flagArgs) {
25633
26053
  }
25634
26054
  }
25635
26055
  async function runHarnessMassDestroy(flagArgs) {
25636
- const USAGE = "wayai admin harness mass-destroy --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25637
- const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE);
26056
+ const USAGE2 = "wayai admin harness mass-destroy --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
26057
+ const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE2);
25638
26058
  if (!dryRun && !yes && !jsonOutput) {
25639
26059
  console.log(`About to REAP every live harness sandbox / token / slot in: ${targetLabel}.`);
25640
26060
  console.log("This destroys running sandboxes and revokes their tokens. Run with --dry-run first to see the blast radius.");
@@ -25688,8 +26108,8 @@ function rejectHarnessFlags(flagArgs, usage3) {
25688
26108
  process.exit(1);
25689
26109
  }
25690
26110
  async function runHarnessToggle(enable, flagArgs) {
25691
- const USAGE = "wayai admin harness enable|disable";
25692
- rejectHarnessFlags(flagArgs, USAGE);
26111
+ const USAGE2 = "wayai admin harness enable|disable";
26112
+ rejectHarnessFlags(flagArgs, USAGE2);
25693
26113
  const { config, accessToken } = await requireAuth();
25694
26114
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
25695
26115
  let value;
@@ -25710,8 +26130,8 @@ async function runHarnessToggle(enable, flagArgs) {
25710
26130
  }
25711
26131
  }
25712
26132
  async function runHarnessStatus(flagArgs) {
25713
- const USAGE = "wayai admin harness status";
25714
- rejectHarnessFlags(flagArgs, USAGE);
26133
+ const USAGE2 = "wayai admin harness status";
26134
+ rejectHarnessFlags(flagArgs, USAGE2);
25715
26135
  const { config, accessToken } = await requireAuth();
25716
26136
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
25717
26137
  let value = 0;
@@ -26612,26 +27032,26 @@ async function reportGetCommand(args2) {
26612
27032
  console.log(JSON.stringify(result, null, 2));
26613
27033
  return;
26614
27034
  }
26615
- const { report, messages } = result;
26616
- const title = sanitizeTerminalText(report.title);
26617
- const description = sanitizeTerminalText(report.description);
26618
- console.log(`Report ${report.report_id}`);
27035
+ const { report: report2, messages } = result;
27036
+ const title = sanitizeTerminalText(report2.title);
27037
+ const description = sanitizeTerminalText(report2.description);
27038
+ console.log(`Report ${report2.report_id}`);
26619
27039
  console.log(` title: ${title}`);
26620
27040
  console.log(` description: ${description.replace(/\n/g, "\n ")}`);
26621
- console.log(` status: ${report.status}`);
26622
- console.log(` classification: ${report.classification}`);
26623
- if (report.contest_count > 0) console.log(` contests: ${report.contest_count}`);
26624
- if (report.dismissal_final) console.log(" dismissal: final (non-contestable)");
27041
+ console.log(` status: ${report2.status}`);
27042
+ console.log(` classification: ${report2.classification}`);
27043
+ if (report2.contest_count > 0) console.log(` contests: ${report2.contest_count}`);
27044
+ if (report2.dismissal_final) console.log(" dismissal: final (non-contestable)");
26625
27045
  if (messages.length > 0) {
26626
27046
  console.log("\n Thread:");
26627
27047
  for (const m of messages) {
26628
27048
  console.log(` [${m.author_role}] ${sanitizeTerminalText(m.body)}`);
26629
27049
  }
26630
27050
  }
26631
- if (report.status === "shipped") {
27051
+ if (report2.status === "shipped") {
26632
27052
  console.log(`
26633
- This fix is shipped. Run \`wayai report accept ${report.report_id}\` if it works,`);
26634
- console.log(` or \`wayai report contest ${report.report_id} --reason "..."\` if it does not.`);
27053
+ This fix is shipped. Run \`wayai report accept ${report2.report_id}\` if it works,`);
27054
+ console.log(` or \`wayai report contest ${report2.report_id} --reason "..."\` if it does not.`);
26635
27055
  }
26636
27056
  } catch (err) {
26637
27057
  reportActionError(err);
@@ -26911,7 +27331,7 @@ var init_actions = __esm({
26911
27331
 
26912
27332
  // src/data/commands/attachments.ts
26913
27333
  import { Command as Command2 } from "commander";
26914
- import { readFileSync as readFileSync21 } from "fs";
27334
+ import { readFileSync as readFileSync20 } from "fs";
26915
27335
  function findAttachmentByFilename(attachments, filename) {
26916
27336
  return attachments.find((a) => a.key.endsWith(`/${filename}`)) ?? null;
26917
27337
  }
@@ -26952,7 +27372,7 @@ function buildAttachmentsCommand() {
26952
27372
  printOutput(data, outputFormat(this));
26953
27373
  return;
26954
27374
  }
26955
- const body = readFileSync21(opts.file);
27375
+ const body = readFileSync20(opts.file);
26956
27376
  await client.upload(uploadPathFrom(data?.upload_url), body, opts.contentType);
26957
27377
  printOutput({ ...data, uploaded: true }, outputFormat(this));
26958
27378
  });
@@ -27432,16 +27852,16 @@ var init_providers = __esm({
27432
27852
 
27433
27853
  // src/data/commands/report.ts
27434
27854
  import { Command as Command6 } from "commander";
27435
- import { readFileSync as readFileSync22 } from "fs";
27436
- import { dirname as dirname11, join as join32 } from "path";
27855
+ import { readFileSync as readFileSync21 } from "fs";
27856
+ import { dirname as dirname12, join as join31 } from "path";
27437
27857
  import { fileURLToPath as fileURLToPath2 } from "url";
27438
27858
  function resolveCliVersion() {
27439
27859
  for (const candidate of [
27440
- join32(here, "..", "package.json"),
27441
- join32(here, "..", "..", "..", "package.json")
27860
+ join31(here, "..", "package.json"),
27861
+ join31(here, "..", "..", "..", "package.json")
27442
27862
  ]) {
27443
27863
  try {
27444
- const version = JSON.parse(readFileSync22(candidate, "utf-8")).version;
27864
+ const version = JSON.parse(readFileSync21(candidate, "utf-8")).version;
27445
27865
  if (typeof version === "string" && version) return version;
27446
27866
  } catch {
27447
27867
  }
@@ -27475,10 +27895,10 @@ function reportActionError2(err) {
27475
27895
  throw err;
27476
27896
  }
27477
27897
  function buildBasesReportCommand() {
27478
- const report = new Command6("report").description(
27898
+ const report2 = new Command6("report").description(
27479
27899
  "File a report to the Data triage queue and verify the outcome"
27480
27900
  );
27481
- report.command("create").description("Submit a report to the Data triage queue (deduplicated)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--source <source>", "Origin: cli_report (default) | review | security_audit").option("--classification <c>", "Nature: bug (default) | flaky_test | security | enhancement").option(
27901
+ report2.command("create").description("Submit a report to the Data triage queue (deduplicated)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--source <source>", "Origin: cli_report (default) | review | security_audit").option("--classification <c>", "Nature: bug (default) | flaky_test | security | enhancement").option(
27482
27902
  "--dedup-key <key>",
27483
27903
  'Stable dedup key (e.g. "<file>::<test>"); collapses repeat occurrences'
27484
27904
  ).option("--severity <level>", "low | medium | high | critical").option("--steps <text>", "Steps to reproduce").option("--error-message <text>", "Exact error text if any").option("--context <text>", "Additional context (logs, request ids)").option("--locale <code>", "Notification locale (en | pt | es)", "en").option("--reporter-email <addr>", "Override reporter email (defaults to session email)").option("--base-id <id>", "Base the error relates to (defaults to the configured base)").option("--record-type <name>", "Record type the error relates to").option("--record-id <id>", "Record id (UUID) the error relates to").option("--external-id <id>", "External id of the related record").option("--external-source <src>", "External source of the related record").option("--relationship-id <id>", "Relationship id the error relates to").action(async function(opts) {
@@ -27529,7 +27949,7 @@ function buildBasesReportCommand() {
27529
27949
  );
27530
27950
  }
27531
27951
  });
27532
- report.command("list").description("List your reports, newest first").option("--status <s>", "Filter by status (e.g. shipped \u2014 awaiting your verification)").action(async function(opts) {
27952
+ report2.command("list").description("List your reports, newest first").option("--status <s>", "Filter by status (e.g. shipped \u2014 awaiting your verification)").action(async function(opts) {
27533
27953
  const client = await createDataClient();
27534
27954
  const qs = opts.status ? `?status=${encodeURIComponent(opts.status)}` : "";
27535
27955
  try {
@@ -27552,7 +27972,7 @@ function buildBasesReportCommand() {
27552
27972
  reportActionError2(err);
27553
27973
  }
27554
27974
  });
27555
- report.command("get <report_id>").description("Show one of your reports: status + the message thread").action(async function(reportId) {
27975
+ report2.command("get <report_id>").description("Show one of your reports: status + the message thread").action(async function(reportId) {
27556
27976
  const client = await createDataClient();
27557
27977
  try {
27558
27978
  const data = await client.request("GET", `/v1/reports/${pathSegment(reportId, "report id")}`);
@@ -27589,7 +28009,7 @@ function buildBasesReportCommand() {
27589
28009
  reportActionError2(err);
27590
28010
  }
27591
28011
  });
27592
- report.command("accept <report_id>").description("Accept a shipped fix (\u2192 addressed)").action(async function(reportId) {
28012
+ report2.command("accept <report_id>").description("Accept a shipped fix (\u2192 addressed)").action(async function(reportId) {
27593
28013
  const client = await createDataClient();
27594
28014
  try {
27595
28015
  const data = await client.request(
@@ -27605,7 +28025,7 @@ function buildBasesReportCommand() {
27605
28025
  reportActionError2(err);
27606
28026
  }
27607
28027
  });
27608
- report.command("contest <report_id>").description("Contest a shipped fix or a dismissal (\u2192 back to triage)").requiredOption("--reason <text>", "Why the fix/dismissal is wrong").action(async function(reportId, opts) {
28028
+ report2.command("contest <report_id>").description("Contest a shipped fix or a dismissal (\u2192 back to triage)").requiredOption("--reason <text>", "Why the fix/dismissal is wrong").action(async function(reportId, opts) {
27609
28029
  const client = await createDataClient();
27610
28030
  try {
27611
28031
  const data = await client.request(
@@ -27624,7 +28044,7 @@ function buildBasesReportCommand() {
27624
28044
  reportActionError2(err);
27625
28045
  }
27626
28046
  });
27627
- return report;
28047
+ return report2;
27628
28048
  }
27629
28049
  var here;
27630
28050
  var init_report2 = __esm({
@@ -27637,13 +28057,13 @@ var init_report2 = __esm({
27637
28057
  init_terminal_output();
27638
28058
  init_workspace();
27639
28059
  init_skill_version();
27640
- here = dirname11(fileURLToPath2(import.meta.url));
28060
+ here = dirname12(fileURLToPath2(import.meta.url));
27641
28061
  }
27642
28062
  });
27643
28063
 
27644
28064
  // src/data/commands/credentials.ts
27645
28065
  import { Command as Command7 } from "commander";
27646
- import { readFileSync as readFileSync23 } from "fs";
28066
+ import { readFileSync as readFileSync22 } from "fs";
27647
28067
  function withValueSourceOptions(cmd, what) {
27648
28068
  return cmd.option(
27649
28069
  "--file <path>",
@@ -27656,7 +28076,7 @@ async function resolveValue(opts, label) {
27656
28076
  throw expected("--file cannot be combined with --value-stdin or --value-prompt \u2014 pass one.");
27657
28077
  }
27658
28078
  try {
27659
- return readFileSync23(opts.file).toString("base64");
28079
+ return readFileSync22(opts.file).toString("base64");
27660
28080
  } catch (e) {
27661
28081
  throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
27662
28082
  }
@@ -27805,7 +28225,7 @@ var init_credentials = __esm({
27805
28225
 
27806
28226
  // src/data/commands/sql.ts
27807
28227
  import { Command as Command8 } from "commander";
27808
- import { readFileSync as readFileSync24 } from "fs";
28228
+ import { readFileSync as readFileSync23 } from "fs";
27809
28229
  function buildBasesSqlCommand() {
27810
28230
  return withBaseOption(new Command8("sql")).description("Execute a read-only SQL query against base data").argument("[query]", "SQL query (SELECT only)").option("--file <path>", "Read SQL from a file instead of the argument").option(
27811
28231
  "--param <kv...>",
@@ -27815,7 +28235,7 @@ function buildBasesSqlCommand() {
27815
28235
  let query;
27816
28236
  if (opts.file) {
27817
28237
  try {
27818
- query = readFileSync24(opts.file, "utf-8").trim();
28238
+ query = readFileSync23(opts.file, "utf-8").trim();
27819
28239
  } catch (e) {
27820
28240
  throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
27821
28241
  }
@@ -27950,16 +28370,16 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
27950
28370
  ).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
27951
28371
  const client = await createDataClient();
27952
28372
  const id = pathSegment(tokenId, "token id");
27953
- const path36 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
28373
+ const path35 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
27954
28374
  const format = outputFormat(this);
27955
28375
  const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
27956
28376
  if (opts.force) {
27957
- await client.request("DELETE", path36(true));
28377
+ await client.request("DELETE", path35(true));
27958
28378
  revoked(true);
27959
28379
  return;
27960
28380
  }
27961
28381
  try {
27962
- await client.request("DELETE", path36(false));
28382
+ await client.request("DELETE", path35(false));
27963
28383
  revoked(false);
27964
28384
  } catch (err) {
27965
28385
  if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
@@ -27974,7 +28394,7 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
27974
28394
  console.error("Aborted.");
27975
28395
  return process.exit(1);
27976
28396
  }
27977
- await client.request("DELETE", path36(true));
28397
+ await client.request("DELETE", path35(true));
27978
28398
  revoked(true);
27979
28399
  }
27980
28400
  });
@@ -28122,10 +28542,9 @@ var init_base_tags = __esm({
28122
28542
 
28123
28543
  // src/data/commands/bases.ts
28124
28544
  import { Command as Command10 } from "commander";
28125
- import * as path35 from "path";
28126
- function pageOf(path36, cursor) {
28127
- if (!cursor) return path36;
28128
- return `${path36}${path36.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
28545
+ function pageOf(path35, cursor) {
28546
+ if (!cursor) return path35;
28547
+ return `${path35}${path35.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
28129
28548
  }
28130
28549
  function parseEnum(flag, value, allowed) {
28131
28550
  if (value === void 0) return void 0;
@@ -28282,9 +28701,9 @@ function buildBasesCommand() {
28282
28701
  });
28283
28702
  bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
28284
28703
  const client = await createDataClient();
28285
- const path36 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
28704
+ const path35 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
28286
28705
  printOutput(
28287
- await client.collectPages((cursor) => pageOf(path36, cursor)),
28706
+ await client.collectPages((cursor) => pageOf(path35, cursor)),
28288
28707
  outputFormat(this)
28289
28708
  );
28290
28709
  });
@@ -28331,45 +28750,52 @@ function buildBasesCommand() {
28331
28750
  });
28332
28751
  bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
28333
28752
  const client = await createDataClient();
28334
- const path36 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
28753
+ const path35 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
28335
28754
  printOutput(
28336
- await client.collectPages((cursor) => pageOf(path36, cursor)),
28755
+ await client.collectPages((cursor) => pageOf(path35, cursor)),
28337
28756
  outputFormat(this)
28338
28757
  );
28339
28758
  });
28340
28759
  bases.command("use <base>").description(
28341
- "Bind this worktree to a base so push/pull refuse to run against a different one"
28760
+ "Deprecated alias of `wayai use bases/<id>` \u2014 scope this worktree to a base"
28342
28761
  ).action(async function(selector) {
28343
- const baseId = resolveSelectorToBaseId(selector);
28344
- const previous = readBaseBinding();
28345
- if (previous !== baseId) {
28762
+ const baseId = resolveBaseSelectorToId(findGitRoot(), selector);
28763
+ const previous = readScope().bases;
28764
+ const changed = previous.length !== 1 || previous[0] !== baseId;
28765
+ if (changed) {
28346
28766
  try {
28347
- writeBaseBinding(baseId);
28767
+ setScopeAxis("bases", [baseId]);
28348
28768
  } catch (err) {
28349
28769
  throw expected(err instanceof Error ? err.message : String(err));
28350
28770
  }
28351
28771
  }
28352
28772
  if (outputFormat(this) === "json") {
28353
- printOutput({ base_id: baseId, previous_base_id: previous, changed: previous !== baseId }, "json");
28773
+ printOutput(
28774
+ { base_id: baseId, previous_base_id: previous[0] ?? null, changed },
28775
+ "json"
28776
+ );
28354
28777
  return;
28355
28778
  }
28356
- if (previous === baseId) {
28779
+ if (!changed) {
28357
28780
  console.log(`Worktree already bound to base ${baseId}.`);
28358
28781
  } else {
28359
28782
  console.log(
28360
- previous ? `Worktree rebound: ${previous} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
28783
+ previous.length > 0 ? `Worktree rebound: ${previous.join(", ")} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
28361
28784
  );
28362
28785
  }
28363
28786
  });
28364
- bases.command("unbind").description("Clear the base binding for this worktree").action(async function() {
28365
- const previous = readBaseBinding();
28366
- if (previous) clearBaseBinding();
28787
+ bases.command("unbind").description("Deprecated alias of `wayai unbind bases/<id>` \u2014 clear the base scope").action(async function() {
28788
+ const previous = readScope().bases;
28789
+ if (previous.length > 0) setScopeAxis("bases", []);
28367
28790
  if (outputFormat(this) === "json") {
28368
- printOutput({ previous_base_id: previous, cleared: previous !== null }, "json");
28791
+ printOutput(
28792
+ { previous_base_id: previous[0] ?? null, cleared: previous.length > 0 },
28793
+ "json"
28794
+ );
28369
28795
  return;
28370
28796
  }
28371
28797
  console.log(
28372
- previous ? `Worktree unbound from base (was: ${previous}).` : "No base binding to clear."
28798
+ previous.length > 0 ? `Worktree unbound from base (was: ${previous.join(", ")}).` : "No base binding to clear."
28373
28799
  );
28374
28800
  });
28375
28801
  bases.addCommand(buildBasesTokensCommand());
@@ -28381,13 +28807,6 @@ function buildBasesCommand() {
28381
28807
  bases.addCommand(buildBasesReportCommand());
28382
28808
  return bases;
28383
28809
  }
28384
- function resolveSelectorToBaseId(selector) {
28385
- const gitRoot = findGitRoot();
28386
- if (!gitRoot || selector.includes("/") || selector.includes(path35.sep)) return selector;
28387
- if (!isPathSafeId(selector)) return selector;
28388
- const id = readBaseMeta(path35.join(resolveBasesDir(gitRoot), selector))?.base_id;
28389
- return typeof id === "string" && isPathSafeId(id) ? id : selector;
28390
- }
28391
28810
  var init_bases = __esm({
28392
28811
  "src/data/commands/bases.ts"() {
28393
28812
  "use strict";
@@ -28405,10 +28824,8 @@ var init_bases = __esm({
28405
28824
  init_utils();
28406
28825
  init_terminal_output();
28407
28826
  init_workspace();
28408
- init_layout();
28409
28827
  init_base_workspace();
28410
- init_base_binding();
28411
- init_base_id();
28828
+ init_worktree_scope();
28412
28829
  init_expected();
28413
28830
  }
28414
28831
  });
@@ -28473,7 +28890,7 @@ var init_file_types = __esm({
28473
28890
 
28474
28891
  // src/data/commands/files.ts
28475
28892
  import { Command as Command12 } from "commander";
28476
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
28893
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "fs";
28477
28894
  import { basename as basename18 } from "path";
28478
28895
  function renderFileDiff(fileType, filePath, from, to, d) {
28479
28896
  console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
@@ -28522,7 +28939,7 @@ function buildFilesCommand() {
28522
28939
  "Upload a local file to a path (e.g. wayai files put reports q3/summary.pdf --file ./summary.pdf)"
28523
28940
  ).requiredOption("--file <local>", "Local file to upload").option("--content-type <type>", "MIME type", "application/octet-stream").action(async function(fileType, filePath, opts) {
28524
28941
  const base = pathSegment(requireBase(this), "--base");
28525
- const body = readFileSync25(opts.file);
28942
+ const body = readFileSync24(opts.file);
28526
28943
  const client = await createDataClient();
28527
28944
  printOutput(
28528
28945
  await client.upload(
@@ -28882,8 +29299,8 @@ function buildRecordsCommand() {
28882
29299
  const body = { data: parseData(opts.data) };
28883
29300
  if (opts.externalId) body.external_id = opts.externalId;
28884
29301
  if (opts.externalSource) body.external_source = opts.externalSource;
28885
- const path36 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
28886
- printOutput(await client.request("PUT", path36, body), outputFormat(this));
29302
+ const path35 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
29303
+ printOutput(await client.request("PUT", path35, body), outputFormat(this));
28887
29304
  });
28888
29305
  records.command("query <record_type>").description("List/search records: exact filters + fuzzy `search`, sorting, pagination").option(
28889
29306
  "--external-source <source>",
@@ -28918,9 +29335,9 @@ function buildRecordsCommand() {
28918
29335
  ).action(async function(recordType2, id, opts) {
28919
29336
  const base = pathSegment(requireBase(this), "--base");
28920
29337
  const client = await createDataClient();
28921
- let path36 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
28922
- if (opts.externalSource) path36 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
28923
- printOutput(await client.request("GET", path36), outputFormat(this));
29338
+ let path35 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
29339
+ if (opts.externalSource) path35 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
29340
+ printOutput(await client.request("GET", path35), outputFormat(this));
28924
29341
  });
28925
29342
  records.command("delete <record_type> <id>").description(
28926
29343
  "Delete a record by internal ID, or by external_id (pass --external-source to scope it)"
@@ -28934,11 +29351,11 @@ function buildRecordsCommand() {
28934
29351
  }
28935
29352
  const base = pathSegment(requireBase(this), "--base");
28936
29353
  const client = await createDataClient();
28937
- let path36 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
29354
+ let path35 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
28938
29355
  if (opts.externalSource) {
28939
- path36 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
29356
+ path35 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
28940
29357
  }
28941
- await client.request("DELETE", path36);
29358
+ await client.request("DELETE", path35);
28942
29359
  console.log("Deleted");
28943
29360
  });
28944
29361
  records.command("history <id>").description(
@@ -29103,8 +29520,8 @@ function buildRelationshipsCommand() {
29103
29520
  if (opts.data) body.data = parseData(opts.data);
29104
29521
  const base = pathSegment(requireBase(this), "--base");
29105
29522
  const client = await createDataClient();
29106
- const path36 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
29107
- printOutput(await client.request("PUT", path36, body), outputFormat(this));
29523
+ const path35 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
29524
+ printOutput(await client.request("PUT", path35, body), outputFormat(this));
29108
29525
  });
29109
29526
  relationships.command("get <id>").description(
29110
29527
  "Get a relationship by ID (or by its external key: pass the external_id with --rel-type)"
@@ -29337,8 +29754,8 @@ function buildToolsetsCommand() {
29337
29754
  toolsets.command("get <slug>").description("Get a toolset").option("--resolved", "Include resolved record_type schemas").action(async function(slug, opts) {
29338
29755
  const base = pathSegment(requireBase(this), "--base");
29339
29756
  const client = await createDataClient();
29340
- const path36 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
29341
- printOutput(await client.request("GET", path36), outputFormat(this));
29757
+ const path35 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
29758
+ printOutput(await client.request("GET", path35), outputFormat(this));
29342
29759
  });
29343
29760
  toolsets.command("list").description("List all toolsets").action(async function() {
29344
29761
  const base = pathSegment(requireBase(this), "--base");
@@ -29483,7 +29900,7 @@ __export(program_exports, {
29483
29900
  });
29484
29901
  import { Command as Command22 } from "commander";
29485
29902
  function withDataGlobals(command2) {
29486
- return command2.option("--org <uuid>", "Organization to operate against (overrides .wayai.yaml)").option("--output <format>", "Output format: json or table", "table").option("--json", "Shorthand for --output json");
29903
+ return command2.option("--org <uuid>", "Organization to operate against (overrides the repo's org binding)").option("--output <format>", "Output format: json or table", "table").option("--json", "Shorthand for --output json");
29487
29904
  }
29488
29905
  function routeErrorsToCli(command2) {
29489
29906
  command2.exitOverride().configureOutput({ outputError: () => {
@@ -29571,9 +29988,9 @@ init_errors2();
29571
29988
  init_mask_secrets();
29572
29989
  init_utils();
29573
29990
  init_registry();
29574
- import { readFileSync as readFileSync26 } from "fs";
29991
+ import { readFileSync as readFileSync25 } from "fs";
29575
29992
  import { fileURLToPath as fileURLToPath3 } from "url";
29576
- import { dirname as dirname12, join as join34 } from "path";
29993
+ import { dirname as dirname13, join as join32 } from "path";
29577
29994
 
29578
29995
  // src/lib/version-refresh.ts
29579
29996
  init_version_cache();
@@ -29581,7 +29998,7 @@ init_skill_version();
29581
29998
  import { exec } from "child_process";
29582
29999
  var REFRESH_TIMEOUT_MS = 1e4;
29583
30000
  function refreshCliCache() {
29584
- return new Promise((resolve7) => {
30001
+ return new Promise((resolve8) => {
29585
30002
  exec("npm view @wayai/cli version", { timeout: REFRESH_TIMEOUT_MS }, (err, stdout) => {
29586
30003
  if (!err) {
29587
30004
  const latest = stdout.trim();
@@ -29592,7 +30009,7 @@ function refreshCliCache() {
29592
30009
  }
29593
30010
  }
29594
30011
  }
29595
- resolve7();
30012
+ resolve8();
29596
30013
  });
29597
30014
  });
29598
30015
  }
@@ -29618,8 +30035,8 @@ async function refreshSkillCache() {
29618
30035
  }
29619
30036
  async function refreshAdminSkillCache() {
29620
30037
  let timer;
29621
- const deadline = new Promise((resolve7) => {
29622
- timer = setTimeout(resolve7, REFRESH_TIMEOUT_MS);
30038
+ const deadline = new Promise((resolve8) => {
30039
+ timer = setTimeout(resolve8, REFRESH_TIMEOUT_MS);
29623
30040
  });
29624
30041
  await Promise.race([deadline, fetchAndCacheAdminSkillVersion().catch(() => {
29625
30042
  })]);
@@ -29728,8 +30145,8 @@ Run \`wayai admin skill install\` to update.`);
29728
30145
  }
29729
30146
 
29730
30147
  // src/index.ts
29731
- var __dirname = dirname12(fileURLToPath3(import.meta.url));
29732
- var pkg = JSON.parse(readFileSync26(join34(__dirname, "..", "package.json"), "utf-8"));
30148
+ var __dirname = dirname13(fileURLToPath3(import.meta.url));
30149
+ var pkg = JSON.parse(readFileSync25(join32(__dirname, "..", "package.json"), "utf-8"));
29733
30150
  var [, , command, ...args] = process.argv;
29734
30151
  var isBackgroundRefresh = command === REFRESH_COMMAND;
29735
30152
  if (!isBackgroundRefresh) initSentry(command, pkg.version);
@@ -29964,7 +30381,7 @@ Commands:
29964
30381
  logout Clear stored credentials
29965
30382
  status Show current auth and config status
29966
30383
  whoami Show the authenticated identity (supports --json)
29967
- init Set up .wayai.yaml (pick organization)
30384
+ init Scope this repo to an organization (pick one)
29968
30385
  create-credential Create an organization credential (API key, token, etc.)
29969
30386
  update-credential Update / rotate an organization credential (by name)
29970
30387
  set-connection-credential Set a connection's credential directly (org link or --field/--stdin), preview or production
@@ -29978,9 +30395,9 @@ Commands:
29978
30395
  sync Alias of \`publish\` (not to be confused with sync-skills / sync-mcp)
29979
30396
  org create Create a new organization (you become its owner)
29980
30397
  org <pull|push|diff> Sync org-scoped resources as code (wayai-ws/org/)
29981
- use <hub> Bind this worktree to a specific hub (UUID or folder name)
30398
+ use [--add] <sel> Scope this worktree to a hub or base (UUID, folder name, hubs/<n>, bases/<n>; --add widens)
29982
30399
  migrate Move a legacy repo to the wayai-ws/{hubs,org} layout
29983
- unbind Clear the worktree hub binding
30400
+ unbind [<sel>] Drop one entry from the worktree scope, or clear the whole scope
29984
30401
  send-message Send a test message to a hub (preview or production; -f <path> to attach a file)
29985
30402
  conversations List or inspect conversations
29986
30403
  alerts Show a hub's active connection/credential alerts (run this FIRST when a hub misbehaves)
@@ -30002,8 +30419,8 @@ Commands:
30002
30419
  Data \u2014 one namespace per entity. Run \`wayai <namespace> --help\` for its tree.
30003
30420
  ${await dataHelpBlock()}
30004
30421
  bases promote Promote a preview base to production (distinct from \`publish\`, which promotes a hub)
30005
- bases use <base> Bind this worktree to a base (\`wayai use\` binds a hub)
30006
- bases unbind Clear this worktree's base binding
30422
+ bases use <base> Deprecated alias of \`wayai use bases/<id>\`
30423
+ bases unbind Deprecated alias of \`wayai unbind bases/<id>\` (clears the base axis)
30007
30424
 
30008
30425
  Flags:
30009
30426
  --yes, -y Skip confirmation prompts (useful for CI and scripting)
@@ -30047,7 +30464,7 @@ Flags:
30047
30464
  --version, -v Show CLI version
30048
30465
 
30049
30466
  Org-scoped mode:
30050
- Each repository is scoped to a single organization via .wayai.yaml
30467
+ Each repository is scoped to a single organization via wayai-ws/wayai.yaml
30051
30468
  (only \`organization_id\` is required). Hubs self-identify via \`hub.yaml\`
30052
30469
  inside their folder under \`workspace/\`.
30053
30470
 
@@ -30055,12 +30472,15 @@ Org-scoped mode:
30055
30472
  pull/push operate on the single hub in the workspace, or pass
30056
30473
  \`--hub <uuid|name>\` to disambiguate when multiple hubs exist.
30057
30474
 
30058
- Worktree hub binding:
30059
- Each git checkout (main or linked worktree) can be bound to a single hub.
30060
- push/pull refuse to run against a different hub once bound. The binding is
30061
- auto-set on first successful pull (or new-hub creation) into an unbound
30062
- checkout. Use \`wayai use <hub>\` to bind manually and \`wayai unbind\` to clear.
30063
- The binding lives at \`<git-dir>/wayai-binding\` and is naturally per-checkout.
30475
+ Worktree scope:
30476
+ Each git checkout (main or linked worktree) carries a scope: a set of hubs and
30477
+ a set of bases, at \`<git-dir>/wayai-scope\` \u2014 per-checkout, never tracked.
30478
+ push/pull/publish refuse to run against anything outside a non-empty set. Each
30479
+ axis is seeded by the first successful pull/push/create into a checkout that
30480
+ has none, and only widens on purpose: \`wayai use --add <sel>\` adds one,
30481
+ \`wayai use <sel>\` replaces that axis, \`wayai unbind <sel>\` drops one entry and
30482
+ bare \`wayai unbind\` clears everything. It is a routing tripwire, not a lock
30483
+ and not authorization.
30064
30484
 
30065
30485
  Examples:
30066
30486
  wayai login