superbee 0.4.2-pre.1 → 0.4.2-pre.2

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/superbee.mjs CHANGED
@@ -44,7 +44,7 @@ var __toESM = (mod, isNodeMode, target3) => (target3 = mod != null ? __create(__
44
44
  var define_SUPERBEE_BUILD_IDENTITY_default;
45
45
  var init_define_SUPERBEE_BUILD_IDENTITY = __esm({
46
46
  "<define:__SUPERBEE_BUILD_IDENTITY__>"() {
47
- define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.4.2-pre.1" }, source: { commit: "04faac2c7e20ffa33e5738fc471e941484303ce8", dirty: false }, artifact: { channel: "npm-package" }, compatibility_contracts: { skill: 1, hook: 1, mcp: 1 } };
47
+ define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.4.2-pre.2" }, source: { commit: "baa7ac9c51c8c91ae3a17579f19910dddd6a406e", dirty: false }, artifact: { channel: "npm-package" }, compatibility_contracts: { skill: 1, hook: 1, mcp: 1 } };
48
48
  }
49
49
  });
50
50
 
@@ -9475,6 +9475,26 @@ var init_mutation = __esm({
9475
9475
  }
9476
9476
  });
9477
9477
 
9478
+ // ../core/src/managed-document-metadata.ts
9479
+ function isManagedDocumentMetadataField(field) {
9480
+ return MANAGED_DOCUMENT_METADATA_FIELDS.has(field);
9481
+ }
9482
+ var MANAGED_DOCUMENT_METADATA_FIELDS;
9483
+ var init_managed_document_metadata = __esm({
9484
+ "../core/src/managed-document-metadata.ts"() {
9485
+ "use strict";
9486
+ init_define_SUPERBEE_BUILD_IDENTITY();
9487
+ init_define_SUPERBEE_UPDATE_POLICY();
9488
+ MANAGED_DOCUMENT_METADATA_FIELDS = /* @__PURE__ */ new Set([
9489
+ "generated",
9490
+ "verified",
9491
+ "superbee_updated_by",
9492
+ "actor",
9493
+ "timestamp"
9494
+ ]);
9495
+ }
9496
+ });
9497
+
9478
9498
  // ../core/src/kinds.ts
9479
9499
  function isConventionId(id) {
9480
9500
  return id.startsWith(CONVENTIONS_PREFIX);
@@ -9616,18 +9636,32 @@ function projectKindForAuthoring(okfVersion, kind2) {
9616
9636
  }
9617
9637
  };
9618
9638
  }
9639
+ function isKindFieldValidationWarning(warning) {
9640
+ return warning.code === "KIND_FIELD_MISSING" || warning.code === "KIND_FIELD_VALUE" || warning.code === "KIND_FIELD_ARITY";
9641
+ }
9642
+ function kindFieldWarningPrefix(code3, kind2, field) {
9643
+ switch (code3) {
9644
+ case "KIND_FIELD_MISSING":
9645
+ return `'${kind2.governs}' requires a non-empty '${field}' field `;
9646
+ case "KIND_FIELD_VALUE":
9647
+ return `'${field}' value `;
9648
+ case "KIND_FIELD_ARITY":
9649
+ return `'${field}' is enum-restricted and takes exactly ONE value for '${kind2.governs}'; `;
9650
+ }
9651
+ }
9619
9652
  function projectKindValidationWarnings(okfVersion, kind2, warnings) {
9620
9653
  const progress = progressStatusCoordinate(okfVersion, kind2);
9621
9654
  if (!progress) return warnings;
9622
- const stored = `'${progress.storageField}'`;
9623
- const logical = `'${progress.logicalField}'`;
9624
- return warnings.map((warning) => warning.field === progress.storageField ? {
9625
- ...warning,
9626
- field: progress.logicalField,
9627
- // Core's Kind warnings name the field first. Replace that one coordinate only; a later
9628
- // occurrence may be the user's literal value and must remain byte-truthful.
9629
- message: warning.message.replace(stored, logical)
9630
- } : warning);
9655
+ return warnings.map((warning) => {
9656
+ if (!isKindFieldValidationWarning(warning) || warning.field !== progress.storageField) return warning;
9657
+ const storedPrefix = kindFieldWarningPrefix(warning.code, kind2, progress.storageField);
9658
+ const logicalPrefix = kindFieldWarningPrefix(warning.code, kind2, progress.logicalField);
9659
+ return {
9660
+ ...warning,
9661
+ field: progress.logicalField,
9662
+ message: warning.message.startsWith(storedPrefix) ? logicalPrefix + warning.message.slice(storedPrefix.length) : warning.message
9663
+ };
9664
+ });
9631
9665
  }
9632
9666
  function isPlainObject2(value) {
9633
9667
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -10111,7 +10145,7 @@ function validateAgainstKind(doc2, kind2) {
10111
10145
  if (!hasOwn2(fm, field) || !isPresent(fm[field])) {
10112
10146
  warnings.push({
10113
10147
  code: "KIND_FIELD_MISSING",
10114
- message: `'${kind2.governs}' requires a non-empty '${field}' field (declared by ${kind2.id}).`,
10148
+ message: kindFieldWarningPrefix("KIND_FIELD_MISSING", kind2, field) + `(declared by ${kind2.id}).`,
10115
10149
  field,
10116
10150
  severity: "warning"
10117
10151
  });
@@ -10124,7 +10158,7 @@ function validateAgainstKind(doc2, kind2) {
10124
10158
  if (Array.isArray(raw)) {
10125
10159
  warnings.push({
10126
10160
  code: "KIND_FIELD_ARITY",
10127
- message: `'${field}' is enum-restricted and takes exactly ONE value for '${kind2.governs}'; got ${raw.length} (${raw.map((v2) => String(v2)).join(", ")}).`,
10161
+ message: kindFieldWarningPrefix("KIND_FIELD_ARITY", kind2, field) + `got ${raw.length} (${raw.map((v2) => String(v2)).join(", ")}).`,
10128
10162
  field,
10129
10163
  severity: "warning"
10130
10164
  });
@@ -10135,7 +10169,7 @@ function validateAgainstKind(doc2, kind2) {
10135
10169
  if (!allowedStrs.includes(v2)) {
10136
10170
  warnings.push({
10137
10171
  code: "KIND_FIELD_VALUE",
10138
- message: `'${field}' value '${v2}' is not one of the allowed values for '${kind2.governs}': ${allowedStrs.join(", ")}.`,
10172
+ message: kindFieldWarningPrefix("KIND_FIELD_VALUE", kind2, field) + `'${v2}' is not one of the allowed values for '${kind2.governs}': ${allowedStrs.join(", ")}.`,
10139
10173
  field,
10140
10174
  severity: "warning"
10141
10175
  });
@@ -10295,7 +10329,7 @@ function assertOrdinaryPatch(existing, proposed) {
10295
10329
  }
10296
10330
  }
10297
10331
  function assertAssignmentName(field) {
10298
- if (managed.has(field)) throw new InvalidInputError(`'${field}' is managed metadata and cannot be assigned by a field action.`);
10332
+ if (isManagedDocumentMetadataField(field)) throw new InvalidInputError(`'${field}' is managed metadata and cannot be assigned by a field action.`);
10299
10333
  if (field === "tags" || field === "sources") throw new InvalidInputError(`'${field}' requires an explicit collection add/remove/replace-all action.`);
10300
10334
  }
10301
10335
  function prepareDocumentAssignments(existing, assignments) {
@@ -10369,7 +10403,7 @@ function prepareDocumentFieldAction(existing, action, context) {
10369
10403
  ...context.okfVersion === "0.2" ? v02 : [],
10370
10404
  ...kind2 ? kindInputFieldNames(context.okfVersion, kind2) : []
10371
10405
  ])].filter((field) => {
10372
- if (managed.has(field) || field === "tags" || field === "sources") return false;
10406
+ if (isManagedDocumentMetadataField(field) || field === "tags" || field === "sources") return false;
10373
10407
  if (context.okfVersion === "0.1" && (field === "stale_after" || field === "usage_window")) return false;
10374
10408
  const storage = kind2 && resolveKindFieldCoordinate(context.okfVersion, kind2, field);
10375
10409
  return !containsCollection(existing.frontmatter[storage?.storageField ?? field]);
@@ -10512,12 +10546,13 @@ function prepareDocumentFieldAction(existing, action, context) {
10512
10546
  if (scope.outcome !== "unchanged" && context.okfVersion === "0.1") candidate.frontmatter.timestamp = (context.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))();
10513
10547
  return { candidate, scope, storageField };
10514
10548
  }
10515
- var FieldActionError, managed, standard, v02;
10549
+ var FieldActionError, standard, v02;
10516
10550
  var init_document_field_actions = __esm({
10517
10551
  "../core/src/document-field-actions.ts"() {
10518
10552
  "use strict";
10519
10553
  init_define_SUPERBEE_BUILD_IDENTITY();
10520
10554
  init_define_SUPERBEE_UPDATE_POLICY();
10555
+ init_managed_document_metadata();
10521
10556
  init_errors();
10522
10557
  init_okf_authored_values();
10523
10558
  init_kinds();
@@ -10530,7 +10565,6 @@ var init_document_field_actions = __esm({
10530
10565
  this.details = details;
10531
10566
  }
10532
10567
  };
10533
- managed = /* @__PURE__ */ new Set(["generated", "verified", "superbee_updated_by", "actor", "timestamp"]);
10534
10568
  standard = /* @__PURE__ */ new Set(["title", "description", "type", "resource"]);
10535
10569
  v02 = /* @__PURE__ */ new Set(["status", "stale_after", "usage_window"]);
10536
10570
  }
@@ -10801,6 +10835,14 @@ function validateCandidate(id, candidate, registry2, strict, okfVersion, now, ex
10801
10835
  }
10802
10836
  return result3;
10803
10837
  }
10838
+ function validateUnchangedDocument(existing, registry2, strict, okfVersion) {
10839
+ const kind2 = registry2.kinds.get(String(existing.frontmatter.type));
10840
+ const warnings = kind2 ? validateAgainstKind(existing, kind2) : [];
10841
+ if (strict && kind2 && warnings.length > 0) {
10842
+ throw new KindConformanceError(existing.id, kind2.governs, warnings, okfVersion);
10843
+ }
10844
+ return warnings;
10845
+ }
10804
10846
  function withV02Metadata(candidate, existing, okfVersion, registry2, now, seedGenerationClock, actor, compareTimestamp, producer) {
10805
10847
  if (okfVersion !== "0.2") return candidate;
10806
10848
  const kind2 = registry2.kinds.get(String(candidate.frontmatter.type));
@@ -10848,7 +10890,7 @@ function prepareDocumentMutationCandidate(existing, rawCandidate, opts) {
10848
10890
  opts.okfVersion === "0.2" && persistActor && opts.actor !== void 0,
10849
10891
  opts.registry.kinds.get(String(comparison.frontmatter.type))?.fields.required.includes("actor") ?? false
10850
10892
  )) {
10851
- return { candidate: { frontmatter: structuredClone(existing.frontmatter), body: existing.body }, changed: false, warnings: [] };
10893
+ return { candidate: { frontmatter: structuredClone(existing.frontmatter), body: existing.body }, changed: false, warnings: validateUnchangedDocument(existing, opts.registry, opts.strict, opts.okfVersion) };
10852
10894
  }
10853
10895
  const candidate = attributeCandidate(comparison, opts.actor, persistActor, opts.okfVersion, opts.registry);
10854
10896
  const { warnings } = validateCandidate(opts.id, candidate, opts.registry, opts.strict, opts.okfVersion, decisionNow, existing);
@@ -10992,6 +11034,17 @@ async function mutateDocument(opts) {
10992
11034
  okfVersion,
10993
11035
  opts.registry
10994
11036
  );
11037
+ if (existing && isNoopMutation(
11038
+ existing,
11039
+ candidate,
11040
+ compareTimestamp,
11041
+ okfVersion,
11042
+ persistActor && opts.actor !== void 0,
11043
+ opts.registry.kinds.get(String(candidate.frontmatter.type))?.fields.required.includes("actor") ?? false
11044
+ )) {
11045
+ warnings = validateUnchangedDocument(existing, opts.registry, opts.strict, okfVersion);
11046
+ return { action: "done", result: { doc: existing } };
11047
+ }
10995
11048
  const validated = validateCandidate(
10996
11049
  opts.id,
10997
11050
  candidate,
@@ -11005,16 +11058,6 @@ async function mutateDocument(opts) {
11005
11058
  if (!opts.strict && existing && warnings.length > 0 && conforms(existing, opts.registry)) {
11006
11059
  throw new KindConformanceError(opts.id, validated.kind.governs, warnings, okfVersion);
11007
11060
  }
11008
- if (existing && isNoopMutation(
11009
- existing,
11010
- candidate,
11011
- compareTimestamp,
11012
- okfVersion,
11013
- persistActor && opts.actor !== void 0,
11014
- validated.kind?.fields.required.includes("actor") ?? false
11015
- )) {
11016
- return { action: "done", result: { doc: existing } };
11017
- }
11018
11061
  return { action: "write", next: { id: opts.id, ...candidate }, result: {} };
11019
11062
  },
11020
11063
  write: async (next, expectedVersion) => {
@@ -11056,7 +11099,7 @@ async function mutateDocument(opts) {
11056
11099
  compareTimestamp,
11057
11100
  seedGenerationClock: seedClock
11058
11101
  });
11059
- if (!changed) return { action: "done", result: { doc: existing, warnings: [] } };
11102
+ if (!changed) return { action: "done", result: { doc: existing, warnings } };
11060
11103
  return { action: "write", next: { id: opts.id, ...candidate }, result: { warnings } };
11061
11104
  },
11062
11105
  write: async (next, expectedVersion) => {
@@ -11070,7 +11113,7 @@ async function mutateDocument(opts) {
11070
11113
  maxAttempts: hardCas ? 1 : maxAttempts
11071
11114
  });
11072
11115
  const scoped = { ...scope ? { scope } : {}, ...scopes ? { scopes } : {} };
11073
- return outcome.wrote ? { doc: savedDoc, changed: true, version: outcome.version, warnings: outcome.result.warnings, ...scoped } : { doc: outcome.result.doc, changed: false, version: outcome.version, warnings: [], ...scoped };
11116
+ return outcome.wrote ? { doc: savedDoc, changed: true, version: outcome.version, warnings: outcome.result.warnings, ...scoped } : { doc: outcome.result.doc, changed: false, version: outcome.version, warnings: outcome.result.warnings, ...scoped };
11074
11117
  }
11075
11118
  var DEFAULT_MAX_ATTEMPTS, KindConformanceError, DocumentNotFoundError;
11076
11119
  var init_document_mutation = __esm({
@@ -11727,6 +11770,7 @@ var init_src = __esm({
11727
11770
  init_document_field_actions();
11728
11771
  init_document_mutation();
11729
11772
  init_kind_field_mutation();
11773
+ init_managed_document_metadata();
11730
11774
  }
11731
11775
  });
11732
11776
 
@@ -11749,6 +11793,16 @@ function hasRejectedReason(text5, reasons) {
11749
11793
  }
11750
11794
  return false;
11751
11795
  }
11796
+ function hasLostRefLock(text5) {
11797
+ for (const line2 of text5.split("\n")) {
11798
+ const lower = line2.toLowerCase();
11799
+ const remote = lower.indexOf("[remote rejected]");
11800
+ if (remote !== -1 && lower.indexOf("(incorrect old value provided)", remote) !== -1) return true;
11801
+ const at = lower.indexOf("cannot lock ref");
11802
+ if (at !== -1 && lower.indexOf("but expected", at) !== -1) return true;
11803
+ }
11804
+ return false;
11805
+ }
11752
11806
  function hasUnmergeableOriginRef(text5) {
11753
11807
  const suffix = " - not something we can merge";
11754
11808
  for (const line2 of text5.split("\n")) {
@@ -11790,7 +11844,7 @@ ${f2.stdout}`;
11790
11844
  { details: { op: op2, retryable: true } }
11791
11845
  );
11792
11846
  }
11793
- if (op2 === "push" && (hasRejectedReason(text5, ["fetch first", "non-fast-forward"]) || /Updates were rejected because (?:the remote contains work|the tip of your current branch is behind)/i.test(text5))) {
11847
+ if (op2 === "push" && (hasRejectedReason(text5, ["fetch first", "non-fast-forward"]) || /Updates were rejected because (?:the remote contains work|the tip of your current branch is behind)/i.test(text5) || hasLostRefLock(text5))) {
11794
11848
  return new BoardGitError(
11795
11849
  "TRANSIENT",
11796
11850
  "a teammate pushed to the board at the same time \u2014 re-run sync to incorporate their changes and retry",
@@ -11930,6 +11984,68 @@ import {
11930
11984
  } from "node:fs";
11931
11985
  import { tmpdir } from "node:os";
11932
11986
  import path6 from "node:path";
11987
+ function isBoardBranchName(name) {
11988
+ return name === BOARD_BRANCH || name.length <= 100 && NAMED_BOARD_BRANCH.test(name) && !RESERVED_BOARD_LIKE_BRANCHES.has(name);
11989
+ }
11990
+ function declaredBoardBranchAtRef(dir, ref) {
11991
+ const entry = runGit(dir, ["ls-tree", "-z", ref, "--", BOARD_MARKER_FILE]);
11992
+ if (entry.status !== 0 || !/^100644 blob [0-9a-f]+\t\.superbee-board\.json\0$/.test(entry.stdout)) return null;
11993
+ const size = runGit(dir, ["cat-file", "-s", `${ref}:${BOARD_MARKER_FILE}`]);
11994
+ if (size.status !== 0 || Number(size.stdout.trim()) > BOARD_MARKER_BYTES) return null;
11995
+ const shown2 = runGit(dir, ["show", `${ref}:${BOARD_MARKER_FILE}`]);
11996
+ if (shown2.status !== 0) return null;
11997
+ try {
11998
+ const parsed2 = JSON.parse(shown2.stdout);
11999
+ if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2)) return null;
12000
+ const { schema, branch } = parsed2;
12001
+ if (schema !== BOARD_MARKER_SCHEMA || typeof branch !== "string") return null;
12002
+ return branch !== BOARD_BRANCH && isBoardBranchName(branch) ? branch : null;
12003
+ } catch {
12004
+ return null;
12005
+ }
12006
+ }
12007
+ function isDeclaredBoardBranch(dir, branch) {
12008
+ if (branch === BOARD_BRANCH) return declaredBoardBranchAtRef(dir, `refs/heads/${BOARD_BRANCH}`) === null;
12009
+ if (!isBoardBranchName(branch)) return false;
12010
+ return declaredBoardBranchAtRef(dir, `refs/heads/${branch}`) === branch;
12011
+ }
12012
+ function namedBoardRecoveryHelp(dir, branch) {
12013
+ const restore = `restore ${BOARD_MARKER_FILE} with {"schema": ${BOARD_MARKER_SCHEMA}, "branch": "${branch}"} in a new commit (git log -- ${BOARD_MARKER_FILE} shows who changed it)`;
12014
+ if (declaredBoardBranchAtRef(dir, `refs/remotes/${BOARD_REMOTE}/${branch}`) === branch) {
12015
+ return `${BOARD_REMOTE}/${branch} still declares the board: git pull --rebase ${BOARD_REMOTE} ${branch} restores ${BOARD_MARKER_FILE} here, then re-run sync`;
12016
+ }
12017
+ return `git fetch ${BOARD_REMOTE} ${branch}; if ${BOARD_REMOTE}/${branch} declares the board again, git pull --rebase ${BOARD_REMOTE} ${branch} restores the marker, otherwise ${restore}; then re-run sync`;
12018
+ }
12019
+ function rebaseStartBranch(dir) {
12020
+ for (const state of ["rebase-merge", "rebase-apply"]) {
12021
+ const headNamePath = path6.join(worktreeGitPath(dir, state), "head-name");
12022
+ if (!existsSync2(headNamePath)) continue;
12023
+ try {
12024
+ const name = readFileSync4(headNamePath, "utf8").trim();
12025
+ if (name.startsWith("refs/heads/")) return name.slice("refs/heads/".length);
12026
+ } catch {
12027
+ }
12028
+ }
12029
+ return null;
12030
+ }
12031
+ function boardBranchOf(boardPath) {
12032
+ const attached = runGit(boardPath, ["symbolic-ref", "-q", "--short", "HEAD"]);
12033
+ const name = attached.status === 0 ? attached.stdout.trim() : rebaseStartBranch(boardPath);
12034
+ if (name === null || name === BOARD_BRANCH || !isBoardBranchName(name)) return BOARD_BRANCH;
12035
+ if (isDeclaredBoardBranch(boardPath, name)) return name;
12036
+ if (!hasTrackedBundleRootAtRef(boardPath, `refs/heads/${name}`)) return BOARD_BRANCH;
12037
+ throw new BoardGitError(
12038
+ "CONFLICT",
12039
+ `this checkout is on the named board branch '${name}', but its tip no longer declares it: ${BOARD_MARKER_FILE} is missing or names another branch \u2014 sync will not guess which board to use`,
12040
+ {
12041
+ details: { path: boardPath, state: "named-board-undeclared", branch: name },
12042
+ help: namedBoardRecoveryHelp(boardPath, name)
12043
+ }
12044
+ );
12045
+ }
12046
+ function boardRefOf(boardPath) {
12047
+ return `${BOARD_REMOTE}/${boardBranchOf(boardPath)}`;
12048
+ }
11933
12049
  function recognizedBundlePath(name, sourcePath) {
11934
12050
  try {
11935
12051
  const link4 = lstatSync(sourcePath);
@@ -12168,28 +12284,32 @@ function hasTrackedBundleRootAtRef(top, ref) {
12168
12284
  function hasTrackedBundleRootAtHead(top) {
12169
12285
  return hasTrackedBundleRootAtRef(top, "HEAD");
12170
12286
  }
12171
- function boardBranchTracksOrigin(top) {
12172
- const remote = runGit(top, ["config", "--get", `branch.${BOARD_BRANCH}.remote`]);
12173
- const merge2 = runGit(top, ["config", "--get", `branch.${BOARD_BRANCH}.merge`]);
12174
- return remote.status === 0 && remote.stdout.trim() === BOARD_REMOTE && merge2.status === 0 && merge2.stdout.trim() === `refs/heads/${BOARD_BRANCH}`;
12287
+ function boardBranchTracksOrigin(top, branch = BOARD_BRANCH) {
12288
+ const remote = runGit(top, ["config", "--get", `branch.${branch}.remote`]);
12289
+ const merge2 = runGit(top, ["config", "--get", `branch.${branch}.merge`]);
12290
+ return remote.status === 0 && remote.stdout.trim() === BOARD_REMOTE && merge2.status === 0 && merge2.stdout.trim() === `refs/heads/${branch}`;
12175
12291
  }
12176
- function standaloneHistoryBase(top) {
12177
- const r2 = runGit(top, ["merge-base", `refs/heads/${BOARD_BRANCH}`, `refs/remotes/${BOARD_REF}`]);
12292
+ function standaloneHistoryBase(top, branch = BOARD_BRANCH) {
12293
+ const r2 = runGit(top, ["merge-base", `refs/heads/${branch}`, `refs/remotes/${BOARD_REMOTE}/${branch}`]);
12178
12294
  return r2.status === 0 && r2.stdout.trim().length > 0 ? r2.stdout.trim() : null;
12179
12295
  }
12180
12296
  function resolveStandaloneBoardCheckout(dir, opts = {}) {
12181
12297
  const top = repoTopLevel(dir);
12182
- if (!top || currentBranch(top) !== BOARD_BRANCH) return null;
12183
- if (!hasTrackedBundleRootAtRef(top, `refs/heads/${BOARD_BRANCH}`) || !boardBranchTracksOrigin(top)) return null;
12184
- const remote = runGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]);
12298
+ if (!top) return null;
12299
+ const branch = currentBranch(top);
12300
+ if (!isDeclaredBoardBranch(top, branch)) return null;
12301
+ if (!hasTrackedBundleRootAtRef(top, `refs/heads/${branch}`) || !boardBranchTracksOrigin(top, branch)) return null;
12302
+ const remote = runGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REMOTE}/${branch}`]);
12185
12303
  if (remote.status !== 0) return opts.requireRemoteRef === false ? top : null;
12186
- return standaloneHistoryBase(top) ? top : null;
12304
+ return standaloneHistoryBase(top, branch) ? top : null;
12187
12305
  }
12188
12306
  function isRecoverableStandaloneBoardCheckout(dir) {
12189
12307
  const top = repoTopLevel(dir);
12190
- if (!top || !detectStaleRebase(top) || !rebaseWasFromBoardBranch(top)) return false;
12191
- if (!hasTrackedBundleRootAtRef(top, `refs/heads/${BOARD_BRANCH}`) || !boardBranchTracksOrigin(top)) return false;
12192
- return standaloneHistoryBase(top) !== null;
12308
+ if (!top || !detectStaleRebase(top)) return false;
12309
+ const branch = rebaseStartBranch(top);
12310
+ if (branch === null || !isDeclaredBoardBranch(top, branch)) return false;
12311
+ if (!hasTrackedBundleRootAtRef(top, `refs/heads/${branch}`) || !boardBranchTracksOrigin(top, branch)) return false;
12312
+ return standaloneHistoryBase(top, branch) !== null;
12193
12313
  }
12194
12314
  function resolveProvisionedBoardPath(dir, hostPolicy) {
12195
12315
  const policy = captureBoardHostPolicy(hostPolicy);
@@ -12200,13 +12320,24 @@ function resolveProvisionedBoardPath(dir, hostPolicy) {
12200
12320
  return conventionalProvisionedBoardPath(top, policy);
12201
12321
  }
12202
12322
  function standaloneRootWrongBranch(top, branch) {
12323
+ const stray = branch === BOARD_BRANCH ? declaredBoardBranchAtRef(top, `refs/heads/${BOARD_BRANCH}`) : null;
12324
+ if (stray !== null) {
12325
+ return new BoardGitError(
12326
+ "CONFLICT",
12327
+ `this '${BOARD_BRANCH}' checkout's tip carries ${BOARD_MARKER_FILE} declaring the named board '${stray}' \u2014 a named board's marker does not belong on the default board, so sync will not publish this tree`,
12328
+ {
12329
+ details: { path: top, state: "default-board-carries-named-marker", branch, declares: stray },
12330
+ help: `if this clone is the named board, rename it back (git branch -m ${stray} and track ${BOARD_REMOTE}/${stray}); otherwise remove the stray marker (git rm ${BOARD_MARKER_FILE} && git commit; git log -- ${BOARD_MARKER_FILE} shows who added it), then re-run sync`
12331
+ }
12332
+ );
12333
+ }
12203
12334
  const shown2 = branch === "HEAD" ? "detached HEAD" : `'${branch}'`;
12204
12335
  return new BoardGitError(
12205
12336
  "CONFLICT",
12206
- `this repository root is an OKF bundle, but it is checked out at ${shown2}; a standalone root checkout may sync only when it is attached to the dedicated '${BOARD_BRANCH}' branch`,
12337
+ `this repository root is an OKF bundle, but it is checked out at ${shown2}; a standalone root checkout may sync only when it is attached to the dedicated '${BOARD_BRANCH}' branch or to a named board branch (board-<name>) whose committed ${BOARD_MARKER_FILE} declares that name`,
12207
12338
  {
12208
12339
  details: { path: top, state: "standalone-board-wrong-branch", branch },
12209
- help: `use a separate checkout attached to '${BOARD_BRANCH}', or run sync from the repository's code checkout and let it provision the conventional bundle worktree`
12340
+ help: branch !== BOARD_BRANCH && isBoardBranchName(branch) ? namedBoardRecoveryHelp(top, branch) : `use a separate checkout attached to '${BOARD_BRANCH}' (or to the named board branch), or run sync from the repository's code checkout and let it provision the conventional bundle worktree`
12210
12341
  }
12211
12342
  );
12212
12343
  }
@@ -12439,11 +12570,13 @@ function provisionBoardWorktree(dir, budget = {}, hostPolicy) {
12439
12570
  if (!top) return { kind: "no_repo" };
12440
12571
  const rootIsBundle = hasTrackedBundleRootAtHead(top);
12441
12572
  const rootBranch = rootIsBundle ? currentBranch(top) : void 0;
12442
- if (rootBranch !== void 0 && rootBranch !== BOARD_BRANCH) {
12573
+ if (rootBranch !== void 0 && !isDeclaredBoardBranch(top, rootBranch)) {
12443
12574
  throw standaloneRootWrongBranch(top, rootBranch);
12444
12575
  }
12445
- const rootOriginBefore = rootBranch === BOARD_BRANCH ? runGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]) : null;
12446
- const rootCheckout = rootBranch === BOARD_BRANCH ? resolveStandaloneBoardCheckout(top, { requireRemoteRef: false }) : null;
12576
+ const branch = rootBranch ?? BOARD_BRANCH;
12577
+ const remoteRef = `refs/remotes/${BOARD_REMOTE}/${branch}`;
12578
+ const rootOriginBefore = rootBranch !== void 0 ? runGit(top, ["rev-parse", "--verify", "--quiet", remoteRef]) : null;
12579
+ const rootCheckout = rootBranch !== void 0 ? resolveStandaloneBoardCheckout(top, { requireRemoteRef: false }) : null;
12447
12580
  const rootCachedBaseline = rootCheckout !== null && rootOriginBefore?.status === 0 ? rootOriginBefore.stdout.trim() : null;
12448
12581
  const bundleDir = bundleDirNameForProject(top);
12449
12582
  const boardPath = path6.join(top, bundleDir);
@@ -12475,13 +12608,13 @@ function provisionBoardWorktree(dir, budget = {}, hostPolicy) {
12475
12608
  "ls-remote",
12476
12609
  "--exit-code",
12477
12610
  BOARD_REMOTE,
12478
- `refs/heads/${BOARD_BRANCH}`
12611
+ `refs/heads/${branch}`
12479
12612
  ]);
12480
12613
  if (probe?.status === 0) {
12481
12614
  const children = runGit(top, [
12482
12615
  "for-each-ref",
12483
12616
  "--format=%(refname)",
12484
- `refs/remotes/${BOARD_REF}/`
12617
+ `${remoteRef}/`
12485
12618
  ]);
12486
12619
  let namespaceReady = children.status === 0;
12487
12620
  for (const child of children.stdout.split("\n").filter(Boolean)) {
@@ -12493,7 +12626,7 @@ function provisionBoardWorktree(dir, budget = {}, hostPolicy) {
12493
12626
  "--prune",
12494
12627
  "--no-tags",
12495
12628
  BOARD_REMOTE,
12496
- `+refs/heads/${BOARD_BRANCH}:refs/remotes/${BOARD_REF}`
12629
+ `+refs/heads/${branch}:${remoteRef}`
12497
12630
  ]);
12498
12631
  remoteState = fetch2?.status === 0 ? "absent" : "unknown";
12499
12632
  liveFetch = fetch2?.status === 0;
@@ -12502,23 +12635,23 @@ function provisionBoardWorktree(dir, budget = {}, hostPolicy) {
12502
12635
  }
12503
12636
  } else if (probe?.status === 2) {
12504
12637
  remoteBoardKnownAbsent = true;
12505
- runGit(top, ["update-ref", "-d", `refs/remotes/${BOARD_REF}`]);
12638
+ runGit(top, ["update-ref", "-d", remoteRef]);
12506
12639
  } else {
12507
12640
  remoteState = "unknown";
12508
12641
  }
12509
12642
  }
12510
- const localBoard = runGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${BOARD_BRANCH}`]);
12511
- const remoteBoard = runGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]);
12643
+ const localBoard = runGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
12644
+ const remoteBoard = runGit(top, ["rev-parse", "--verify", "--quiet", remoteRef]);
12512
12645
  const hasLocal = localBoard.status === 0;
12513
12646
  const hasRemote = remoteBoard.status === 0 && !remoteBoardKnownAbsent;
12514
- if (rootIsBundle && rootBranch === BOARD_BRANCH) {
12647
+ if (rootIsBundle && rootBranch !== void 0) {
12515
12648
  if (remoteState === "unknown" && !liveFetch) {
12516
12649
  return { kind: "no_board", remoteState: "unknown" };
12517
12650
  }
12518
12651
  const standaloneCheckout = hasRemote ? resolveStandaloneBoardCheckout(top) : null;
12519
12652
  if (standaloneCheckout !== null) {
12520
12653
  const fetchedOrigin = remoteBoard.stdout.trim();
12521
- const originBaseline = rootCachedBaseline !== null ? rootCachedBaseline === fetchedOrigin ? null : rootCachedBaseline : standaloneHistoryBase(top);
12654
+ const originBaseline = rootCachedBaseline !== null ? rootCachedBaseline === fetchedOrigin ? null : rootCachedBaseline : standaloneHistoryBase(top, branch);
12522
12655
  return originBaseline === null ? { kind: "already", boardPath: top } : { kind: "already", boardPath: top, originBaseline };
12523
12656
  }
12524
12657
  return { kind: "local_board", boardPath: top, remoteExists: hasRemote };
@@ -12953,13 +13086,14 @@ ${bodyLines2.join("\n")}
12953
13086
  }
12954
13087
  function fetchRebaseResolving(boardPath, exportDir) {
12955
13088
  mustGit(boardPath, ["fetch", "--prune", BOARD_REMOTE], { timeoutMs: NETWORK_TIMEOUT_MS });
12956
- if (runGit(boardPath, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]).status !== 0) {
13089
+ const boardRef = boardRefOf(boardPath);
13090
+ if (runGit(boardPath, ["rev-parse", "--verify", "--quiet", `refs/remotes/${boardRef}`]).status !== 0) {
12957
13091
  return { status: "no_upstream" };
12958
13092
  }
12959
13093
  const idFlags = identityFlags(boardPath);
12960
- const r2 = runGit(boardPath, [...idFlags, "rebase", BOARD_REF], { rebase: true, timeoutMs: NETWORK_TIMEOUT_MS });
13094
+ const r2 = runGit(boardPath, [...idFlags, "rebase", boardRef], { rebase: true, timeoutMs: NETWORK_TIMEOUT_MS });
12961
13095
  if (r2.status === 0) return { status: "clean" };
12962
- if (!detectStaleRebase(boardPath)) throw classifyGitError(failureOf(["rebase", BOARD_REF], r2));
13096
+ if (!detectStaleRebase(boardPath)) throw classifyGitError(failureOf(["rebase", boardRef], r2));
12963
13097
  const listConflicted = () => mustGit(boardPath, ["diff", "--name-only", "-z", "--diff-filter=U"]).split("\0").filter((l) => l.length > 0);
12964
13098
  const byPath = /* @__PURE__ */ new Map();
12965
13099
  try {
@@ -12998,8 +13132,8 @@ function fetchRebaseResolving(boardPath, exportDir) {
12998
13132
  }
12999
13133
  }
13000
13134
  }
13001
- if (runGit(boardPath, ["cat-file", "-e", `refs/remotes/${BOARD_REF}:${relPath}`]).status === 0) {
13002
- mustGit(boardPath, ["checkout", BOARD_REF, "--", relPath]);
13135
+ if (runGit(boardPath, ["cat-file", "-e", `refs/remotes/${boardRef}:${relPath}`]).status === 0) {
13136
+ mustGit(boardPath, ["checkout", boardRef, "--", relPath]);
13003
13137
  mustGit(boardPath, ["add", "--", relPath]);
13004
13138
  } else {
13005
13139
  mustGit(boardPath, ["rm", "-f", "--", relPath]);
@@ -13026,8 +13160,9 @@ function fetchRebaseResolving(boardPath, exportDir) {
13026
13160
  }
13027
13161
  return { status: "resolved", conflicts: [...byPath.values()] };
13028
13162
  }
13029
- function push(boardPath) {
13030
- mustGit(boardPath, ["push", BOARD_REMOTE, BOARD_BRANCH], { timeoutMs: NETWORK_TIMEOUT_MS });
13163
+ function push(boardPath, commit, branch = boardBranchOf(boardPath)) {
13164
+ const refspec = commit === void 0 ? branch : `${commit}:refs/heads/${branch}`;
13165
+ mustGit(boardPath, ["push", BOARD_REMOTE, refspec], { timeoutMs: NETWORK_TIMEOUT_MS });
13031
13166
  }
13032
13167
  function pushBoardUpstream(top) {
13033
13168
  mustGit(top, ["push", "-u", BOARD_REMOTE, BOARD_BRANCH], { timeoutMs: NETWORK_TIMEOUT_MS });
@@ -13121,7 +13256,7 @@ function ffPull(boardPath, budget = {}) {
13121
13256
  if (fetched.status !== 0) {
13122
13257
  fetchReason = swallowReason(classifyGitError(failureOf(["fetch", "--prune"], fetched)));
13123
13258
  }
13124
- const merged = runGit(boardPath, ["merge", "--ff-only", BOARD_REF]);
13259
+ const merged = runGit(boardPath, ["merge", "--ff-only", boardRefOf(boardPath)]);
13125
13260
  if (merged.status !== 0) {
13126
13261
  const text5 = `${merged.stderr}
13127
13262
  ${merged.stdout}`;
@@ -13143,10 +13278,11 @@ ${merged.stdout}`;
13143
13278
  }
13144
13279
  }
13145
13280
  function unpushedCount(boardPath) {
13146
- if (runGit(boardPath, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]).status !== 0) {
13281
+ const boardRef = boardRefOf(boardPath);
13282
+ if (runGit(boardPath, ["rev-parse", "--verify", "--quiet", `refs/remotes/${boardRef}`]).status !== 0) {
13147
13283
  return null;
13148
13284
  }
13149
- const out = mustGit(boardPath, ["rev-list", "--count", `${BOARD_REF}..HEAD`]).trim();
13285
+ const out = mustGit(boardPath, ["rev-list", "--count", `${boardRef}..HEAD`]).trim();
13150
13286
  const n = Number.parseInt(out, 10);
13151
13287
  return Number.isFinite(n) ? n : 0;
13152
13288
  }
@@ -13177,7 +13313,7 @@ function readDocBytesAtRef(dir, ref, relPath) {
13177
13313
  }
13178
13314
  return shown2.stdout;
13179
13315
  }
13180
- var BOARD_BRANCH, BOARD_REMOTE, BOARD_REF, BUNDLE_DIR, LEGACY_BUNDLE_DIR, BUNDLE_DIRS, RELATIVE_WORKTREE_CONFIG, SCRUBBED_GIT_VARS, LOCAL_TIMEOUT_MS, NETWORK_TIMEOUT_MS, IDENTITY_FALLBACK_ACTOR, IGNORABLE_BOARD_DIR_ENTRIES, UNKNOWN2, MAX_REBASE_STOPS, GITIGNORE_ENTRIES;
13316
+ var BOARD_BRANCH, BOARD_REMOTE, BOARD_REF, BOARD_MARKER_FILE, BOARD_MARKER_SCHEMA, BOARD_MARKER_BYTES, NAMED_BOARD_BRANCH, RESERVED_BOARD_LIKE_BRANCHES, BUNDLE_DIR, LEGACY_BUNDLE_DIR, BUNDLE_DIRS, RELATIVE_WORKTREE_CONFIG, SCRUBBED_GIT_VARS, LOCAL_TIMEOUT_MS, NETWORK_TIMEOUT_MS, IDENTITY_FALLBACK_ACTOR, IGNORABLE_BOARD_DIR_ENTRIES, UNKNOWN2, MAX_REBASE_STOPS, GITIGNORE_ENTRIES;
13181
13317
  var init_porcelain = __esm({
13182
13318
  "../board-git/src/porcelain.ts"() {
13183
13319
  "use strict";
@@ -13190,6 +13326,11 @@ var init_porcelain = __esm({
13190
13326
  BOARD_BRANCH = "board";
13191
13327
  BOARD_REMOTE = "origin";
13192
13328
  BOARD_REF = `${BOARD_REMOTE}/${BOARD_BRANCH}`;
13329
+ BOARD_MARKER_FILE = ".superbee-board.json";
13330
+ BOARD_MARKER_SCHEMA = 1;
13331
+ BOARD_MARKER_BYTES = 4 * 1024;
13332
+ NAMED_BOARD_BRANCH = /^board-[a-z0-9]+(?:-[a-z0-9]+)*$/;
13333
+ RESERVED_BOARD_LIKE_BRANCHES = /* @__PURE__ */ new Set(["board-cleanup"]);
13193
13334
  BUNDLE_DIR = ".superbee";
13194
13335
  LEGACY_BUNDLE_DIR = ".agentstate-lite";
13195
13336
  BUNDLE_DIRS = [BUNDLE_DIR, LEGACY_BUNDLE_DIR];
@@ -13223,7 +13364,7 @@ function localBranchExists(top, name) {
13223
13364
  return runGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${name}`]).status === 0;
13224
13365
  }
13225
13366
  function resolveOriginRef(boardPath) {
13226
- const r2 = runGit(boardPath, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]);
13367
+ const r2 = runGit(boardPath, ["rev-parse", "--verify", "--quiet", `refs/remotes/${boardRefOf(boardPath)}`]);
13227
13368
  return r2.status === 0 ? r2.stdout.trim() : null;
13228
13369
  }
13229
13370
  function hasLocalOnlyBundle(dir) {
@@ -13932,7 +14073,10 @@ function hasStandaloneBoardSignature(p2) {
13932
14073
  try {
13933
14074
  if (!statSync3(path10.join(p2, ".git")).isDirectory()) return false;
13934
14075
  if (!statSync3(path10.join(p2, "index.md")).isFile()) return false;
13935
- return readFileSync6(path10.join(p2, ".git", "HEAD"), "utf8").trim() === `ref: refs/heads/${BOARD_BRANCH}`;
14076
+ const head = readFileSync6(path10.join(p2, ".git", "HEAD"), "utf8").trim();
14077
+ if (head === `ref: refs/heads/${BOARD_BRANCH}`) return true;
14078
+ const branch = head.startsWith("ref: refs/heads/") ? head.slice("ref: refs/heads/".length) : "";
14079
+ return isBoardBranchName(branch) && statSync3(path10.join(p2, BOARD_MARKER_FILE)).isFile();
13936
14080
  } catch {
13937
14081
  return false;
13938
14082
  }
@@ -14386,6 +14530,7 @@ var init_errors4 = __esm({
14386
14530
  NO_UPSTREAM: EXIT.RUNTIME,
14387
14531
  GIT_BUSY: EXIT.RUNTIME,
14388
14532
  CONFLICT: EXIT.CONFLICT,
14533
+ GATE_FAILED: EXIT.CONFLICT,
14389
14534
  CREDENTIAL_STORE_UNAVAILABLE: EXIT.RUNTIME
14390
14535
  };
14391
14536
  CliError = class extends Error {
@@ -17189,7 +17334,7 @@ var init_command_spec = __esm({
17189
17334
  id: "sync",
17190
17335
  leaves: [publicLeaf("sync", "sync", zero, 22, DIR_SYNC_SURFACE)],
17191
17336
  usage: "sync [--establish [--yes] | --pull-only | --show-incoming <id> [--out <file> | --body-out <file>] | --inspect --doc <id> [--out <file>] | --resolve keep|take|revise --doc <id> | --restore-deletes | --accept-deletes <token> | --take-host-deletions <token>] [--dir <path>] [--limit <n>]",
17192
- summary: "Share the board branch with a remote \u2014 commits, pulls, and pushes (git tier; --pull-only skips commit+push). The remote repository must already exist: Superbee does not create it. After confirming the repository exists and origin/board does not, --establish is the separate explicit act that creates/pushes the board branch; it needs repository-specific push capability and branch-create policy clearance. If origin/board exists, plain `sync` joins it. Works both from a project's conventional bundle worktree and from a standalone clone whose tracked OKF root is attached to the shared board branch. A bundle folder already committed on the code branch is the same flag's hard case: preview first, --yes executes, and the folder's removal from the code branch rides a prepared side-branch commit you push and open as a PR. A bundle committed with code and NO board branch anywhere is the IN-TREE mode (read-side): full sync refuses (sharing rides your normal commit/push), --pull-only fetches the branch's tracking upstream and reports incoming board docs ('git pull' delivers them), and --establish converts to a dedicated board branch. A doc changed on both sides converges: teammate's version kept, yours exported; --show-incoming <id> (exclusive with --pull-only) prints the incoming version as of the last fetch; --inspect --doc <id> and --resolve keep|take|revise --doc <id> settle that saved conflict with the same verbs as a hosted checkout. Board-reading commands (list/doc read/status/home/link show) auto-run the ff-only pull when board state is >~5m stale \u2014 silent, bounded (~2s), never a push; SUPERBEE_NO_AUTOPULL=<any value, even 0> disables it. Hosted checkout (a folder made by `checkout`): sync instead sends each edited file as one whole document under your own access and refreshes documents changed on the host, with one row per document (committed, conflict, held, refused, unknown, paused; non-zero exit until all are committed). A change to one document and a host change to another both land; a document changed on both sides is never merged but comes back as a conflict, resolved with --inspect --doc <id> then --resolve keep|take|revise --doc <id> (a resolution is recorded, then sent by the next plain sync). A held mass delete is accepted only by the person, typing the count in their own terminal (--accept-deletes); --restore-deletes puts the files back; --take-host-deletions takes a mass deletion the pull refused."
17337
+ summary: "Share the board branch with a remote \u2014 commits, pulls, and pushes (git tier; --pull-only skips commit+push). The remote repository must already exist: Superbee does not create it. After confirming the repository exists and origin/board does not, --establish is the separate explicit act that creates/pushes the board branch; it needs repository-specific push capability and branch-create policy clearance. If origin/board exists, plain `sync` joins it. Works both from a project's conventional bundle worktree and from a standalone clone whose tracked OKF root is attached to the shared board branch; a named board-<name> branch that commits a .superbee-board.json declaring that name is a second board in the same repository, synced from its own standalone clone against origin/board-<name>. A bundle folder already committed on the code branch is the same flag's hard case: preview first, --yes executes, and the folder's removal from the code branch rides a prepared side-branch commit you push and open as a PR. A bundle committed with code and NO board branch anywhere is the IN-TREE mode (read-side): full sync refuses (sharing rides your normal commit/push), --pull-only fetches the branch's tracking upstream and reports incoming board docs ('git pull' delivers them), and --establish converts to a dedicated board branch. A doc changed on both sides converges: teammate's version kept, yours exported; --show-incoming <id> (exclusive with --pull-only) prints the incoming version as of the last fetch; --inspect --doc <id> and --resolve keep|take|revise --doc <id> settle that saved conflict with the same verbs as a hosted checkout. A clone may declare a pre-push gate with git config superbee.syncGate '<command>': it runs on the rebased tree before every push that sends commits, and a failure holds the push (work committed locally, exit 5, code GATE_FAILED). A push that loses a race to another writer re-fetches, rebases, re-gates and retries (up to 5 attempts) and is reported as a race, never as a permission problem. Board-reading commands (list/doc read/status/home/link show) auto-run the ff-only pull when board state is >~5m stale \u2014 silent, bounded (~2s), never a push; SUPERBEE_NO_AUTOPULL=<any value, even 0> disables it. Hosted checkout (a folder made by `checkout`): sync instead sends each edited file as one whole document under your own access and refreshes documents changed on the host, with one row per document (committed, conflict, held, refused, unknown, paused; non-zero exit until all are committed). A change to one document and a host change to another both land; a document changed on both sides is never merged but comes back as a conflict, resolved with --inspect --doc <id> then --resolve keep|take|revise --doc <id> (a resolution is recorded, then sent by the next plain sync). A held mass delete is accepted only by the person, typing the count in their own terminal (--accept-deletes); --restore-deletes puts the files back; --take-host-deletions takes a mass deletion the pull refused."
17193
17338
  }
17194
17339
  ]
17195
17340
  },
@@ -19191,7 +19336,9 @@ duplication \u2014 a plain overwrite that converges to the same on-disk state).
19191
19336
 
19192
19337
  If a declared Kind governs the document type, prefer 'superbee new "<Kind>" <id> ...'; its
19193
19338
  authoring is strict and create-only. Use 'doc write' for a generic type or a deliberate full
19194
- replacement.
19339
+ replacement. Kind validation is advisory by default: violations appear as warnings, including on
19340
+ no-ops. --strict rejects violations without writing. A changed replacement of a previously conforming
19341
+ document cannot be nonconforming, even in advisory mode.
19195
19342
 
19196
19343
  Options:
19197
19344
  --type <t> OKF concept type (non-empty) [required]
@@ -19252,6 +19399,10 @@ Only the fields you pass change; everything else \u2014 including the body when
19252
19399
  is preserved verbatim. Idempotent: a patch that changes NOTHING (ignoring the auto-refreshed
19253
19400
  timestamp) converges to changed:false (no write, no timestamp refresh).
19254
19401
 
19402
+ Standard-field patches use advisory Kind validation by default, returning warnings even on no-ops;
19403
+ --strict rejects violations without writing. Dynamic declared fields are always strict: passing a
19404
+ --<field> flag requires the whole resulting document to satisfy its Kind, even for a same-value patch.
19405
+
19255
19406
  Options:
19256
19407
  --title <t> Replace the title
19257
19408
  --description <d> Replace the description
@@ -19507,7 +19658,7 @@ function buildCompletingUpdateCommand(id, violations, kind2, okfVersion) {
19507
19658
  const flags = [];
19508
19659
  for (const violation of violations) {
19509
19660
  const field = violation.field;
19510
- if (!field || seen.has(field) || !declaredFields.has(field)) continue;
19661
+ if (!isKindFieldValidationWarning(violation) || !field || seen.has(field) || !declaredFields.has(field)) continue;
19511
19662
  seen.add(field);
19512
19663
  const allowed = kind2.fields.values[field];
19513
19664
  const placeholder = allowed && allowed.length > 0 ? commandToken(`<${allowed.join("|")}>`) : commandLiteral("<value>");
@@ -19722,6 +19873,7 @@ async function translateMutationError(error51, opts) {
19722
19873
  }
19723
19874
  async function mutateDoc(opts) {
19724
19875
  try {
19876
+ let okfVersion;
19725
19877
  const result3 = await mutateDocument({
19726
19878
  ...opts.seedGenerationClock === void 0 ? {} : { seedGenerationClock: opts.seedGenerationClock },
19727
19879
  bundle: opts.bundle,
@@ -19731,7 +19883,8 @@ async function mutateDoc(opts) {
19731
19883
  strict: opts.strict,
19732
19884
  buildCandidate: opts.buildCandidate,
19733
19885
  input: opts.input,
19734
- assertCandidate: (existing, candidate) => {
19886
+ assertCandidate: (existing, candidate, context) => {
19887
+ okfVersion = context.okfVersion;
19735
19888
  if (existing) guardBodyReplace(opts.bundle, existing, candidate.body, opts.bodyReplace);
19736
19889
  },
19737
19890
  onAbsent: opts.onAbsent,
@@ -19742,7 +19895,8 @@ async function mutateDoc(opts) {
19742
19895
  expectedVersion: opts.expectedVersion
19743
19896
  });
19744
19897
  if (result3.changed) await firePostPersist(opts.onPersisted);
19745
- return result3;
19898
+ const kind2 = opts.registry.kinds.get(String(result3.doc.frontmatter.type));
19899
+ return kind2 && result3.warnings.length > 0 ? { ...result3, warnings: projectKindValidationWarnings(okfVersion, kind2, result3.warnings) } : result3;
19746
19900
  } catch (error51) {
19747
19901
  return await translateMutationError(error51, opts);
19748
19902
  }
@@ -29069,8 +29223,9 @@ async function gitBoardAt(canonicalRoot) {
29069
29223
  if (top === null) return null;
29070
29224
  if (top === canonicalRoot) {
29071
29225
  const branch2 = gitText(top, ["symbolic-ref", "-q", "--short", "HEAD"]);
29072
- if (branch2 !== BOARD_BRANCH) return null;
29073
- const upstream2 = gitText(top, ["for-each-ref", "--format=%(upstream:short)", `refs/heads/${branch2}`]) ?? (gitText(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]) === null ? null : BOARD_REF);
29226
+ if (branch2 === null || !isDeclaredBoardBranch(top, branch2)) return null;
29227
+ const boardRef = `${BOARD_REMOTE}/${branch2}`;
29228
+ const upstream2 = gitText(top, ["for-each-ref", "--format=%(upstream:short)", `refs/heads/${branch2}`]) ?? (gitText(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${boardRef}`]) === null ? null : boardRef);
29074
29229
  return { channel: "branch", branch: branch2, upstream: upstream2, shared: upstream2 !== null, top, prefix: "" };
29075
29230
  }
29076
29231
  let committed;
@@ -30075,9 +30230,10 @@ async function loadClaimPolicy(boardPath) {
30075
30230
  const bundle = configuredBundle(boardPath);
30076
30231
  const [registry2, okfVersion] = await Promise.all([loadKinds(bundle), readBundleOkfVersion2(bundle)]);
30077
30232
  if (registry2.kinds.size === 0) return INACTIVE_POLICY;
30233
+ const upstreamRef = boardRefOf(boardPath);
30078
30234
  const resolved = /* @__PURE__ */ new Map();
30079
30235
  return {
30080
- provenance: `${BOARD_UPSTREAM_REF}@${originSha}`,
30236
+ provenance: `${upstreamRef}@${originSha}`,
30081
30237
  forType(type) {
30082
30238
  const name = typeof type === "string" ? type.trim() : "";
30083
30239
  if (name === "") return void 0;
@@ -30088,7 +30244,7 @@ async function loadClaimPolicy(boardPath) {
30088
30244
  return resolved.get(name);
30089
30245
  },
30090
30246
  upstreamFrontmatter(relPath) {
30091
- const shown2 = runGit(boardPath, ["show", `${BOARD_UPSTREAM_REF}:${relPath}`]);
30247
+ const shown2 = runGit(boardPath, ["show", `${upstreamRef}:${relPath}`]);
30092
30248
  if (shown2.status !== 0) return void 0;
30093
30249
  try {
30094
30250
  return parseMarkdown(shown2.stdout, relPath).frontmatter;
@@ -30112,7 +30268,7 @@ function claimLostStatement(owner, provenance2) {
30112
30268
  const who = owner === void 0 ? CLAIM_NO_OWNER_RECORDED : `owner is ${owner}`;
30113
30269
  return `${who} as of ${provenance2}; ${CLAIM_NOT_ARBITRATED}`;
30114
30270
  }
30115
- var CLAIM_LOST_KEY, CLAIM_NOT_ARBITRATED, CLAIM_NO_OWNER_RECORDED, BOARD_UPSTREAM_REF, ENGINE_STAMPED_FIELDS, INACTIVE_POLICY;
30271
+ var CLAIM_LOST_KEY, CLAIM_NOT_ARBITRATED, CLAIM_NO_OWNER_RECORDED, ENGINE_STAMPED_FIELDS, INACTIVE_POLICY;
30116
30272
  var init_claim_conflict = __esm({
30117
30273
  "../cli/src/commands/sync/claim-conflict.ts"() {
30118
30274
  "use strict";
@@ -30124,7 +30280,6 @@ var init_claim_conflict = __esm({
30124
30280
  CLAIM_LOST_KEY = "claim_lost";
30125
30281
  CLAIM_NOT_ARBITRATED = "your claim was not arbitrated";
30126
30282
  CLAIM_NO_OWNER_RECORDED = "no owner is recorded";
30127
- BOARD_UPSTREAM_REF = `${BOARD_REMOTE}/${BOARD_BRANCH}`;
30128
30283
  ENGINE_STAMPED_FIELDS = /* @__PURE__ */ new Set([
30129
30284
  "timestamp",
30130
30285
  "generated",
@@ -30465,11 +30620,12 @@ async function showIncoming(id, values, deps, route) {
30465
30620
  if (path27.isAbsolute(id) || id.split("/").some((seg) => seg === "..")) {
30466
30621
  throw new CliError("USAGE", `--show-incoming needs a repo-relative doc id or path without '..' segments: ${id}`);
30467
30622
  }
30468
- let readRef = `refs/remotes/${BOARD_REF}`;
30623
+ const boardBranch = boardBranchOf(top);
30624
+ let readRef = `refs/remotes/${BOARD_REMOTE}/${boardBranch}`;
30469
30625
  let pathPrefix = "";
30470
30626
  let inTreeBundleDir = bundleDirNameForProject(top);
30471
- if (runGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]).status !== 0) {
30472
- if (runGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${BOARD_BRANCH}`]).status === 0) {
30627
+ if (runGit(top, ["rev-parse", "--verify", "--quiet", readRef]).status !== 0) {
30628
+ if (runGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${boardBranch}`]).status === 0) {
30473
30629
  throw ffSwallowToError("no-upstream", inv, top);
30474
30630
  }
30475
30631
  const committed = committedBundleAtHead(top);
@@ -30773,7 +30929,7 @@ async function locateBoard(args, cwd) {
30773
30929
  throw new CliError("CONFLICT", "the selected private board has a board-origin rebase pending; run sync first to recover it", { help: `${inv} sync${dirSuffix(args)}` });
30774
30930
  }
30775
30931
  const root = route.kind === "bound-board" ? route.owner.bundleRoot : route.bundle.root;
30776
- if (repoTopLevel(root) === null || runGit(root, ["rev-parse", "--verify", "--quiet", `refs/remotes/${BOARD_REF}`]).status !== 0) {
30932
+ if (repoTopLevel(root) === null || runGit(root, ["rev-parse", "--verify", "--quiet", `refs/remotes/${boardRefOf(root)}`]).status !== 0) {
30777
30933
  throw new CliError("USAGE", "--inspect and --resolve apply to a shared Git board or a hosted checkout; this bundle is neither, so sync has no conflicts to show", {
30778
30934
  details: { bundle: root },
30779
30935
  help: `${inv} sync --help`
@@ -30842,7 +30998,7 @@ function commandFor(args, choice) {
30842
30998
  }
30843
30999
  async function runInspect(args, board, cwd) {
30844
31000
  const local = await fs10.readFile(board.exportPath);
30845
- const remote = readDocBytesAtRef(board.root, `refs/remotes/${BOARD_REF}`, board.relPath);
31001
+ const remote = readDocBytesAtRef(board.root, `refs/remotes/${boardRefOf(board.root)}`, board.relPath);
30846
31002
  if (args.out !== void 0) {
30847
31003
  if (remote === null) throw new CliError("NOT_FOUND", `the teammate's side has no version of '${args.id}' to write: it was deleted there`, { details: { id: args.id } });
30848
31004
  const outHelp = `${cliInvocation()} sync --inspect --doc ${commandToken(args.id)} --out <file outside the bundle>`;
@@ -30885,7 +31041,7 @@ async function runInspect(args, board, cwd) {
30885
31041
  file: board.file,
30886
31042
  reason: deleted ? "deleted_remotely" : "changed_remotely",
30887
31043
  local: { version: versionOfBytes(localText), saved_at: board.exportPath, ...preview(localText) },
30888
- remote: { version: remoteText === null ? null : versionOfBytes(remoteText), ref: `${BOARD_REF} (as of the last fetch)`, ...deleted ? { deleted: true } : {}, ...preview(remoteText) },
31044
+ remote: { version: remoteText === null ? null : versionOfBytes(remoteText), ref: `${boardRefOf(board.root)} (as of the last fetch)`, ...deleted ? { deleted: true } : {}, ...preview(remoteText) },
30889
31045
  ...claimFields(divergence),
30890
31046
  ...differs.length > 0 ? { frontmatter_differs: differs } : {},
30891
31047
  ...args.out !== void 0 ? { remote_written_to: path28.resolve(cwd, args.out) } : {},
@@ -30919,7 +31075,7 @@ async function discardSavedCopy(board) {
30919
31075
  }
30920
31076
  async function runResolve(args, choice, board, cwd) {
30921
31077
  const inv = cliInvocation();
30922
- const remote = readDocBytesAtRef(board.root, `refs/remotes/${BOARD_REF}`, board.relPath);
31078
+ const remote = readDocBytesAtRef(board.root, `refs/remotes/${boardRefOf(board.root)}`, board.relPath);
30923
31079
  const bundleDir = conflictBundleDir(args.dir, cwd);
30924
31080
  const dirArgs = bundleDir !== void 0 ? ["--dir", bundleDir] : [];
30925
31081
  const local = await fs10.readFile(board.exportPath);
@@ -30965,7 +31121,7 @@ async function runResolve(args, choice, board, cwd) {
30965
31121
  } else if (present !== null && present.equals(remote)) {
30966
31122
  fileState = "unchanged";
30967
31123
  } else {
30968
- const restored = runGit(board.root, ["restore", `--source=refs/remotes/${BOARD_REF}`, "--worktree", "--", board.relPath]);
31124
+ const restored = runGit(board.root, ["restore", `--source=refs/remotes/${boardRefOf(board.root)}`, "--worktree", "--", board.relPath]);
30969
31125
  if (restored.status !== 0) {
30970
31126
  throw new CliError("RUNTIME", `could not restore the teammate's version of '${args.id}'; your saved copy was kept`, {
30971
31127
  details: { id: args.id, git: restored.stderr.trim() },
@@ -32252,6 +32408,7 @@ __export(sync_exports, {
32252
32408
  SYNC_IN_TREE_CURRENT: () => SYNC_IN_TREE_CURRENT,
32253
32409
  SYNC_IN_TREE_NO_BASIS: () => SYNC_IN_TREE_NO_BASIS,
32254
32410
  SYNC_LOCAL_ONLY_MESSAGE: () => SYNC_LOCAL_ONLY_MESSAGE,
32411
+ SYNC_PUSH_ATTEMPTS: () => SYNC_PUSH_ATTEMPTS,
32255
32412
  SYNC_REMOTE_STATE_UNKNOWN_MESSAGE: () => SYNC_REMOTE_STATE_UNKNOWN_MESSAGE,
32256
32413
  SYNC_USAGE: () => SYNC_USAGE,
32257
32414
  buildConvergeError: () => buildConvergeError,
@@ -32314,7 +32471,11 @@ ${HOSTED_SYNC_USAGE}`));
32314
32471
  }
32315
32472
  if (requestsHostedOnlyVerb(argv2)) throw hostedOnlyVerbError(await targetHome(argv2, deps.cwd ?? process.cwd(), home2), argv2);
32316
32473
  if (requestsConflictVerb(argv2)) {
32317
- await gitConflictVerb(argv2, deps);
32474
+ try {
32475
+ await gitConflictVerb(argv2, deps);
32476
+ } catch (err) {
32477
+ throw isBoardGitError(err) ? cliErrorFromBoardGit(err) : err;
32478
+ }
32318
32479
  return;
32319
32480
  }
32320
32481
  await sync(argv2, deps);
@@ -32346,6 +32507,7 @@ var init_sync = __esm({
32346
32507
  init_converge();
32347
32508
  init_show_incoming();
32348
32509
  init_sync_outcomes();
32510
+ init_src2();
32349
32511
  init_errors4();
32350
32512
  init_output2();
32351
32513
  init_sync2();
@@ -32654,7 +32816,7 @@ async function gitTurnEnd(dir, home2, partial2, stopHookActive) {
32654
32816
  await run([...dir === void 0 ? [] : ["--dir", dir], "--limit", String(REASON_ROWS), "--json"], () => {
32655
32817
  });
32656
32818
  } catch (error51) {
32657
- if (error51 instanceof CliError && (error51.code === "CONFLICT" || error51.code === "AUTH_REQUIRED" || error51.details?.reason === "board_moved")) blocking = error51;
32819
+ if (error51 instanceof CliError && (error51.code === "CONFLICT" || error51.code === "GATE_FAILED" || error51.code === "AUTH_REQUIRED" || error51.details?.reason === "board_moved")) blocking = error51;
32658
32820
  }
32659
32821
  if (!blocking) {
32660
32822
  if (await readGitBlock(home2, board.root) !== null) await recordGitBlock(home2, board.root, null).catch(() => {
@@ -34530,6 +34692,9 @@ async function establishBoard(dir, inv, mode, stdout, deps, opts = {}) {
34530
34692
  throw syncOutcomeError("establish.origin-unconfigured", { inv });
34531
34693
  }
34532
34694
  assertNotLinkedWorktree(top, inv);
34695
+ if (currentBranch(top) !== BOARD_BRANCH && resolveStandaloneBoardCheckout(top, { requireRemoteRef: false }) !== null) {
34696
+ return { already: true };
34697
+ }
34533
34698
  const committed = committedBundleAtHead(top);
34534
34699
  if (committed !== null) {
34535
34700
  assertBundleOutsidePrivateState(path31.join(top, committed.bundleDir));
@@ -34598,6 +34763,114 @@ var init_establish = __esm({
34598
34763
  }
34599
34764
  });
34600
34765
 
34766
+ // ../cli/src/commands/sync/gate.ts
34767
+ import { spawn as spawn4 } from "node:child_process";
34768
+ function declaredSyncGate(boardPath) {
34769
+ const configured = runGit(boardPath, ["config", "--get", SYNC_GATE_CONFIG]);
34770
+ const command = configured.status === 0 ? configured.stdout.trim() : "";
34771
+ if (command === "") return null;
34772
+ const timeout = runGit(boardPath, ["config", "--get", SYNC_GATE_TIMEOUT_CONFIG]);
34773
+ const raw = timeout.status === 0 ? timeout.stdout.trim() : "";
34774
+ const seconds = /^\d+$/.test(raw) && Number(raw) > 0 ? Number(raw) : DEFAULT_SYNC_GATE_TIMEOUT_SECONDS;
34775
+ return { command, timeoutSeconds: seconds };
34776
+ }
34777
+ async function runSyncGate(boardPath, branch, gate, attempt) {
34778
+ const head = currentHead(boardPath);
34779
+ const upstreamSha = resolveOriginRef(boardPath) ?? "";
34780
+ const base = {
34781
+ command: gate.command,
34782
+ attempt,
34783
+ head,
34784
+ upstream: `origin/${branch}`,
34785
+ ...upstreamSha ? { upstream_sha: upstreamSha } : {}
34786
+ };
34787
+ const result3 = await runInOwnGroup(gate.command, boardPath, gate.timeoutSeconds * 1e3, {
34788
+ ...gateBaseEnv(),
34789
+ SUPERBEE_SYNC_GATE: "1",
34790
+ SUPERBEE_BOARD_PATH: boardPath,
34791
+ SUPERBEE_BOARD_BRANCH: branch,
34792
+ SUPERBEE_BOARD_UPSTREAM_REF: `origin/${branch}`,
34793
+ SUPERBEE_BOARD_UPSTREAM_SHA: upstreamSha,
34794
+ SUPERBEE_BOARD_HEAD_SHA: head,
34795
+ SUPERBEE_SYNC_ATTEMPT: String(attempt)
34796
+ });
34797
+ const tail = outputTail(result3.output);
34798
+ const failed = (reason, extra) => ({
34799
+ passed: false,
34800
+ reason,
34801
+ details: { ...base, ...extra, ...tail ? { output_tail: tail } : {} }
34802
+ });
34803
+ if (result3.timedOut) {
34804
+ return failed(`timed out after ${gate.timeoutSeconds}s`, { timed_out: true, timeout_seconds: gate.timeoutSeconds });
34805
+ }
34806
+ if (result3.spawnError) return failed(`could not run (${result3.spawnError})`, { spawn_error: result3.spawnError });
34807
+ if (result3.signal) return failed(`was killed by ${result3.signal}`, { signal: result3.signal });
34808
+ if (result3.status !== 0) return failed(`exited ${result3.status}`, { exit_status: result3.status });
34809
+ const attached = runGit(boardPath, ["symbolic-ref", "-q", "--short", "HEAD"]);
34810
+ if (runGit(boardPath, ["rev-parse", "HEAD"]).stdout.trim() !== head || attached.status !== 0 || attached.stdout.trim() !== branch || countUncommitted(boardPath) > 0) {
34811
+ return failed("changed the board (a gate must only inspect it)", { modified_board: true });
34812
+ }
34813
+ return { passed: true, details: { ...base, exit_status: 0 } };
34814
+ }
34815
+ function runInOwnGroup(command, cwd, timeoutMs, env) {
34816
+ return new Promise((resolve2) => {
34817
+ let output = "";
34818
+ let timedOut = false;
34819
+ let settled = false;
34820
+ const child = spawn4(command, { cwd, env, shell: true, detached: true, stdio: ["ignore", "pipe", "pipe"] });
34821
+ const killGroup = () => {
34822
+ if (child.pid === void 0) return;
34823
+ try {
34824
+ process.kill(-child.pid, "SIGKILL");
34825
+ } catch {
34826
+ }
34827
+ };
34828
+ const collect = (chunk) => {
34829
+ output += chunk.toString("utf8");
34830
+ if (output.length > OUTPUT_MAX_CHARS) output = output.slice(-OUTPUT_MAX_CHARS);
34831
+ };
34832
+ child.stdout?.on("data", collect);
34833
+ child.stderr?.on("data", collect);
34834
+ const timer = setTimeout(() => {
34835
+ timedOut = true;
34836
+ killGroup();
34837
+ }, timeoutMs);
34838
+ const finish = (run) => {
34839
+ if (settled) return;
34840
+ settled = true;
34841
+ clearTimeout(timer);
34842
+ killGroup();
34843
+ resolve2({ ...run, output, timedOut });
34844
+ };
34845
+ child.on("error", (error51) => finish({ status: null, signal: null, spawnError: error51.code ?? error51.message }));
34846
+ child.on("close", (status2, signal) => finish({ status: status2, signal }));
34847
+ });
34848
+ }
34849
+ function gateBaseEnv() {
34850
+ const env = { ...process.env };
34851
+ for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"]) delete env[name];
34852
+ return env;
34853
+ }
34854
+ function outputTail(output) {
34855
+ const lines = output.replace(/\s+$/, "").split("\n");
34856
+ if (lines.length === 1 && lines[0] === "") return "";
34857
+ return lines.slice(-OUTPUT_TAIL_LINES).join("\n");
34858
+ }
34859
+ var SYNC_GATE_CONFIG, SYNC_GATE_TIMEOUT_CONFIG, DEFAULT_SYNC_GATE_TIMEOUT_SECONDS, OUTPUT_TAIL_LINES, OUTPUT_MAX_CHARS;
34860
+ var init_gate = __esm({
34861
+ "../cli/src/commands/sync/gate.ts"() {
34862
+ "use strict";
34863
+ init_define_SUPERBEE_BUILD_IDENTITY();
34864
+ init_define_SUPERBEE_UPDATE_POLICY();
34865
+ init_src2();
34866
+ SYNC_GATE_CONFIG = "superbee.syncGate";
34867
+ SYNC_GATE_TIMEOUT_CONFIG = "superbee.syncGateTimeoutSeconds";
34868
+ DEFAULT_SYNC_GATE_TIMEOUT_SECONDS = 600;
34869
+ OUTPUT_TAIL_LINES = 40;
34870
+ OUTPUT_MAX_CHARS = 256 * 1024;
34871
+ }
34872
+ });
34873
+
34601
34874
  // ../cli/src/bound-board-recovery.ts
34602
34875
  function recoveryFailure(target3, stage, message) {
34603
34876
  return new CliError("CONFLICT", `project binding cannot recover its private board: ${message}`, {
@@ -34799,6 +35072,7 @@ __export(orchestrate_exports, {
34799
35072
  SYNC_IN_TREE_CURRENT: () => SYNC_IN_TREE_CURRENT,
34800
35073
  SYNC_IN_TREE_NO_BASIS: () => SYNC_IN_TREE_NO_BASIS,
34801
35074
  SYNC_LOCAL_ONLY_MESSAGE: () => SYNC_LOCAL_ONLY_MESSAGE,
35075
+ SYNC_PUSH_ATTEMPTS: () => SYNC_PUSH_ATTEMPTS,
34802
35076
  SYNC_REMOTE_STATE_UNKNOWN_MESSAGE: () => SYNC_REMOTE_STATE_UNKNOWN_MESSAGE,
34803
35077
  SYNC_USAGE: () => SYNC_USAGE,
34804
35078
  inTreePullHint: () => inTreePullHint,
@@ -35109,7 +35383,7 @@ async function commitPhase(board, pullOnly2) {
35109
35383
  return commitResult;
35110
35384
  }
35111
35385
  async function pullPhase(run, board, commitResult) {
35112
- const { boardPath, key: key2, outcome } = board;
35386
+ const { boardPath, outcome } = board;
35113
35387
  if (run.pullOnly) {
35114
35388
  const ff = ffPull(boardPath);
35115
35389
  if (ff.swallowed) {
@@ -35117,7 +35391,11 @@ async function pullPhase(run, board, commitResult) {
35117
35391
  }
35118
35392
  return;
35119
35393
  }
35120
- const fail4 = (err) => throwPostCommitFailure(withProvisionAnnouncement(err, outcome), commitResult.committed, key2, boardPath);
35394
+ await convergingRebase(run, board, commitResult.committed);
35395
+ }
35396
+ async function convergingRebase(run, board, committedThisRun) {
35397
+ const { boardPath, key: key2, outcome } = board;
35398
+ const fail4 = (err) => throwPostCommitFailure(withProvisionAnnouncement(err, outcome), committedThisRun, key2, boardPath);
35121
35399
  let rebaseOutcome;
35122
35400
  try {
35123
35401
  rebaseOutcome = fetchRebaseResolving(boardPath, defaultSyncStore.exportsDir(key2));
@@ -35150,29 +35428,94 @@ async function deltaPhase(board, baseline) {
35150
35428
  );
35151
35429
  return { originDelta, changes: [], reanchorNote: REANCHOR_NOTE };
35152
35430
  }
35153
- async function pushPhase(run, board, commitResult, delta) {
35154
- if (run.pullOnly) return { commits: 0, documents: 0 };
35155
- const ahead = unpushedCount(board.boardPath) ?? 0;
35156
- const outgoing = ahead > 0 ? new Set(originDocsBetween(board.boardPath, resolveOriginRef(board.boardPath), currentHead(board.boardPath)).map((change) => change.docId)).size : 0;
35157
- try {
35158
- push(board.boardPath);
35159
- return { commits: ahead, documents: outgoing };
35160
- } catch (err) {
35161
- const classified = withSharingDetails(toCliError(err, "push"), { operation: "update-board" });
35162
- const warning = pushFailureMessage(classified);
35163
- const partial2 = buildPushFailurePartial(
35164
- board.outcome,
35165
- warning,
35166
- commitResult.docs,
35167
- delta.originDelta,
35168
- run.limit,
35169
- delta.reanchorNote,
35170
- classified.details
35171
- );
35172
- run.stdout(render(withSyncEnvelope(partial2, syncEnvelope("git", { received: delta.originDelta.length })), run.mode));
35173
- await writeAwarenessCache(board.key, board.boardPath, delta.changes, delta.reanchorNote);
35174
- throw asHandled(new CliError(classified.code, warning, { details: classified.details }));
35175
- }
35431
+ function raceBackoffMs(attempt) {
35432
+ return Math.floor(250 * attempt + Math.random() * 750 * attempt);
35433
+ }
35434
+ async function pushPhase(run, board, commitResult, delta, baseline) {
35435
+ if (run.pullOnly) return { commits: 0, documents: 0, delta };
35436
+ const gate = declaredSyncGate(board.boardPath);
35437
+ const branch = boardBranchOf(board.boardPath);
35438
+ let gateDetails;
35439
+ for (let attempt = 1; ; attempt += 1) {
35440
+ const head = currentHead(board.boardPath);
35441
+ const counted = unpushedCount(board.boardPath);
35442
+ const ahead = counted ?? 0;
35443
+ const outgoing = ahead > 0 ? new Set(originDocsBetween(board.boardPath, resolveOriginRef(board.boardPath), head).map((change) => change.docId)).size : 0;
35444
+ if (gate && (counted === null || ahead > 0)) {
35445
+ const judged = await runSyncGate(board.boardPath, branch, gate, attempt);
35446
+ if (!judged.passed) await failGate(run, board, commitResult, delta, judged.reason ?? "failed", judged.details);
35447
+ gateDetails = judged.details;
35448
+ }
35449
+ try {
35450
+ push(board.boardPath, head, branch);
35451
+ return { commits: ahead, documents: outgoing, delta, ...gateDetails ? { gate: gateDetails } : {} };
35452
+ } catch (err) {
35453
+ const classified = withSharingDetails(toCliError(err, "push"), { operation: "update-board" });
35454
+ const raced = classified.code === "TRANSIENT" && classified.details?.reason === "non-fast-forward";
35455
+ if (raced && attempt < SYNC_PUSH_ATTEMPTS) {
35456
+ await new Promise((resolve2) => setTimeout(resolve2, raceBackoffMs(attempt)));
35457
+ if (boardBranchOf(board.boardPath) !== branch) {
35458
+ throw new CliError("CONFLICT", `the board checkout left '${branch}' during sync; nothing more was pushed`, {
35459
+ details: { state: "board-branch-moved", branch },
35460
+ help: `check out '${branch}' in ${board.boardPath}, then ${run.inv} sync`
35461
+ });
35462
+ }
35463
+ await convergingRebase(run, board, commitResult.committed);
35464
+ refuseMovedBoard(board.boardPath);
35465
+ delta = await deltaPhase(board, baseline);
35466
+ holdMalformedCommitted(run, board, delta);
35467
+ continue;
35468
+ }
35469
+ const details = raced ? { ...classified.details, attempts: attempt } : classified.details;
35470
+ const exhausted = raced ? new CliError(
35471
+ classified.code,
35472
+ `another writer pushed to the board each time sync tried (${attempt} attempts, each re-fetched and rebased${gate ? " and re-gated" : ""}) \u2014 re-run sync to try again`,
35473
+ { details }
35474
+ ) : classified;
35475
+ const warning = pushFailureMessage(exhausted);
35476
+ const partial2 = buildPushFailurePartial(
35477
+ board.outcome,
35478
+ warning,
35479
+ commitResult.docs,
35480
+ delta.originDelta,
35481
+ run.limit,
35482
+ delta.reanchorNote,
35483
+ details
35484
+ );
35485
+ run.stdout(render(withSyncEnvelope(partial2, syncEnvelope("git", { received: delta.originDelta.length })), run.mode));
35486
+ await writeAwarenessCache(board.key, board.boardPath, delta.changes, delta.reanchorNote);
35487
+ throw asHandled(new CliError(exhausted.code, warning, { details }));
35488
+ }
35489
+ }
35490
+ }
35491
+ function holdMalformedCommitted(run, board, delta) {
35492
+ const originRef = resolveOriginRef(board.boardPath);
35493
+ if (originRef === null) return;
35494
+ const held2 = malformedCommittedDocuments(board.boardPath, originRef, "HEAD");
35495
+ if (held2.length > 0) reportHeld(run, board, delta, held2, "committed locally");
35496
+ }
35497
+ async function failGate(run, board, commitResult, delta, reason, details) {
35498
+ const warning = `committed to the board locally \u2014 your work is saved. The sync gate ${reason} on the rebased board, so nothing was pushed; fix what it reports, then re-run sync`;
35499
+ const help2 = `run the gate yourself from ${board.boardPath} (git config --get ${SYNC_GATE_CONFIG}), fix what it reports, then ${run.inv} sync`;
35500
+ const partial2 = buildPushFailurePartial(
35501
+ board.outcome,
35502
+ warning,
35503
+ commitResult.docs,
35504
+ delta.originDelta,
35505
+ run.limit,
35506
+ delta.reanchorNote,
35507
+ { gate: details }
35508
+ );
35509
+ partial2.code = "GATE_FAILED";
35510
+ partial2.help = help2;
35511
+ const envelope2 = syncEnvelope("git", { received: delta.originDelta.length, next: [`${run.inv} sync`] });
35512
+ run.stdout(render(withSyncEnvelope(partial2, envelope2), run.mode));
35513
+ await writeAwarenessCache(board.key, board.boardPath, delta.changes, delta.reanchorNote);
35514
+ throw new CliError("GATE_FAILED", `the sync gate ${reason}; nothing was pushed`, {
35515
+ help: help2,
35516
+ details: { gate: details },
35517
+ handled: true
35518
+ });
35176
35519
  }
35177
35520
  async function receiptPhase(run, board, commitResult, delta, pushed, establishAlreadyNote) {
35178
35521
  await writeAwarenessCache(board.key, board.boardPath, delta.changes, delta.reanchorNote);
@@ -35187,6 +35530,7 @@ async function receiptPhase(run, board, commitResult, delta, pushed, establishAl
35187
35530
  reanchorNote: delta.reanchorNote,
35188
35531
  hookHint
35189
35532
  });
35533
+ if (pushed.gate) receipt3.gate = `passed: ${String(pushed.gate.command)} (attempt ${String(pushed.gate.attempt)})`;
35190
35534
  run.stdout(render(withSyncEnvelope(receipt3, syncEnvelope("git", { sent: pushed.documents, received: delta.originDelta.length })), run.mode));
35191
35535
  }
35192
35536
  async function heldRun(run, board, baseline, held2) {
@@ -35279,15 +35623,11 @@ async function syncCommand(argv2, deps = {}) {
35279
35623
  await pullPhase(run, board, commitResult);
35280
35624
  if (!run.pullOnly) refuseMovedBoard(board.boardPath);
35281
35625
  const delta = await deltaPhase(board, baseline);
35282
- const originRef = run.pullOnly ? null : resolveOriginRef(board.boardPath);
35283
- if (originRef !== null) {
35284
- const held2 = malformedCommittedDocuments(board.boardPath, originRef, "HEAD");
35285
- if (held2.length > 0) reportHeld(run, board, delta, held2, "committed locally");
35286
- }
35287
- const pushed = await pushPhase(run, board, commitResult, delta);
35288
- await receiptPhase(run, board, commitResult, delta, pushed, establishAlreadyNote);
35626
+ if (!run.pullOnly) holdMalformedCommitted(run, board, delta);
35627
+ const pushed = await pushPhase(run, board, commitResult, delta, baseline);
35628
+ await receiptPhase(run, board, commitResult, pushed.delta, pushed, establishAlreadyNote);
35289
35629
  }
35290
- var SYNC_USAGE, DEFAULT_LIMIT, SYNC_LOCAL_ONLY_MESSAGE, SYNC_REMOTE_STATE_UNKNOWN_MESSAGE, SYNC_IN_TREE_BOARD_LINE, SYNC_IN_TREE_NO_BASIS, SYNC_IN_TREE_CURRENT;
35630
+ var SYNC_USAGE, DEFAULT_LIMIT, SYNC_LOCAL_ONLY_MESSAGE, SYNC_REMOTE_STATE_UNKNOWN_MESSAGE, SYNC_IN_TREE_BOARD_LINE, SYNC_IN_TREE_NO_BASIS, SYNC_IN_TREE_CURRENT, SYNC_PUSH_ATTEMPTS;
35291
35631
  var init_orchestrate = __esm({
35292
35632
  "../cli/src/commands/sync/orchestrate.ts"() {
35293
35633
  "use strict";
@@ -35302,6 +35642,7 @@ var init_orchestrate = __esm({
35302
35642
  init_establish();
35303
35643
  init_converge();
35304
35644
  init_show_incoming();
35645
+ init_gate();
35305
35646
  init_sync_outcomes();
35306
35647
  init_errors4();
35307
35648
  init_args();
@@ -35349,6 +35690,11 @@ board changes.
35349
35690
  The same verb also works when an automation or chat agent clones the shared \`board\` branch as
35350
35691
  the repository root: a tracked OKF root index plus the exact attached \`board\` branch identifies
35351
35692
  that standalone checkout, so sync operates there directly and never creates a nested bundle.
35693
+ A repository can carry more than one board: a NAMED board lives on its own \`board-<name>\` branch
35694
+ (lowercase letters, digits and hyphens) whose root commits \`.superbee-board.json\` containing
35695
+ \`{"schema": 1, "branch": "board-<name>"}\`. A standalone clone of that branch, tracking
35696
+ \`origin/board-<name>\`, syncs exactly as above against \`origin/board-<name>\` and never touches
35697
+ \`origin/board\`; the conventional \`.superbee\` worktree always uses \`board\`.
35352
35698
  \`--establish\` on an already-established project is a safe no-op that notes \`already established\`
35353
35699
  and proceeds as an ordinary sync.
35354
35700
 
@@ -35392,7 +35738,25 @@ any error envelope) on stderr. A doc absent upstream renders as an expected stat
35392
35738
 
35393
35739
  If the push fails after a local commit already landed (offline, revoked/expired credentials, or a
35394
35740
  locked repository), the receipt still reports what committed/pulled successfully \u2014 your work is
35395
- saved locally either way, and re-running sync retries the push.
35741
+ saved locally either way, and re-running sync retries the push. A push that loses a race to
35742
+ another writer (non-fast-forward, including the remote's "incorrect old value" refusal) is not a
35743
+ permission problem: sync re-fetches, rebases with the same converging mechanic, re-runs the gate,
35744
+ and pushes again, up to 5 attempts in all, then exits 1 with \`details.reason: non-fast-forward\`.
35745
+
35746
+ A clone can declare a SYNC GATE: a command sync runs on the converged tree after rebasing onto
35747
+ the remote board and before every push that sends commits (\`git config superbee.syncGate
35748
+ '<command>'\`, any Git config scope; an empty value declares none; \`superbee.syncGateTimeoutSeconds\`
35749
+ overrides the 600s timeout). It runs through the shell from the board root with
35750
+ SUPERBEE_BOARD_BRANCH, SUPERBEE_BOARD_UPSTREAM_REF, SUPERBEE_BOARD_UPSTREAM_SHA (the remote tip
35751
+ rebased onto), SUPERBEE_BOARD_HEAD_SHA (what would be pushed) and SUPERBEE_SYNC_ATTEMPT set. A
35752
+ non-zero exit, a timeout, or any edit to the board holds the push: the work stays committed
35753
+ locally and sync exits 5 with code GATE_FAILED and the gate's last output lines. Exactly the
35754
+ judged commit is pushed. The command comes from Git configuration, never from board files, so
35755
+ board content cannot choose the command; keep the gate's code outside the board too (a script
35756
+ inside the board runs whatever a teammate last pushed). A gate must leave the board as it found
35757
+ it: an untracked, non-ignored file it writes counts as an edit. The opt-in turn-end sync runs
35758
+ the gate as well, inside the agent host's hook time limit, so keep a gate fast there.
35759
+ \`--pull-only\` never runs it, and neither does \`--establish\`'s first publication of a board.
35396
35760
 
35397
35761
  A board can also ride IN-TREE: \`.superbee/\` or legacy \`.agentstate-lite/\` committed WITH the code on the current
35398
35762
  branch, with no dedicated \`board\` branch anywhere. That is a supported, read-side mode \u2014 sync
@@ -35462,6 +35826,7 @@ Options:
35462
35826
  SYNC_IN_TREE_BOARD_LINE = syncInTreeBoardLine(BUNDLE_DIR);
35463
35827
  SYNC_IN_TREE_NO_BASIS = "no-comparison-basis";
35464
35828
  SYNC_IN_TREE_CURRENT = "checkout is current with upstream";
35829
+ SYNC_PUSH_ATTEMPTS = 5;
35465
35830
  }
35466
35831
  });
35467
35832
 
@@ -83884,6 +84249,9 @@ bundle's compatible storage coordinate through each document's declared Kind.
83884
84249
 
83885
84250
  // ../cli/src/kind-draft.ts
83886
84251
  import { createHash as createHash15 } from "node:crypto";
84252
+ function excludedInferenceField(field) {
84253
+ return EXCLUDED_FIELDS.has(field) || isManagedDocumentMetadataField(field) || field.startsWith("superbee_") && field !== SUPERBEE_PROGRESS_STATUS_FIELD;
84254
+ }
83887
84255
  function kindDeclaresAnything(kind2) {
83888
84256
  return Object.values(DECLARES).some((declares) => declares(kind2));
83889
84257
  }
@@ -83934,7 +84302,7 @@ function inferKindCandidate(type, instances, stats = collectInstanceStats(instan
83934
84302
  const optional2 = [];
83935
84303
  const values = {};
83936
84304
  for (const [key2, present] of Object.entries(stats.keyPresence)) {
83937
- if (EXCLUDED_FIELDS.has(key2)) continue;
84305
+ if (excludedInferenceField(key2)) continue;
83938
84306
  if (present === count5 && count5 > 0) {
83939
84307
  required2.push(key2);
83940
84308
  if (count5 >= ENUM_MIN_INSTANCES && everyValueScalarString(instances, key2)) {
@@ -84010,7 +84378,7 @@ var init_kind_draft = __esm({
84010
84378
  ENUM_MIN_DISTINCT = 2;
84011
84379
  ENUM_MAX_DISTINCT = 6;
84012
84380
  SECTION_PROMOTION_FLOOR = 0.6;
84013
- EXCLUDED_FIELDS = /* @__PURE__ */ new Set([...RESERVED_KIND_FIELD_NAMES, SUPERBEE_UPDATED_BY_FIELD]);
84381
+ EXCLUDED_FIELDS = /* @__PURE__ */ new Set([...RESERVED_KIND_FIELD_NAMES, "head_version"]);
84014
84382
  FIELDS_DECLARES = {
84015
84383
  required: (f2) => f2.required.length > 0,
84016
84384
  optional: (f2) => f2.optional.length > 0,
@@ -84147,9 +84515,9 @@ function renderKindHelp(kind2, registry2, inv) {
84147
84515
  ${sectionLines}
84148
84516
  ` + linksBlock + `${pathLine}
84149
84517
 
84150
- Repeat a flag to set an array value (e.g. --tag a --tag b). Validation is STRICT.
84518
+ Repeat a flag to set an array value (e.g. --tag a --tag b). Validation is STRICT; violations reject without creating a document.
84151
84519
  ` + (req.includes("timestamp") || opt.includes("timestamp") ? `In OKF v0.2, --timestamp requires a real ISO-8601 date and time with an explicit UTC offset (Z or numeric).
84152
- ` : "") + `To ADD a field to this kind, edit its convention doc (${inv} kinds names it; then pull \u2192 edit fields.optional \u2192 promote).
84520
+ ` : "") + `To ADD a field: ${inv} kind field ${commandToken(kind2.governs)} add <name> [--required] [--values <a,b,c>].
84153
84521
 
84154
84522
  Options:
84155
84523
  --stale-after <iso> Expiration instant (OKF v0.2 only); valid date, time, and zone required
@@ -85937,7 +86305,7 @@ async function prepareRecipeEvolution(bundle, sourceRecipe) {
85937
86305
  id: doc2.id,
85938
86306
  current_version: contentVersion(doc2)
85939
86307
  });
85940
- for (const warning of validateAgainstKind(doc2, kind2)) {
86308
+ for (const warning of projectKindValidationWarnings(okfVersion, kind2, validateAgainstKind(doc2, kind2))) {
85941
86309
  evolutionBlocker(
85942
86310
  blockers,
85943
86311
  "RECIPE_EVOLUTION_INSTANCE_INVALID",
@@ -86555,9 +86923,9 @@ async function status(argv2, deps = {}) {
86555
86923
  for (const doc2 of docs) {
86556
86924
  const kind2 = registry2.kinds.get(docType2(doc2));
86557
86925
  if (!kind2) continue;
86558
- for (const w2 of validateAgainstKind(doc2, kind2)) {
86926
+ for (const w2 of projectKindValidationWarnings(okfVersion, kind2, validateAgainstKind(doc2, kind2))) {
86559
86927
  lintRows.push({ id: doc2.id, field: w2.field ?? "", code: w2.code });
86560
- if (FRONTMATTER_VIOLATION_CODES.has(w2.code) && !conformanceDebtDocs.has(doc2.id)) {
86928
+ if (isKindFieldValidationWarning(w2) && !conformanceDebtDocs.has(doc2.id)) {
86561
86929
  conformanceDebtDocs.set(doc2.id, docType2(doc2));
86562
86930
  }
86563
86931
  }
@@ -86856,7 +87224,7 @@ async function status(argv2, deps = {}) {
86856
87224
  }
86857
87225
  stdout(render({ home: "local", sync: "none (local only)", ...unboundCopyOf(facts), ...out }, resolveMode(values)));
86858
87226
  }
86859
- var STATUS_USAGE, DEFAULT_LIMIT3, FRONTMATTER_VIOLATION_CODES;
87227
+ var STATUS_USAGE, DEFAULT_LIMIT3;
86860
87228
  var init_status2 = __esm({
86861
87229
  "../cli/src/commands/status.ts"() {
86862
87230
  "use strict";
@@ -87037,7 +87405,6 @@ Options:
87037
87405
  -h, --help Show this help
87038
87406
  `;
87039
87407
  DEFAULT_LIMIT3 = 20;
87040
- FRONTMATTER_VIOLATION_CODES = /* @__PURE__ */ new Set(["KIND_FIELD_MISSING", "KIND_FIELD_VALUE", "KIND_FIELD_ARITY"]);
87041
87408
  }
87042
87409
  });
87043
87410
 
@@ -125254,9 +125621,9 @@ function manifestContent(assets) {
125254
125621
  return `${JSON.stringify(manifest, null, 2)}
125255
125622
  `;
125256
125623
  }
125257
- function unmanagedExtras(dir, managed2) {
125624
+ function unmanagedExtras(dir, managed) {
125258
125625
  return listFilesRelative(dir).filter(
125259
- (f2) => f2 !== SKILL_MANIFEST_FILENAME && !managed2.has(f2) && !isManagedDebris(f2, managed2)
125626
+ (f2) => f2 !== SKILL_MANIFEST_FILENAME && !managed.has(f2) && !isManagedDebris(f2, managed)
125260
125627
  );
125261
125628
  }
125262
125629
  function isDirectory(p2) {
@@ -125304,10 +125671,10 @@ function preflightInstallIntoDir(dir, assets) {
125304
125671
  reason: `installed skill uses newer compatibility contract ${manifest.compatibility_contract}; running CLI contract ${assets.compatibilityContract} will not downgrade it`
125305
125672
  };
125306
125673
  }
125307
- const managed2 = /* @__PURE__ */ new Set([...manifest.files, ...assets.files, SKILL_MANIFEST_FILENAME]);
125308
- const obstructed = directoryObstructions(dir, managed2);
125674
+ const managed = /* @__PURE__ */ new Set([...manifest.files, ...assets.files, SKILL_MANIFEST_FILENAME]);
125675
+ const obstructed = directoryObstructions(dir, managed);
125309
125676
  if (obstructed.length > 0) return directoryObstructionRefusal(obstructed);
125310
- const extras = unmanagedExtras(dir, managed2);
125677
+ const extras = unmanagedExtras(dir, managed);
125311
125678
  if (extras.length > 0) {
125312
125679
  return {
125313
125680
  ok: false,
@@ -125396,10 +125763,10 @@ function preflightUninstallFromDir(dir) {
125396
125763
  if (manifest === null) {
125397
125764
  return { ok: false, reason: `${SKILL_MANIFEST_FILENAME} does not prove ownership \u2014 refusing to delete anything from a folder in an unknown state` };
125398
125765
  }
125399
- const managed2 = /* @__PURE__ */ new Set([...manifest.files, SKILL_MANIFEST_FILENAME]);
125400
- const obstructed = directoryObstructions(dir, managed2);
125766
+ const managed = /* @__PURE__ */ new Set([...manifest.files, SKILL_MANIFEST_FILENAME]);
125767
+ const obstructed = directoryObstructions(dir, managed);
125401
125768
  if (obstructed.length > 0) return directoryObstructionRefusal(obstructed);
125402
- const extras = unmanagedExtras(dir, managed2);
125769
+ const extras = unmanagedExtras(dir, managed);
125403
125770
  if (extras.length > 0) {
125404
125771
  return {
125405
125772
  ok: false,
@@ -126863,12 +127230,12 @@ function runPassiveUpdateOrientation(deps = {}) {
126863
127230
  releaseActiveUpdateLease(home2, claimed.lease.token);
126864
127231
  return void 0;
126865
127232
  }
126866
- const spawn4 = deps.spawn ?? ((command, argv2, options) => currentHost().spawnChild(command, argv2, options));
127233
+ const spawn5 = deps.spawn ?? ((command, argv2, options) => currentHost().spawnChild(command, argv2, options));
126867
127234
  if (!activeLeaseAuthority(home2, claimed.lease.token, now())) {
126868
127235
  return void 0;
126869
127236
  }
126870
127237
  try {
126871
- const child = spawn4(
127238
+ const child = spawn5(
126872
127239
  process.execPath,
126873
127240
  [entry, "__update-refresh-v1", claimed.lease.token],
126874
127241
  { detached: true, stdio: "ignore" }