superbee 0.4.2-pre.3 → 0.4.2-pre.4
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/portable-export.d.ts +119 -0
- package/dist/portable-export.mjs +474 -0
- package/dist/publication/index.d.ts +2 -0
- package/dist/publication/source-exclusions.d.ts +29 -0
- package/dist/publication.mjs +72 -53
- package/dist/superbee.mjs +179 -61
- package/package.json +5 -1
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.
|
|
47
|
+
define_SUPERBEE_BUILD_IDENTITY_default = { schema: "superbee.build-identity.v1", package: { name: "superbee", version: "0.4.2-pre.4" }, source: { commit: "2809f64a406f710381dcf6ad72a408742af7bf2d", dirty: false }, artifact: { channel: "npm-package" }, compatibility_contracts: { skill: 1, hook: 1, mcp: 1 } };
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -12923,8 +12923,7 @@ function isConceptDocPath(relPath) {
|
|
|
12923
12923
|
return relPath.endsWith(".md") && !isReservedFile(relPath);
|
|
12924
12924
|
}
|
|
12925
12925
|
function nameStatusRows(out) {
|
|
12926
|
-
const fields2 = out
|
|
12927
|
-
if (fields2.length > 0 && fields2[fields2.length - 1] === "") fields2.pop();
|
|
12926
|
+
const fields2 = nulFields(out);
|
|
12928
12927
|
const rows = [];
|
|
12929
12928
|
for (let i = 0; i + 1 < fields2.length; i += 2) {
|
|
12930
12929
|
const letter = (fields2[i] ?? "").trim().charAt(0);
|
|
@@ -13004,13 +13003,8 @@ ${bodyLines2.join("\n")}
|
|
|
13004
13003
|
return { committed: true, sha, subject, docs };
|
|
13005
13004
|
}
|
|
13006
13005
|
function malformedOutgoingDocuments(boardPath) {
|
|
13007
|
-
const listed2 = runGit(boardPath, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames"]);
|
|
13008
|
-
if (listed2.status !== 0) throw classifyGitError(failureOf(["status"], listed2));
|
|
13009
13006
|
const held2 = [];
|
|
13010
|
-
for (const
|
|
13011
|
-
if (record6.length < 4) continue;
|
|
13012
|
-
const code3 = record6.slice(0, 2);
|
|
13013
|
-
const relPath = record6.slice(3);
|
|
13007
|
+
for (const { status: code3, path: relPath } of readStatusRows(boardPath, { allUntracked: true })) {
|
|
13014
13008
|
if (code3.includes("D") || !isConceptDocPath(relPath)) continue;
|
|
13015
13009
|
const file2 = path7.join(boardPath, relPath);
|
|
13016
13010
|
const content3 = readRegularFile(file2);
|
|
@@ -13239,7 +13233,6 @@ function fetchRebaseResolving(boardPath, exportDir) {
|
|
|
13239
13233
|
const r2 = runGit(boardPath, [...idFlags, "rebase", boardRef], { rebase: true, timeoutMs: NETWORK_TIMEOUT_MS });
|
|
13240
13234
|
if (r2.status === 0) return { status: "clean" };
|
|
13241
13235
|
if (!detectStaleRebase(boardPath)) throw classifyGitError(failureOf(["rebase", boardRef], r2));
|
|
13242
|
-
const listConflicted = () => mustGit(boardPath, ["diff", "--name-only", "-z", "--diff-filter=U"]).split("\0").filter((l) => l.length > 0);
|
|
13243
13236
|
const byPath = /* @__PURE__ */ new Map();
|
|
13244
13237
|
try {
|
|
13245
13238
|
let stops = 0;
|
|
@@ -13250,7 +13243,7 @@ function fetchRebaseResolving(boardPath, exportDir) {
|
|
|
13250
13243
|
`sync's converging rebase did not terminate after ${MAX_REBASE_STOPS} stops \u2014 aborting to leave the board unchanged`
|
|
13251
13244
|
);
|
|
13252
13245
|
}
|
|
13253
|
-
const conflicted =
|
|
13246
|
+
const conflicted = conflictedPaths(boardPath);
|
|
13254
13247
|
if (conflicted.length === 0) {
|
|
13255
13248
|
runGit(boardPath, [...idFlags, "rebase", "--skip"], { rebase: true });
|
|
13256
13249
|
continue;
|
|
@@ -13306,13 +13299,8 @@ function fetchRebaseResolving(boardPath, exportDir) {
|
|
|
13306
13299
|
return { status: "resolved", conflicts: [...byPath.values()] };
|
|
13307
13300
|
}
|
|
13308
13301
|
function privateStateInOutgoingWorktree(boardPath) {
|
|
13309
|
-
const listed2 = runGit(boardPath, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames"]);
|
|
13310
|
-
if (listed2.status !== 0) throw classifyGitError(failureOf(["status"], listed2));
|
|
13311
13302
|
const found = [];
|
|
13312
|
-
for (const
|
|
13313
|
-
if (record6.length < 4) continue;
|
|
13314
|
-
const code3 = record6.slice(0, 2);
|
|
13315
|
-
const relPath = record6.slice(3);
|
|
13303
|
+
for (const { status: code3, path: relPath } of readStatusRows(boardPath, { allUntracked: true })) {
|
|
13316
13304
|
if (code3.includes("D")) continue;
|
|
13317
13305
|
const finding = privateStateFinding(relPath, readPrivateStateCandidate(path7.join(boardPath, relPath)));
|
|
13318
13306
|
if (finding) found.push(finding);
|
|
@@ -13532,10 +13520,42 @@ function currentBranch(top) {
|
|
|
13532
13520
|
const r2 = runGit(top, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
13533
13521
|
return r2.status === 0 ? r2.stdout.trim() : "HEAD";
|
|
13534
13522
|
}
|
|
13523
|
+
function nulFields(out) {
|
|
13524
|
+
const fields2 = out.split("\0");
|
|
13525
|
+
if (fields2.at(-1) === "") fields2.pop();
|
|
13526
|
+
return fields2;
|
|
13527
|
+
}
|
|
13528
|
+
function readStatusRows(dir, opts = {}) {
|
|
13529
|
+
const args = [
|
|
13530
|
+
"status",
|
|
13531
|
+
"--porcelain=v1",
|
|
13532
|
+
"-z",
|
|
13533
|
+
...opts.renamePolicy === "configured" ? [] : ["--no-renames"],
|
|
13534
|
+
...opts.allUntracked ? ["--untracked-files=all"] : [],
|
|
13535
|
+
...opts.prefix ? ["--", opts.prefix] : []
|
|
13536
|
+
];
|
|
13537
|
+
const r2 = runGit(dir, args);
|
|
13538
|
+
if (r2.status !== 0) {
|
|
13539
|
+
if (opts.tolerateFailure) return [];
|
|
13540
|
+
throw classifyGitError(failureOf(args, r2));
|
|
13541
|
+
}
|
|
13542
|
+
const fields2 = nulFields(r2.stdout);
|
|
13543
|
+
const rows = [];
|
|
13544
|
+
for (let i = 0; i < fields2.length; i += 1) {
|
|
13545
|
+
const record6 = fields2[i];
|
|
13546
|
+
if (record6.length < 4) continue;
|
|
13547
|
+
const status2 = record6.slice(0, 2).trim();
|
|
13548
|
+
const row4 = { status: status2, path: record6.slice(3) };
|
|
13549
|
+
if (status2.includes("R") || status2.includes("C")) row4.originalPath = fields2[++i];
|
|
13550
|
+
rows.push(row4);
|
|
13551
|
+
}
|
|
13552
|
+
return rows;
|
|
13553
|
+
}
|
|
13554
|
+
function conflictedPaths(dir) {
|
|
13555
|
+
return nulFields(mustGit(dir, ["diff", "--name-only", "--no-renames", "-z", "--diff-filter=U"]));
|
|
13556
|
+
}
|
|
13535
13557
|
function statusRows(dir, prefix) {
|
|
13536
|
-
|
|
13537
|
-
if (r2.status !== 0) return [];
|
|
13538
|
-
return r2.stdout.split("\n").map((l) => l.trimEnd()).filter((l) => l.length > 0).map((l) => ({ status: l.slice(0, 2).trim(), path: l.slice(3) }));
|
|
13558
|
+
return readStatusRows(dir, { prefix, tolerateFailure: true, renamePolicy: "configured" });
|
|
13539
13559
|
}
|
|
13540
13560
|
function countUncommitted(boardPath, prefix) {
|
|
13541
13561
|
return statusRows(boardPath, prefix).length;
|
|
@@ -21506,7 +21526,7 @@ var init_nofollow_read = __esm({
|
|
|
21506
21526
|
}
|
|
21507
21527
|
});
|
|
21508
21528
|
|
|
21509
|
-
// ../
|
|
21529
|
+
// ../core/src/portable-export.ts
|
|
21510
21530
|
import { createHash as createHash6 } from "node:crypto";
|
|
21511
21531
|
function crc32(bytes) {
|
|
21512
21532
|
let crc = 4294967295;
|
|
@@ -21645,7 +21665,7 @@ function verifyExport(archive, bundleId, options = {}) {
|
|
|
21645
21665
|
}
|
|
21646
21666
|
if (!isRecord7(manifest)) throw new ExportArchiveError("manifest_mismatch", "the manifest is not an object");
|
|
21647
21667
|
if (manifest.format !== EXPORT_FORMAT) {
|
|
21648
|
-
throw new ExportArchiveError("unsupported_format", `the export format is ${JSON.stringify(manifest.format)}; this
|
|
21668
|
+
throw new ExportArchiveError("unsupported_format", `the export format is ${JSON.stringify(manifest.format)}; this reader accepts ${EXPORT_FORMAT}`);
|
|
21649
21669
|
}
|
|
21650
21670
|
const source = manifest.source;
|
|
21651
21671
|
const counts = manifest.counts;
|
|
@@ -21712,25 +21732,81 @@ function verifyExport(archive, bundleId, options = {}) {
|
|
|
21712
21732
|
if (collision) {
|
|
21713
21733
|
throw new ExportArchiveError("unsafe_path", `the archive holds paths one folder cannot keep apart: ${JSON.stringify(collision[0])} and ${JSON.stringify(collision[1])}`);
|
|
21714
21734
|
}
|
|
21715
|
-
return {
|
|
21735
|
+
return rememberVerified({
|
|
21716
21736
|
source: { tenantId: source.tenantId, bundleId: source.bundleId, revision: source.revision, okfEdition: source.okfEdition },
|
|
21717
21737
|
exportedAt: manifest.exportedAt,
|
|
21718
21738
|
counts: tally,
|
|
21719
21739
|
entries,
|
|
21720
21740
|
bytes,
|
|
21721
21741
|
...page ? { page } : {}
|
|
21742
|
+
}, [{ sha256: sha256Hex2(manifests[0].data), bytes: manifests[0].data.byteLength }], archive.byteLength);
|
|
21743
|
+
}
|
|
21744
|
+
function inventoryOf(verified2) {
|
|
21745
|
+
return verified2.entries.map((entry) => ({
|
|
21746
|
+
path: entry.path,
|
|
21747
|
+
kind: entry.kind,
|
|
21748
|
+
bytes: entry.bytes.byteLength,
|
|
21749
|
+
sha256: sha256Hex2(entry.bytes)
|
|
21750
|
+
})).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
21751
|
+
}
|
|
21752
|
+
function fingerprintOf(verified2) {
|
|
21753
|
+
return JSON.stringify({
|
|
21754
|
+
source: verified2.source,
|
|
21755
|
+
exportedAt: verified2.exportedAt,
|
|
21756
|
+
counts: verified2.counts,
|
|
21757
|
+
bytes: verified2.bytes,
|
|
21758
|
+
page: verified2.page,
|
|
21759
|
+
entries: verified2.entries.map((entry) => ({
|
|
21760
|
+
path: entry.path,
|
|
21761
|
+
kind: entry.kind,
|
|
21762
|
+
bytes: entry.bytes.byteLength,
|
|
21763
|
+
sha256: sha256Hex2(entry.bytes)
|
|
21764
|
+
}))
|
|
21765
|
+
});
|
|
21766
|
+
}
|
|
21767
|
+
function rememberVerified(verified2, manifests, archiveBytes) {
|
|
21768
|
+
const snapshot2 = {
|
|
21769
|
+
source: { ...verified2.source },
|
|
21770
|
+
exportedAt: verified2.exportedAt,
|
|
21771
|
+
counts: { ...verified2.counts },
|
|
21772
|
+
entries: verified2.entries.map((entry) => ({ ...entry, bytes: Uint8Array.from(entry.bytes) })),
|
|
21773
|
+
bytes: verified2.bytes,
|
|
21774
|
+
...verified2.page ? { page: { ...verified2.page, counts: { ...verified2.page.counts } } } : {}
|
|
21775
|
+
};
|
|
21776
|
+
const state = {
|
|
21777
|
+
snapshot: snapshot2,
|
|
21778
|
+
archiveBytes,
|
|
21779
|
+
fingerprint: fingerprintOf(verified2),
|
|
21780
|
+
provenance: {
|
|
21781
|
+
source: { ...verified2.source },
|
|
21782
|
+
exportedAt: verified2.exportedAt,
|
|
21783
|
+
manifests: manifests.map((manifest) => ({ ...manifest })),
|
|
21784
|
+
inventorySha256: sha256Hex2(new TextEncoder().encode(JSON.stringify(inventoryOf(verified2))))
|
|
21785
|
+
}
|
|
21722
21786
|
};
|
|
21787
|
+
verifiedStates.set(verified2, state);
|
|
21788
|
+
verifiedStates.set(snapshot2, state);
|
|
21789
|
+
return verified2;
|
|
21723
21790
|
}
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
21791
|
+
function exportProvenance(verified2) {
|
|
21792
|
+
const state = verifiedStates.get(verified2);
|
|
21793
|
+
if (!state || state.fingerprint !== fingerprintOf(verified2)) {
|
|
21794
|
+
throw new ExportArchiveError("manifest_mismatch", "the verified export handle is absent or changed");
|
|
21795
|
+
}
|
|
21796
|
+
return {
|
|
21797
|
+
...state.provenance,
|
|
21798
|
+
source: { ...state.provenance.source },
|
|
21799
|
+
manifests: state.provenance.manifests.map((manifest) => ({ ...manifest }))
|
|
21800
|
+
};
|
|
21801
|
+
}
|
|
21802
|
+
var EXPORT_MANIFEST, EXPORT_FORMAT, MAX_EXPORT_ENTRIES, MAX_EXPORT_BYTES, LOCAL, CENTRAL, END, UTF8_NAMES, ExportArchiveError, CRC_TABLE, sha256Hex2, utf8, WINDOWS_DEVICE_NAMES, fold3, ExportPageChain, verifiedStates;
|
|
21803
|
+
var init_portable_export = __esm({
|
|
21804
|
+
"../core/src/portable-export.ts"() {
|
|
21727
21805
|
"use strict";
|
|
21728
21806
|
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
21729
21807
|
init_define_SUPERBEE_UPDATE_POLICY();
|
|
21730
|
-
|
|
21808
|
+
init_paths();
|
|
21731
21809
|
EXPORT_MANIFEST = "superbee-export.json";
|
|
21732
|
-
IN_PLACE_STAGING = ".superbee-export-partial";
|
|
21733
|
-
IN_PLACE_JOURNAL = "journal.json";
|
|
21734
21810
|
EXPORT_FORMAT = "superbee-export/1";
|
|
21735
21811
|
MAX_EXPORT_ENTRIES = 20001;
|
|
21736
21812
|
MAX_EXPORT_BYTES = 128 * 1024 * 1024;
|
|
@@ -21763,17 +21839,23 @@ var init_export_archive = __esm({
|
|
|
21763
21839
|
#pages = [];
|
|
21764
21840
|
#from = 0;
|
|
21765
21841
|
#ended = false;
|
|
21842
|
+
#archiveBytes = 0;
|
|
21766
21843
|
/** Adds the next page; answers its next cursor, or undefined once the export is whole. */
|
|
21767
21844
|
add(verified2) {
|
|
21768
|
-
|
|
21769
|
-
const
|
|
21845
|
+
exportProvenance(verified2);
|
|
21846
|
+
const state = verifiedStates.get(verified2);
|
|
21847
|
+
const accepted = state.snapshot;
|
|
21848
|
+
const page = accepted.page;
|
|
21849
|
+
const first = this.#pages[0] ?? accepted;
|
|
21770
21850
|
if (!page || !first.page || this.#ended) throw new ExportArchiveError("manifest_mismatch", "a paged export answered a page outside its run");
|
|
21771
21851
|
const counts = first.page.counts;
|
|
21772
|
-
if (
|
|
21852
|
+
if (accepted.source.tenantId !== first.source.tenantId || accepted.source.bundleId !== first.source.bundleId || accepted.source.revision !== first.source.revision || accepted.source.okfEdition !== first.source.okfEdition || page.counts.documents !== counts.documents || page.counts.reserved !== counts.reserved || page.counts.blobs !== counts.blobs || page.total !== first.page.total || page.total !== counts.documents + counts.reserved + counts.blobs || page.total > MAX_EXPORT_ENTRIES - 1) {
|
|
21773
21853
|
throw new ExportArchiveError("manifest_mismatch", "the pages do not describe one export of one revision");
|
|
21774
21854
|
}
|
|
21775
21855
|
if (page.from !== this.#from) throw new ExportArchiveError("manifest_mismatch", "the pages leave a gap or overlap");
|
|
21776
|
-
this.#pages
|
|
21856
|
+
if (state.archiveBytes > MAX_EXPORT_BYTES - this.#archiveBytes) throw malformed2("the pages are larger than an export carries");
|
|
21857
|
+
this.#archiveBytes += state.archiveBytes;
|
|
21858
|
+
this.#pages.push(accepted);
|
|
21777
21859
|
this.#from += page.count;
|
|
21778
21860
|
if (page.next === void 0) this.#ended = true;
|
|
21779
21861
|
return page.next;
|
|
@@ -21788,7 +21870,7 @@ var init_export_archive = __esm({
|
|
|
21788
21870
|
let bytes = 0;
|
|
21789
21871
|
for (const verified2 of this.#pages) {
|
|
21790
21872
|
for (const entry of verified2.entries) {
|
|
21791
|
-
entries.push(entry);
|
|
21873
|
+
entries.push({ ...entry, bytes: Uint8Array.from(entry.bytes) });
|
|
21792
21874
|
tally[entry.kind === "document" ? "documents" : entry.kind === "reserved" ? "reserved" : "blobs"] += 1;
|
|
21793
21875
|
bytes += entry.bytes.byteLength;
|
|
21794
21876
|
}
|
|
@@ -21806,9 +21888,23 @@ var init_export_archive = __esm({
|
|
|
21806
21888
|
if (collision) {
|
|
21807
21889
|
throw new ExportArchiveError("unsafe_path", `the archive holds paths one folder cannot keep apart: ${JSON.stringify(collision[0])} and ${JSON.stringify(collision[1])}`);
|
|
21808
21890
|
}
|
|
21809
|
-
return { source: first.source, exportedAt: first.exportedAt, counts: tally, entries, bytes };
|
|
21891
|
+
return rememberVerified({ source: { ...first.source }, exportedAt: first.exportedAt, counts: tally, entries, bytes }, this.#pages.flatMap((verified2) => exportProvenance(verified2).manifests), this.#archiveBytes);
|
|
21810
21892
|
}
|
|
21811
21893
|
};
|
|
21894
|
+
verifiedStates = /* @__PURE__ */ new WeakMap();
|
|
21895
|
+
}
|
|
21896
|
+
});
|
|
21897
|
+
|
|
21898
|
+
// ../cli/src/hosted/export-archive.ts
|
|
21899
|
+
var IN_PLACE_STAGING, IN_PLACE_JOURNAL;
|
|
21900
|
+
var init_export_archive = __esm({
|
|
21901
|
+
"../cli/src/hosted/export-archive.ts"() {
|
|
21902
|
+
"use strict";
|
|
21903
|
+
init_define_SUPERBEE_BUILD_IDENTITY();
|
|
21904
|
+
init_define_SUPERBEE_UPDATE_POLICY();
|
|
21905
|
+
init_portable_export();
|
|
21906
|
+
IN_PLACE_STAGING = ".superbee-export-partial";
|
|
21907
|
+
IN_PLACE_JOURNAL = "journal.json";
|
|
21812
21908
|
}
|
|
21813
21909
|
});
|
|
21814
21910
|
|
|
@@ -26582,10 +26678,12 @@ function chunked(items, size) {
|
|
|
26582
26678
|
for (let start = 0; start < items.length; start += size) out.push(items.slice(start, start + size));
|
|
26583
26679
|
return out;
|
|
26584
26680
|
}
|
|
26585
|
-
function
|
|
26586
|
-
const
|
|
26587
|
-
if (!Number.isInteger(
|
|
26588
|
-
|
|
26681
|
+
function admitFetchOptions(options) {
|
|
26682
|
+
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
26683
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) throw new InvalidInputError(`concurrency must be an integer of at least 1, got ${String(concurrency)}`);
|
|
26684
|
+
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
26685
|
+
if (!Number.isSafeInteger(batchSize) || batchSize < 1) throw new InvalidInputError(`batchSize must be a safe integer of at least 1, got ${String(batchSize)}`);
|
|
26686
|
+
return { batchSize, concurrency };
|
|
26589
26687
|
}
|
|
26590
26688
|
async function forEachBatch(batches, concurrency, work) {
|
|
26591
26689
|
let next = 0;
|
|
@@ -26705,7 +26803,7 @@ async function wireFor(remote, local, feature, options) {
|
|
|
26705
26803
|
return (await capabilitiesOf(local, wire))[feature] ? wire : null;
|
|
26706
26804
|
}
|
|
26707
26805
|
async function bootstrap(remote, local, options = {}) {
|
|
26708
|
-
const concurrency =
|
|
26806
|
+
const { batchSize, concurrency } = admitFetchOptions(options);
|
|
26709
26807
|
const backend = await runtimeBackend(local);
|
|
26710
26808
|
const bodyMode2 = await admitBodyMode(backendOf(local));
|
|
26711
26809
|
const validateReadSide = bodyMode2 ? () => assertBodyRemoteEdition(remote, bodyMode2) : void 0;
|
|
@@ -26724,7 +26822,6 @@ async function bootstrap(remote, local, options = {}) {
|
|
|
26724
26822
|
const rootIndex = await remote.readReserved("", "index.md");
|
|
26725
26823
|
if (rootIndex) await backend.writeReserved("", "index.md", rootIndex.content);
|
|
26726
26824
|
}
|
|
26727
|
-
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
26728
26825
|
const findings = [];
|
|
26729
26826
|
const held2 = [];
|
|
26730
26827
|
const written = /* @__PURE__ */ new Map();
|
|
@@ -26984,6 +27081,16 @@ async function retireQuietly(backend, mode, target3) {
|
|
|
26984
27081
|
function conflictResolutionKey(id) {
|
|
26985
27082
|
return `conflict-resolution:${id}`;
|
|
26986
27083
|
}
|
|
27084
|
+
function documentResolutionKey(id) {
|
|
27085
|
+
return `conflict-resolution-document:${id}`;
|
|
27086
|
+
}
|
|
27087
|
+
async function readConflictResolution(local, id) {
|
|
27088
|
+
const backend = backendOf(local);
|
|
27089
|
+
const identity = await backend.readMeta(documentResolutionKey(id));
|
|
27090
|
+
if (typeof identity !== "string") return void 0;
|
|
27091
|
+
const receipt3 = await backend.readMeta(conflictResolutionKey(identity));
|
|
27092
|
+
return receipt3?.id === identity && receipt3.reviewed?.id === id ? receipt3 : void 0;
|
|
27093
|
+
}
|
|
26987
27094
|
async function readConflictRemote(remote, id) {
|
|
26988
27095
|
try {
|
|
26989
27096
|
const read = await remote.read(id);
|
|
@@ -27054,7 +27161,7 @@ async function resolveConflict(local, remote, reviewed, choice, options = {}) {
|
|
|
27054
27161
|
if (reviewedShared.version !== review.remote.version || reviewedShared.content !== review.remote.content || reviewedShared.tombstone !== review.remote.tombstone) throw new ConflictReviewStaleError();
|
|
27055
27162
|
if (current.deleting && selected2.kind === "revise") throw new InvalidInputError("A deletion in conflict is resolved by keeping the deletion or taking the shared version.");
|
|
27056
27163
|
const resolvedAt = options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
27057
|
-
const requestId2 = selected2.kind === "take-remote" ? null : mintRequestId();
|
|
27164
|
+
const requestId2 = selected2.kind === "take-remote" || current.deleting && shared.base.version === null ? null : mintRequestId();
|
|
27058
27165
|
const receipt3 = {
|
|
27059
27166
|
// The original conflict identity lets a caller recover the receipt after a lost local reply.
|
|
27060
27167
|
id: current.intents[0].requestId,
|
|
@@ -27066,6 +27173,7 @@ async function resolveConflict(local, remote, reviewed, choice, options = {}) {
|
|
|
27066
27173
|
const resolveIntents = { expected: current.intents };
|
|
27067
27174
|
const meta3 = [
|
|
27068
27175
|
{ key: conflictResolutionKey(receipt3.id), value: receipt3 },
|
|
27176
|
+
{ key: documentResolutionKey(review.id), value: receipt3.id },
|
|
27069
27177
|
// Someone else's deletion is never remembered as this working copy's own: a later create of
|
|
27070
27178
|
// the id acknowledges nothing and meets the conflict again, until an explicit keep.
|
|
27071
27179
|
{ key: baseKey(review.id), value: { version: shared.base.version, content: shared.base.content } }
|
|
@@ -27074,7 +27182,7 @@ async function resolveConflict(local, remote, reviewed, choice, options = {}) {
|
|
|
27074
27182
|
if (current.deleting && selected2.kind === "keep-local") {
|
|
27075
27183
|
if (shared.base.version === null) {
|
|
27076
27184
|
await backend.deleteJournaled(review.id, common);
|
|
27077
|
-
return { receipt:
|
|
27185
|
+
return { receipt: receipt3, version: null, intent: null };
|
|
27078
27186
|
}
|
|
27079
27187
|
const deleted = await backend.deleteJournaled(review.id, {
|
|
27080
27188
|
...common,
|
|
@@ -27538,7 +27646,7 @@ async function lastKnownDigest(backend) {
|
|
|
27538
27646
|
return { digest: marker.headsDigest, lastPull: read };
|
|
27539
27647
|
}
|
|
27540
27648
|
async function pull(local, remote, options = {}) {
|
|
27541
|
-
const concurrency =
|
|
27649
|
+
const { batchSize, concurrency } = admitFetchOptions(options);
|
|
27542
27650
|
const backend = await runtimeBackend(local);
|
|
27543
27651
|
const bodyMode2 = await admitBodyMode(backendOf(local));
|
|
27544
27652
|
const validateReadSide = bodyMode2 ? () => assertBodyRemoteEdition(remote, bodyMode2) : void 0;
|
|
@@ -27638,7 +27746,7 @@ async function pull(local, remote, options = {}) {
|
|
|
27638
27746
|
}
|
|
27639
27747
|
};
|
|
27640
27748
|
let consistent = true;
|
|
27641
|
-
const fetchAndApply = (candidates2, listing) => forEachBatch(chunked(candidates2,
|
|
27749
|
+
const fetchAndApply = (candidates2, listing) => forEachBatch(chunked(candidates2, batchSize), concurrency, async (batch) => {
|
|
27642
27750
|
if (superseded) return;
|
|
27643
27751
|
const premises2 = bodyMode2 ? await captureBodyRefresh(backendOf(local), bodyMode2, batch) : void 0;
|
|
27644
27752
|
if (!listing) {
|
|
@@ -37502,33 +37610,40 @@ function resolvedRecord(binding, id, choice, fileState, sends, already = false,
|
|
|
37502
37610
|
help: sends ? [sync3] : []
|
|
37503
37611
|
};
|
|
37504
37612
|
}
|
|
37505
|
-
function
|
|
37506
|
-
return `cli-resolved:${id}`;
|
|
37613
|
+
function folderResolutionKey(id) {
|
|
37614
|
+
return `cli-folder-resolved:${id}`;
|
|
37507
37615
|
}
|
|
37508
|
-
async function
|
|
37616
|
+
async function beginFolderResolution(store, id, choice) {
|
|
37509
37617
|
if (choice === "take") return;
|
|
37510
37618
|
const after = (await store.listIntents()).reduce((max, row4) => Math.max(max, row4.sequence), 0);
|
|
37511
|
-
await store.writeMeta(
|
|
37619
|
+
await store.writeMeta(folderResolutionKey(id), { choice, after });
|
|
37512
37620
|
}
|
|
37513
|
-
async function
|
|
37621
|
+
async function recordFolderResolution(store, id, choice) {
|
|
37514
37622
|
if (choice === "take") {
|
|
37515
|
-
await store.writeMeta(
|
|
37623
|
+
await store.writeMeta(folderResolutionKey(id), null);
|
|
37516
37624
|
return;
|
|
37517
37625
|
}
|
|
37518
37626
|
const requestIds = (await store.readWithJournal(id)).intents.filter((row4) => row4.state !== "acknowledged").map((row4) => row4.requestId);
|
|
37519
|
-
await store.writeMeta(
|
|
37627
|
+
await store.writeMeta(folderResolutionKey(id), { choice, requestIds });
|
|
37520
37628
|
}
|
|
37521
37629
|
function isResolution(record6, row4) {
|
|
37522
|
-
|
|
37523
|
-
if (Array.isArray(record6.requestIds)) return record6.requestIds.includes(row4.requestId);
|
|
37524
|
-
return typeof record6.after === "number" && row4.sequence > record6.after;
|
|
37630
|
+
return !!record6?.choice && Array.isArray(record6.requestIds) && record6.requestIds.includes(row4.requestId);
|
|
37525
37631
|
}
|
|
37526
37632
|
async function resolveAgain(session, id, choice, resumeCommand) {
|
|
37527
|
-
const { binding, store, projection } = session;
|
|
37633
|
+
const { binding, store, projection, local } = session;
|
|
37528
37634
|
const file2 = path38.join(binding.path, `${id}.md`);
|
|
37529
|
-
const earlier = await store.readMeta(resolvedKey(id));
|
|
37530
37635
|
const rows = (await store.readWithJournal(id)).intents.filter((row5) => row5.state !== "acknowledged");
|
|
37636
|
+
const receipt3 = await readConflictResolution(local, id);
|
|
37637
|
+
const folder = await store.readMeta(folderResolutionKey(id));
|
|
37638
|
+
const journal = receipt3 ? { choice: receipt3.choice === "keep-local" ? "keep" : receipt3.choice === "revise" ? "revise" : "take", requestIds: receipt3.replacementRequestId === null ? [] : [receipt3.replacementRequestId] } : void 0;
|
|
37639
|
+
const earlier = folder && rows[0] && isResolution(folder, rows[0]) ? folder : journal ?? folder ?? await store.readMeta(`cli-resolved:${id}`);
|
|
37531
37640
|
const sync3 = syncCommand2(binding);
|
|
37641
|
+
if (earlier?.choice && rows.length > 0 && !Array.isArray(earlier.requestIds) && typeof earlier.after === "number") {
|
|
37642
|
+
throw new CliError("CONFLICT", `'${id}' has an interrupted resolution whose replacement identity is unknown; its pending work is preserved`, {
|
|
37643
|
+
details: { reason: "resolution_identity_unknown", id, earlier: earlier.choice, states: rows.map((row5) => row5.state) },
|
|
37644
|
+
help: `run ${sync3} to send the existing edit; if it reports a conflict on '${id}', inspect and resolve that one`
|
|
37645
|
+
});
|
|
37646
|
+
}
|
|
37532
37647
|
if (!earlier?.choice || rows.length === 0 || !isResolution(earlier, rows[0])) {
|
|
37533
37648
|
throw new CliError("CONFLICT", `'${id}' has an unsent change but no conflict to resolve; the next sync sends it`, {
|
|
37534
37649
|
details: { reason: "unsent_change", id, states: rows.map((row5) => row5.state) },
|
|
@@ -37557,11 +37672,14 @@ async function resolveAgain(session, id, choice, resumeCommand) {
|
|
|
37557
37672
|
await store.writeJournaled(id, { id, frontmatter: parsed2.frontmatter, body: parsed2.body }, {
|
|
37558
37673
|
expectedVersion: current.document?.version ?? null,
|
|
37559
37674
|
supersede: { requestId: row4.requestId, expectedState: "pending", expectedAttempts: 0 },
|
|
37560
|
-
meta: [
|
|
37675
|
+
meta: [
|
|
37676
|
+
{ key: baseKey(id), value: { version: row4.base, content: row4.baseContent } },
|
|
37677
|
+
{ key: folderResolutionKey(id), value: null },
|
|
37678
|
+
{ key: `cli-resolved:${id}`, value: null }
|
|
37679
|
+
]
|
|
37561
37680
|
});
|
|
37562
37681
|
if (bytes === null) delete projection.files[id];
|
|
37563
37682
|
const exported = await exportCheckout(binding.path, store, projection, { only: /* @__PURE__ */ new Set([id]), placeMissing: true });
|
|
37564
|
-
await store.writeMeta(resolvedKey(id), null);
|
|
37565
37683
|
await session.persist();
|
|
37566
37684
|
return resolvedRecord(binding, id, choice, exported.placed.length > 0 ? bytes === null ? "restored" : "replaced" : "unchanged", false, false, earlier.choice);
|
|
37567
37685
|
}
|
|
@@ -37830,10 +37948,10 @@ async function runResolve2(binding, values, deps, mode) {
|
|
|
37830
37948
|
throw error51;
|
|
37831
37949
|
}
|
|
37832
37950
|
await assertInspectedCurrent(session, id, remoteVersionOf(conflict), choice);
|
|
37833
|
-
await beginResolution(store, id, choice);
|
|
37834
37951
|
let fileState;
|
|
37835
37952
|
if (conflict.kind === "folder") {
|
|
37836
37953
|
if (conflict.deleted && choice !== "take") await assertInspectedTombstone(session, id, null, choice);
|
|
37954
|
+
await beginFolderResolution(store, id, choice);
|
|
37837
37955
|
fileState = await resolveFolder(session, id, choice, conflict, resumeCommand);
|
|
37838
37956
|
} else {
|
|
37839
37957
|
const deleting = conflict.review.local.deleted === true;
|
|
@@ -37891,7 +38009,7 @@ async function runResolve2(binding, values, deps, mode) {
|
|
|
37891
38009
|
}
|
|
37892
38010
|
}
|
|
37893
38011
|
await store.writeMeta(inspectedKey(id), null);
|
|
37894
|
-
await
|
|
38012
|
+
if (conflict.kind === "folder") await recordFolderResolution(store, id, choice);
|
|
37895
38013
|
await session.persist();
|
|
37896
38014
|
return resolvedRecord(binding, id, choice, fileState, choice !== "take");
|
|
37897
38015
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superbee",
|
|
3
|
-
"version": "0.4.2-pre.
|
|
3
|
+
"version": "0.4.2-pre.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-facing Superbee CLI for reading and writing local OKF knowledge bundles: context notes, docs, cross-links, and live bundle Views.",
|
|
6
6
|
"keywords": [
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"./bundle-descriptor": {
|
|
38
38
|
"types": "./dist/bundle-descriptor/index.d.ts",
|
|
39
39
|
"default": "./dist/bundle-descriptor.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./portable-export": {
|
|
42
|
+
"types": "./dist/portable-export.d.ts",
|
|
43
|
+
"default": "./dist/portable-export.mjs"
|
|
40
44
|
}
|
|
41
45
|
},
|
|
42
46
|
"files": [
|