superbee 0.1.4-pre.1 → 0.1.5-pre.1

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/README.md CHANGED
@@ -40,24 +40,32 @@ know invisible to the humans they work for. Superbee fixes all three:
40
40
  The npm package ships one self-contained executable with zero runtime dependencies, plus an
41
41
  Agent Skill that teaches agents how to use it.
42
42
 
43
- ## Install Steps
43
+ ## How do I download Superbee on Windows?
44
44
 
45
- **Requirements: Node.js 20 or newer on macOS, Linux, or Windows.** On Windows, Superbee keeps
46
- per-user operational state under `%LOCALAPPDATA%\Superbee` and uses Windows-native containment and
47
- path-identity checks instead of relying on synthesized POSIX mode bits. Native Windows behavior and
48
- the globally installed `superbee.cmd` entrypoint are covered by the required CI contract.
45
+ **Requirements: Node.js 20 or newer on macOS, Linux, or native Windows.** You do not need WSL,
46
+ Ubuntu, or Docker. On Windows, Superbee keeps per-user operational state under
47
+ `%LOCALAPPDATA%\Superbee`; npm installs the command as `superbee.cmd`.
49
48
 
50
- 1. Install the CLI globally:
49
+ Superbee currently has two npm release channels:
51
50
 
52
- ```sh
53
- npm install -g superbee
54
- ```
51
+ - `latest` is the stable channel selected by bare `superbee`. Its current release predates native
52
+ Windows support.
53
+ - `next` is the prerelease channel. It contains the current native-Windows build.
55
54
 
56
- 2. Once superbee is installed, ask your AI agent to run `superbee setup`. Setup walks the
57
- agent through the remaining integration steps (Agent Skill, SessionStart hook, MCP
58
- registration) and orients it to the Superbee environment. It is read-only — it inspects
59
- your configuration and returns one safe next command at a time, so the agent (with your
60
- approval) performs any actual changes.
55
+ Install the current prerelease on Windows:
56
+
57
+ ```powershell
58
+ npm install -g superbee@next
59
+ superbee.cmd setup
60
+ ```
61
+
62
+ On macOS or Linux, the stable channel remains available with `npm install -g superbee`. To test the
63
+ same prerelease as Windows, install `superbee@next` instead.
64
+
65
+ After installation, ask your AI agent to run `superbee setup` (`superbee.cmd setup` when invoking
66
+ the Windows shim explicitly). Setup walks the agent through Agent Skill, SessionStart hook, and MCP
67
+ registration. It is read-only: it inspects your configuration and returns one safe next command at
68
+ a time, so the agent performs any actual changes with your approval.
61
69
 
62
70
  Upgrading from the legacy `@holaxis/aslite` package or the retired marketplace plugin? Install
63
71
  `superbee` alongside it, have your agent run `superbee setup` to migrate the exact legacy
@@ -3947,46 +3947,69 @@ async function selectLockRoot(options2) {
3947
3947
  await ensurePrivateLockRoot(lockRoot);
3948
3948
  return lockRoot;
3949
3949
  }
3950
+ var WINDOWS_CLAIM_CONTENTION_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
3951
+ async function classifyLockClaimFailure(error, lockPath) {
3952
+ const code = error.code;
3953
+ if (code === "EEXIST") return "contention";
3954
+ if (process.platform !== "win32" || !WINDOWS_CLAIM_CONTENTION_CODES.has(code ?? "")) return "terminal";
3955
+ try {
3956
+ await fs.lstat(lockPath);
3957
+ return "contention";
3958
+ } catch (probeError) {
3959
+ const probeCode = probeError.code;
3960
+ return probeCode === "ENOENT" || probeCode === "ENOTDIR" ? "unwitnessed-windows-sharing-error" : "terminal";
3961
+ }
3962
+ }
3950
3963
  async function claimLockPath(lockPath, owner, waitMs, pollMs) {
3951
3964
  const started = owner.created_at_ms;
3965
+ let unwitnessedWindowsRetryUsed = false;
3952
3966
  while (true) {
3953
3967
  try {
3954
3968
  await fs.mkdir(lockPath, { mode: 448 });
3955
- try {
3956
- await fs.writeFile(path.join(lockPath, OWNER_FILE), `${JSON.stringify(owner)}
3957
- `, {
3958
- encoding: "utf8",
3959
- flag: "wx",
3960
- mode: 384
3961
- });
3962
- } catch (err) {
3963
- await fs.rm(lockPath, { recursive: true, force: true }).catch(() => {
3964
- });
3965
- throw err;
3969
+ } catch (err) {
3970
+ const failure = await classifyLockClaimFailure(err, lockPath);
3971
+ if (failure === "terminal") throw err;
3972
+ if (failure === "unwitnessed-windows-sharing-error") {
3973
+ if (unwitnessedWindowsRetryUsed) throw err;
3974
+ unwitnessedWindowsRetryUsed = true;
3975
+ continue;
3976
+ } else {
3977
+ unwitnessedWindowsRetryUsed = false;
3966
3978
  }
3967
- return async () => {
3968
- const current = await readOwner(lockPath);
3969
- if (current?.token !== owner.token) {
3970
- throw new FilesystemMutationLockError(
3971
- `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
3972
- { lockPath, owner: current, stale: false, malformed: current === null }
3973
- );
3974
- }
3975
- try {
3976
- await fs.rm(lockPath, { recursive: true, force: false });
3977
- } catch (err) {
3978
- const message = err instanceof Error ? err.message : String(err);
3979
- throw new FilesystemMutationLockError(
3980
- `mutation completed but filesystem lock '${lockPath}' could not be removed (${message}); inspect the lock before retrying.`,
3981
- { lockPath, owner: current, stale: false, malformed: false }
3982
- );
3983
- }
3984
- };
3979
+ if (Date.now() - started >= waitMs) throw timeoutError(lockPath, await readOwner(lockPath), owner.target);
3980
+ await delay(pollMs);
3981
+ continue;
3982
+ }
3983
+ try {
3984
+ await fs.writeFile(path.join(lockPath, OWNER_FILE), `${JSON.stringify(owner)}
3985
+ `, {
3986
+ encoding: "utf8",
3987
+ flag: "wx",
3988
+ mode: 384
3989
+ });
3985
3990
  } catch (err) {
3986
- if (err.code !== "EEXIST") throw err;
3991
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => {
3992
+ });
3993
+ throw err;
3987
3994
  }
3988
- if (Date.now() - started >= waitMs) throw timeoutError(lockPath, await readOwner(lockPath), owner.target);
3989
- await delay(pollMs);
3995
+ return async () => {
3996
+ const current = await readOwner(lockPath);
3997
+ if (current?.token !== owner.token) {
3998
+ throw new FilesystemMutationLockError(
3999
+ `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
4000
+ { lockPath, owner: current, stale: false, malformed: current === null }
4001
+ );
4002
+ }
4003
+ try {
4004
+ await fs.rm(lockPath, { recursive: true, force: false });
4005
+ } catch (err) {
4006
+ const message = err instanceof Error ? err.message : String(err);
4007
+ throw new FilesystemMutationLockError(
4008
+ `mutation completed but filesystem lock '${lockPath}' could not be removed (${message}); inspect the lock before retrying.`,
4009
+ { lockPath, owner: current, stale: false, malformed: false }
4010
+ );
4011
+ }
4012
+ };
3990
4013
  }
3991
4014
  }
3992
4015
  function newOwner(target) {
@@ -5038,6 +5061,10 @@ var VALID_FIELDS_KEYS = /* @__PURE__ */ new Set([
5038
5061
  "descriptions"
5039
5062
  ]);
5040
5063
  var MISPLACED_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["enum", "enums", "values", "constraints"]);
5064
+ var CLAIM_COORDINATE_KEYS = [
5065
+ ["owner_field", "ownerField"],
5066
+ ["state_field", "stateField"]
5067
+ ];
5041
5068
  function parseConventionDoc(doc) {
5042
5069
  const fm = doc.frontmatter;
5043
5070
  const governs = typeof fm.governs === "string" ? fm.governs.trim() : "";
@@ -5346,6 +5373,35 @@ function parseConventionDoc(doc) {
5346
5373
  if (Object.keys(parsed).length > 0) expectsInbound = parsed;
5347
5374
  }
5348
5375
  }
5376
+ const claimSource = fm.claim;
5377
+ let claim;
5378
+ if (claimSource !== void 0) {
5379
+ if (!isPlainObject(claimSource)) {
5380
+ warnings.push({
5381
+ code: "KIND_CONVENTION_BAD_SHAPE",
5382
+ message: `kind convention '${doc.id}' has a non-map 'claim' key (${describeShape(claimSource)}; expected a map declaring 'owner_field' and/or 'state_field'); ignoring it.`,
5383
+ field: "claim",
5384
+ severity: "warning"
5385
+ });
5386
+ } else {
5387
+ const parsed = {};
5388
+ for (const [key, target] of CLAIM_COORDINATE_KEYS) {
5389
+ const declared = claimSource[key];
5390
+ if (declared === void 0) continue;
5391
+ if (!isScalar(declared) || String(declared).trim() === "") {
5392
+ warnings.push({
5393
+ code: "KIND_CONVENTION_BAD_MEMBER",
5394
+ message: `kind convention '${doc.id}' has a malformed 'claim.${key}' (${describeShape(declared)}; expected a declared field name); skipping it.`,
5395
+ field: `claim.${key}`,
5396
+ severity: "warning"
5397
+ });
5398
+ continue;
5399
+ }
5400
+ parsed[target] = String(declared).trim();
5401
+ }
5402
+ if (parsed.ownerField !== void 0 || parsed.stateField !== void 0) claim = parsed;
5403
+ }
5404
+ }
5349
5405
  const sections = Array.isArray(fm.sections) ? fm.sections.filter((s) => typeof s === "string" && s.trim() !== "") : void 0;
5350
5406
  const title = typeof fm.title === "string" && fm.title.trim() !== "" ? fm.title.trim() : governs;
5351
5407
  let description;
@@ -5378,6 +5434,7 @@ function parseConventionDoc(doc) {
5378
5434
  if (sections && sections.length > 0) kind.sections = sections;
5379
5435
  if (freshnessHorizon !== void 0) kind.freshnessHorizon = freshnessHorizon;
5380
5436
  if (browseCollapsed !== void 0) kind.browseCollapsed = browseCollapsed;
5437
+ if (claim !== void 0) kind.claim = claim;
5381
5438
  return {
5382
5439
  ok: true,
5383
5440
  kind,
@@ -31901,46 +31901,69 @@ async function selectLockRoot(options2) {
31901
31901
  await ensurePrivateLockRoot(lockRoot);
31902
31902
  return lockRoot;
31903
31903
  }
31904
+ var WINDOWS_CLAIM_CONTENTION_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
31905
+ async function classifyLockClaimFailure(error, lockPath) {
31906
+ const code2 = error.code;
31907
+ if (code2 === "EEXIST") return "contention";
31908
+ if (process.platform !== "win32" || !WINDOWS_CLAIM_CONTENTION_CODES.has(code2 ?? "")) return "terminal";
31909
+ try {
31910
+ await fs.lstat(lockPath);
31911
+ return "contention";
31912
+ } catch (probeError) {
31913
+ const probeCode = probeError.code;
31914
+ return probeCode === "ENOENT" || probeCode === "ENOTDIR" ? "unwitnessed-windows-sharing-error" : "terminal";
31915
+ }
31916
+ }
31904
31917
  async function claimLockPath(lockPath, owner, waitMs, pollMs) {
31905
31918
  const started = owner.created_at_ms;
31919
+ let unwitnessedWindowsRetryUsed = false;
31906
31920
  while (true) {
31907
31921
  try {
31908
31922
  await fs.mkdir(lockPath, { mode: 448 });
31909
- try {
31910
- await fs.writeFile(path.join(lockPath, OWNER_FILE), `${JSON.stringify(owner)}
31923
+ } catch (err) {
31924
+ const failure = await classifyLockClaimFailure(err, lockPath);
31925
+ if (failure === "terminal") throw err;
31926
+ if (failure === "unwitnessed-windows-sharing-error") {
31927
+ if (unwitnessedWindowsRetryUsed) throw err;
31928
+ unwitnessedWindowsRetryUsed = true;
31929
+ continue;
31930
+ } else {
31931
+ unwitnessedWindowsRetryUsed = false;
31932
+ }
31933
+ if (Date.now() - started >= waitMs) throw timeoutError(lockPath, await readOwner(lockPath), owner.target);
31934
+ await delay(pollMs);
31935
+ continue;
31936
+ }
31937
+ try {
31938
+ await fs.writeFile(path.join(lockPath, OWNER_FILE), `${JSON.stringify(owner)}
31911
31939
  `, {
31912
- encoding: "utf8",
31913
- flag: "wx",
31914
- mode: 384
31915
- });
31916
- } catch (err) {
31917
- await fs.rm(lockPath, { recursive: true, force: true }).catch(() => {
31918
- });
31919
- throw err;
31920
- }
31921
- return async () => {
31922
- const current = await readOwner(lockPath);
31923
- if (current?.token !== owner.token) {
31924
- throw new FilesystemMutationLockError(
31925
- `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
31926
- { lockPath, owner: current, stale: false, malformed: current === null }
31927
- );
31928
- }
31929
- try {
31930
- await fs.rm(lockPath, { recursive: true, force: false });
31931
- } catch (err) {
31932
- const message = err instanceof Error ? err.message : String(err);
31933
- throw new FilesystemMutationLockError(
31934
- `mutation completed but filesystem lock '${lockPath}' could not be removed (${message}); inspect the lock before retrying.`,
31935
- { lockPath, owner: current, stale: false, malformed: false }
31936
- );
31937
- }
31938
- };
31940
+ encoding: "utf8",
31941
+ flag: "wx",
31942
+ mode: 384
31943
+ });
31939
31944
  } catch (err) {
31940
- if (err.code !== "EEXIST") throw err;
31945
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => {
31946
+ });
31947
+ throw err;
31941
31948
  }
31942
- if (Date.now() - started >= waitMs) throw timeoutError(lockPath, await readOwner(lockPath), owner.target);
31943
- await delay(pollMs);
31949
+ return async () => {
31950
+ const current = await readOwner(lockPath);
31951
+ if (current?.token !== owner.token) {
31952
+ throw new FilesystemMutationLockError(
31953
+ `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
31954
+ { lockPath, owner: current, stale: false, malformed: current === null }
31955
+ );
31956
+ }
31957
+ try {
31958
+ await fs.rm(lockPath, { recursive: true, force: false });
31959
+ } catch (err) {
31960
+ const message = err instanceof Error ? err.message : String(err);
31961
+ throw new FilesystemMutationLockError(
31962
+ `mutation completed but filesystem lock '${lockPath}' could not be removed (${message}); inspect the lock before retrying.`,
31963
+ { lockPath, owner: current, stale: false, malformed: false }
31964
+ );
31965
+ }
31966
+ };
31944
31967
  }
31945
31968
  }
31946
31969
  function newOwner(target) {
@@ -33027,6 +33050,10 @@ var VALID_FIELDS_KEYS = /* @__PURE__ */ new Set([
33027
33050
  "descriptions"
33028
33051
  ]);
33029
33052
  var MISPLACED_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["enum", "enums", "values", "constraints"]);
33053
+ var CLAIM_COORDINATE_KEYS = [
33054
+ ["owner_field", "ownerField"],
33055
+ ["state_field", "stateField"]
33056
+ ];
33030
33057
  function parseConventionDoc(doc) {
33031
33058
  const fm = doc.frontmatter;
33032
33059
  const governs = typeof fm.governs === "string" ? fm.governs.trim() : "";
@@ -33335,6 +33362,35 @@ function parseConventionDoc(doc) {
33335
33362
  if (Object.keys(parsed).length > 0) expectsInbound = parsed;
33336
33363
  }
33337
33364
  }
33365
+ const claimSource = fm.claim;
33366
+ let claim;
33367
+ if (claimSource !== void 0) {
33368
+ if (!isPlainObject(claimSource)) {
33369
+ warnings.push({
33370
+ code: "KIND_CONVENTION_BAD_SHAPE",
33371
+ message: `kind convention '${doc.id}' has a non-map 'claim' key (${describeShape(claimSource)}; expected a map declaring 'owner_field' and/or 'state_field'); ignoring it.`,
33372
+ field: "claim",
33373
+ severity: "warning"
33374
+ });
33375
+ } else {
33376
+ const parsed = {};
33377
+ for (const [key, target] of CLAIM_COORDINATE_KEYS) {
33378
+ const declared = claimSource[key];
33379
+ if (declared === void 0) continue;
33380
+ if (!isScalar(declared) || String(declared).trim() === "") {
33381
+ warnings.push({
33382
+ code: "KIND_CONVENTION_BAD_MEMBER",
33383
+ message: `kind convention '${doc.id}' has a malformed 'claim.${key}' (${describeShape(declared)}; expected a declared field name); skipping it.`,
33384
+ field: `claim.${key}`,
33385
+ severity: "warning"
33386
+ });
33387
+ continue;
33388
+ }
33389
+ parsed[target] = String(declared).trim();
33390
+ }
33391
+ if (parsed.ownerField !== void 0 || parsed.stateField !== void 0) claim = parsed;
33392
+ }
33393
+ }
33338
33394
  const sections = Array.isArray(fm.sections) ? fm.sections.filter((s) => typeof s === "string" && s.trim() !== "") : void 0;
33339
33395
  const title = typeof fm.title === "string" && fm.title.trim() !== "" ? fm.title.trim() : governs;
33340
33396
  let description;
@@ -33367,6 +33423,7 @@ function parseConventionDoc(doc) {
33367
33423
  if (sections && sections.length > 0) kind.sections = sections;
33368
33424
  if (freshnessHorizon !== void 0) kind.freshnessHorizon = freshnessHorizon;
33369
33425
  if (browseCollapsed !== void 0) kind.browseCollapsed = browseCollapsed;
33426
+ if (claim !== void 0) kind.claim = claim;
33370
33427
  return {
33371
33428
  ok: true,
33372
33429
  kind,
@@ -43191,18 +43248,37 @@ async function authorizePublicationRoot(requested) {
43191
43248
  }
43192
43249
  async function assertPublicationRoot(identity) {
43193
43250
  let entry;
43251
+ try {
43252
+ entry = await lstat(identity.requested);
43253
+ } catch (error) {
43254
+ const code2 = error.code;
43255
+ if (code2 === "ENOENT" || code2 === "ENOTDIR") {
43256
+ throw new PublicationError("SOURCE_CHANGED", "the publication source identity became unavailable during capture", { retryable: true, cause: error });
43257
+ }
43258
+ throw new PublicationError("IO_ERROR", "the publication source identity could not be read during capture", { cause: error });
43259
+ }
43260
+ if (entry.isSymbolicLink() || !entry.isDirectory()) {
43261
+ throw new PublicationError("SOURCE_CHANGED", "the publication source root changed during capture", {
43262
+ retryable: true,
43263
+ expected: { canonical: identity.canonical, dev: identity.dev, ino: identity.ino },
43264
+ actual: { symlink: entry.isSymbolicLink(), directory: entry.isDirectory() }
43265
+ });
43266
+ }
43194
43267
  let canonical2;
43195
43268
  let current;
43196
43269
  try {
43197
- [entry, canonical2, current] = await Promise.all([
43198
- lstat(identity.requested),
43270
+ [canonical2, current] = await Promise.all([
43199
43271
  realpath(identity.requested),
43200
43272
  stat(identity.requested)
43201
43273
  ]);
43202
43274
  } catch (error) {
43203
- throw new PublicationError("SOURCE_CHANGED", "the publication source identity became unavailable during capture", { retryable: true, cause: error });
43275
+ const code2 = error.code;
43276
+ if (code2 === "ENOENT" || code2 === "ENOTDIR") {
43277
+ throw new PublicationError("SOURCE_CHANGED", "the publication source identity became unavailable during capture", { retryable: true, cause: error });
43278
+ }
43279
+ throw new PublicationError("IO_ERROR", "the publication source identity could not be read during capture", { cause: error });
43204
43280
  }
43205
- if (entry.isSymbolicLink() || !entry.isDirectory() || !current.isDirectory() || canonical2 !== identity.canonical || current.dev !== identity.dev || current.ino !== identity.ino) {
43281
+ if (!current.isDirectory() || canonical2 !== identity.canonical || current.dev !== identity.dev || current.ino !== identity.ino) {
43206
43282
  throw new PublicationError("SOURCE_CHANGED", "the publication source root changed during capture", {
43207
43283
  retryable: true,
43208
43284
  expected: { canonical: identity.canonical, dev: identity.dev, ino: identity.ino },
@@ -43378,6 +43454,16 @@ function mapCaptureError(error) {
43378
43454
  }
43379
43455
  return new PublicationError("INVALID_BUNDLE", error instanceof Error ? error.message : "the bundle is invalid", { cause: error });
43380
43456
  }
43457
+ async function classifyCaptureError(error, rootIdentity) {
43458
+ const mapped = mapCaptureError(error);
43459
+ if (mapped.code !== "IO_ERROR") return mapped;
43460
+ try {
43461
+ await assertPublicationRoot(rootIdentity);
43462
+ } catch (rootError) {
43463
+ if (rootError instanceof PublicationError && rootError.code === "SOURCE_CHANGED") return rootError;
43464
+ }
43465
+ return mapped;
43466
+ }
43381
43467
  function addObject(objects, bytes, mediaType, representation) {
43382
43468
  const digest2 = sha256(bytes);
43383
43469
  if (!objects.has(digest2)) objects.set(digest2, bytes.slice());
@@ -43577,7 +43663,7 @@ async function capturePublicationSnapshot(options2) {
43577
43663
  }
43578
43664
  return handle;
43579
43665
  } catch (error) {
43580
- lastError = mapCaptureError(error);
43666
+ lastError = await classifyCaptureError(error, rootIdentity);
43581
43667
  if (!lastError.retryable || attempt === maxAttempts) throw lastError;
43582
43668
  }
43583
43669
  }