superbee 0.1.4 → 0.1.5-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/README.md CHANGED
@@ -40,24 +40,36 @@ 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.
69
+
70
+ The managed Agent Skill works in Claude Code, Codex, and OpenCode. OpenCode discovers Superbee from
71
+ its documented Claude-compatible Skill path. It shares Claude Code's Skill bytes by default; if
72
+ Claude Code is explicitly relocated, the installer manages the two documented host paths separately.
61
73
 
62
74
  Upgrading from the legacy `@holaxis/aslite` package or the retired marketplace plugin? Install
63
75
  `superbee` alongside it, have your agent run `superbee setup` to migrate the exact legacy
@@ -1153,7 +1153,7 @@ var require_omap = __commonJS({
1153
1153
  var _toString = Object.prototype.toString;
1154
1154
  function resolveYamlOmap(data) {
1155
1155
  if (data === null) return true;
1156
- var objectKeys = [], index, length, pair, pairKey, pairHasKey, object = data;
1156
+ var objectKeys = {}, index, length, pair, pairKey, pairHasKey, object = data;
1157
1157
  for (index = 0, length = object.length; index < length; index += 1) {
1158
1158
  pair = object[index];
1159
1159
  pairHasKey = false;
@@ -1165,8 +1165,8 @@ var require_omap = __commonJS({
1165
1165
  }
1166
1166
  }
1167
1167
  if (!pairHasKey) return false;
1168
- if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey);
1169
- else return false;
1168
+ if (_hasOwnProperty.call(objectKeys, pairKey)) return false;
1169
+ Object.defineProperty(objectKeys, pairKey, { value: true });
1170
1170
  }
1171
1171
  return true;
1172
1172
  }
@@ -1603,17 +1603,22 @@ var require_loader = __commonJS({
1603
1603
  state.result += _result;
1604
1604
  }
1605
1605
  }
1606
+ function chargeMergeWork(state) {
1607
+ state.totalMergeKeys += 1;
1608
+ if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
1609
+ throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
1610
+ }
1611
+ }
1606
1612
  function mergeMappings(state, destination, source, overridableKeys) {
1607
1613
  var sourceKeys, key, index, quantity;
1608
1614
  if (!common.isObject(source)) {
1609
1615
  throwError(state, "cannot merge mappings; the provided source object is unacceptable");
1610
1616
  }
1617
+ chargeMergeWork(state);
1611
1618
  sourceKeys = Object.keys(source);
1612
1619
  for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
1613
1620
  key = sourceKeys[index];
1614
- if (state.maxTotalMergeKeys !== -1 && ++state.totalMergeKeys > state.maxTotalMergeKeys) {
1615
- throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
1616
- }
1621
+ chargeMergeWork(state);
1617
1622
  if (!_hasOwnProperty.call(destination, key)) {
1618
1623
  setProperty(destination, key, source[key]);
1619
1624
  overridableKeys[key] = true;
@@ -1642,6 +1647,9 @@ var require_loader = __commonJS({
1642
1647
  }
1643
1648
  if (keyTag === "tag:yaml.org,2002:merge") {
1644
1649
  if (Array.isArray(valueNode)) {
1650
+ if (valueNode.length > 100) {
1651
+ throwError(state, "abnormal merge sequence size");
1652
+ }
1645
1653
  for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
1646
1654
  mergeMappings(state, _result, valueNode[index], overridableKeys);
1647
1655
  }
@@ -3582,6 +3590,13 @@ var MalformedDocumentError = class extends Error {
3582
3590
  }
3583
3591
  };
3584
3592
  function parseMarkdown(raw, context) {
3593
+ if (/^---(?:\r?\n|$)/.test(raw)) {
3594
+ const firstLineEnd = raw.indexOf("\n");
3595
+ const afterOpening = firstLineEnd === -1 ? "" : raw.slice(firstLineEnd + 1);
3596
+ if (!/^---\r?$/m.test(afterOpening)) {
3597
+ throw new MalformedDocumentError(context, new Error("unterminated YAML frontmatter delimiter"));
3598
+ }
3599
+ }
3585
3600
  let parsed;
3586
3601
  try {
3587
3602
  parsed = (0, import_gray_matter.default)(raw, { engines: { yaml: yamlEngine } });
@@ -3591,16 +3606,20 @@ function parseMarkdown(raw, context) {
3591
3606
  const frontmatter2 = normalizeFrontmatter(parsed.data ?? {});
3592
3607
  return { frontmatter: frontmatter2, body: parsed.content };
3593
3608
  }
3609
+ function normalizeDocumentBodyForStorage(body) {
3610
+ return body.endsWith("\n") ? body : `${body}
3611
+ `;
3612
+ }
3594
3613
  function stringifyWithData(data, body) {
3595
3614
  const engines2 = import_gray_matter.default.engines;
3596
3615
  const yaml3 = engines2.yaml.stringify(data).trim();
3597
3616
  const content = body ?? "";
3598
3617
  const newline = (value) => value.endsWith("\n") ? value : `${value}
3599
3618
  `;
3600
- if (yaml3 === "{}") return newline(content);
3619
+ if (yaml3 === "{}") return normalizeDocumentBodyForStorage(content);
3601
3620
  return `---
3602
3621
  ${newline(yaml3)}---
3603
- ${newline(content)}`;
3622
+ ${normalizeDocumentBodyForStorage(content)}`;
3604
3623
  }
3605
3624
  function stringifyDoc(frontmatter2, body) {
3606
3625
  return stringifyWithData(frontmatter2, body);
@@ -3885,6 +3904,33 @@ function processExists(pid) {
3885
3904
  return err.code !== "ESRCH";
3886
3905
  }
3887
3906
  }
3907
+ function staleLockQuarantinePath(lockPath, owner) {
3908
+ const tokenHash = createHash("sha256").update(owner.token).digest("hex");
3909
+ return `${lockPath}.stale-${tokenHash}`;
3910
+ }
3911
+ async function pathExists(candidate) {
3912
+ try {
3913
+ await fs.lstat(candidate);
3914
+ return true;
3915
+ } catch (err) {
3916
+ if (err.code === "ENOENT") return false;
3917
+ throw err;
3918
+ }
3919
+ }
3920
+ async function quarantineStaleLock(lockPath, owner) {
3921
+ if (owner.hostname !== hostname() || processExists(owner.pid)) return false;
3922
+ const quarantinePath = staleLockQuarantinePath(lockPath, owner);
3923
+ try {
3924
+ await fs.rename(lockPath, quarantinePath);
3925
+ return true;
3926
+ } catch (err) {
3927
+ const code = err.code;
3928
+ if (code === "ENOENT") return false;
3929
+ if (await pathExists(quarantinePath)) return false;
3930
+ if (process.platform === "win32" && WINDOWS_DIRECTORY_CONTENTION_CODES.has(code ?? "")) return false;
3931
+ throw err;
3932
+ }
3933
+ }
3888
3934
  function delay(ms) {
3889
3935
  return new Promise((resolve) => setTimeout(resolve, ms));
3890
3936
  }
@@ -3947,46 +3993,74 @@ async function selectLockRoot(options2) {
3947
3993
  await ensurePrivateLockRoot(lockRoot);
3948
3994
  return lockRoot;
3949
3995
  }
3996
+ var WINDOWS_DIRECTORY_CONTENTION_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
3997
+ async function classifyLockClaimFailure(error, lockPath) {
3998
+ const code = error.code;
3999
+ if (code === "EEXIST") return "contention";
4000
+ if (process.platform !== "win32" || !WINDOWS_DIRECTORY_CONTENTION_CODES.has(code ?? "")) return "terminal";
4001
+ try {
4002
+ await fs.lstat(lockPath);
4003
+ return "contention";
4004
+ } catch (probeError) {
4005
+ const probeCode = probeError.code;
4006
+ return probeCode === "ENOENT" || probeCode === "ENOTDIR" ? "unwitnessed-windows-sharing-error" : "terminal";
4007
+ }
4008
+ }
3950
4009
  async function claimLockPath(lockPath, owner, waitMs, pollMs) {
3951
4010
  const started = owner.created_at_ms;
4011
+ let unwitnessedWindowsRetryUsed = false;
3952
4012
  while (true) {
3953
4013
  try {
3954
4014
  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;
4015
+ } catch (err) {
4016
+ const failure = await classifyLockClaimFailure(err, lockPath);
4017
+ if (failure === "terminal") throw err;
4018
+ if (failure === "unwitnessed-windows-sharing-error") {
4019
+ if (unwitnessedWindowsRetryUsed) throw err;
4020
+ unwitnessedWindowsRetryUsed = true;
4021
+ continue;
4022
+ } else {
4023
+ unwitnessedWindowsRetryUsed = false;
3966
4024
  }
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
- };
4025
+ let existingOwner = await readOwner(lockPath);
4026
+ if (existingOwner !== null) {
4027
+ if (await quarantineStaleLock(lockPath, existingOwner)) continue;
4028
+ existingOwner = await readOwner(lockPath);
4029
+ }
4030
+ if (Date.now() - started >= waitMs) throw timeoutError(lockPath, existingOwner, owner.target);
4031
+ await delay(pollMs);
4032
+ continue;
4033
+ }
4034
+ try {
4035
+ await fs.writeFile(path.join(lockPath, OWNER_FILE), `${JSON.stringify(owner)}
4036
+ `, {
4037
+ encoding: "utf8",
4038
+ flag: "wx",
4039
+ mode: 384
4040
+ });
3985
4041
  } catch (err) {
3986
- if (err.code !== "EEXIST") throw err;
4042
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => {
4043
+ });
4044
+ throw err;
3987
4045
  }
3988
- if (Date.now() - started >= waitMs) throw timeoutError(lockPath, await readOwner(lockPath), owner.target);
3989
- await delay(pollMs);
4046
+ return async () => {
4047
+ const current = await readOwner(lockPath);
4048
+ if (current?.token !== owner.token) {
4049
+ throw new FilesystemMutationLockError(
4050
+ `refusing to release filesystem mutation lock '${lockPath}' because its owner token changed; the mutation may have completed, inspect the lock before retrying.`,
4051
+ { lockPath, owner: current, stale: false, malformed: current === null }
4052
+ );
4053
+ }
4054
+ try {
4055
+ await fs.rm(lockPath, { recursive: true, force: false });
4056
+ } catch (err) {
4057
+ const message = err instanceof Error ? err.message : String(err);
4058
+ throw new FilesystemMutationLockError(
4059
+ `mutation completed but filesystem lock '${lockPath}' could not be removed (${message}); inspect the lock before retrying.`,
4060
+ { lockPath, owner: current, stale: false, malformed: false }
4061
+ );
4062
+ }
4063
+ };
3990
4064
  }
3991
4065
  }
3992
4066
  function newOwner(target) {
@@ -5038,6 +5112,10 @@ var VALID_FIELDS_KEYS = /* @__PURE__ */ new Set([
5038
5112
  "descriptions"
5039
5113
  ]);
5040
5114
  var MISPLACED_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["enum", "enums", "values", "constraints"]);
5115
+ var CLAIM_COORDINATE_KEYS = [
5116
+ ["owner_field", "ownerField"],
5117
+ ["state_field", "stateField"]
5118
+ ];
5041
5119
  function parseConventionDoc(doc) {
5042
5120
  const fm = doc.frontmatter;
5043
5121
  const governs = typeof fm.governs === "string" ? fm.governs.trim() : "";
@@ -5346,6 +5424,35 @@ function parseConventionDoc(doc) {
5346
5424
  if (Object.keys(parsed).length > 0) expectsInbound = parsed;
5347
5425
  }
5348
5426
  }
5427
+ const claimSource = fm.claim;
5428
+ let claim;
5429
+ if (claimSource !== void 0) {
5430
+ if (!isPlainObject(claimSource)) {
5431
+ warnings.push({
5432
+ code: "KIND_CONVENTION_BAD_SHAPE",
5433
+ 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.`,
5434
+ field: "claim",
5435
+ severity: "warning"
5436
+ });
5437
+ } else {
5438
+ const parsed = {};
5439
+ for (const [key, target] of CLAIM_COORDINATE_KEYS) {
5440
+ const declared = claimSource[key];
5441
+ if (declared === void 0) continue;
5442
+ if (!isScalar(declared) || String(declared).trim() === "") {
5443
+ warnings.push({
5444
+ code: "KIND_CONVENTION_BAD_MEMBER",
5445
+ message: `kind convention '${doc.id}' has a malformed 'claim.${key}' (${describeShape(declared)}; expected a declared field name); skipping it.`,
5446
+ field: `claim.${key}`,
5447
+ severity: "warning"
5448
+ });
5449
+ continue;
5450
+ }
5451
+ parsed[target] = String(declared).trim();
5452
+ }
5453
+ if (parsed.ownerField !== void 0 || parsed.stateField !== void 0) claim = parsed;
5454
+ }
5455
+ }
5349
5456
  const sections = Array.isArray(fm.sections) ? fm.sections.filter((s) => typeof s === "string" && s.trim() !== "") : void 0;
5350
5457
  const title = typeof fm.title === "string" && fm.title.trim() !== "" ? fm.title.trim() : governs;
5351
5458
  let description;
@@ -5378,6 +5485,7 @@ function parseConventionDoc(doc) {
5378
5485
  if (sections && sections.length > 0) kind.sections = sections;
5379
5486
  if (freshnessHorizon !== void 0) kind.freshnessHorizon = freshnessHorizon;
5380
5487
  if (browseCollapsed !== void 0) kind.browseCollapsed = browseCollapsed;
5488
+ if (claim !== void 0) kind.claim = claim;
5381
5489
  return {
5382
5490
  ok: true,
5383
5491
  kind,