@skill-map/cli 0.50.1 → 0.51.0

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/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // cli/entry.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d01bcaaf-2cc8-5f97-915b-f8aac50c50c0")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1a42a4ca-6765-5a66-abc9-6b57464bc9c6")}catch(e){}}();
4
4
  import { existsSync as existsSync33 } from "fs";
5
5
  import { Builtins, Cli as Cli2 } from "clipanion";
6
6
 
@@ -246,7 +246,7 @@ function bucketByKind(kind, instance, bag) {
246
246
  // package.json
247
247
  var package_default = {
248
248
  name: "@skill-map/cli",
249
- version: "0.50.1",
249
+ version: "0.51.0",
250
250
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
251
251
  license: "MIT",
252
252
  type: "module",
@@ -3987,7 +3987,7 @@ var nodeSupersedeAction = {
3987
3987
  }
3988
3988
  };
3989
3989
 
3990
- // core/update-check/index.ts
3990
+ // kernel/update-check/index.ts
3991
3991
  var SEMVER_SHAPE_RE = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3992
3992
  async function fetchLatestVersion(pkg, opts) {
3993
3993
  const controller = new AbortController();
@@ -4122,9 +4122,9 @@ function ansiFor(opts) {
4122
4122
  import { randomUUID } from "crypto";
4123
4123
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4124
4124
  import { homedir } from "os";
4125
- import { join as join2 } from "path";
4125
+ import { join as join3 } from "path";
4126
4126
 
4127
- // core/config/atomic-write.ts
4127
+ // kernel/util/atomic-write.ts
4128
4128
  import {
4129
4129
  closeSync,
4130
4130
  constants as fsConstants,
@@ -4182,17 +4182,31 @@ function writeJsonAtomic(path, content) {
4182
4182
  }
4183
4183
 
4184
4184
  // core/paths/db-path.ts
4185
- import { join, resolve as resolve6 } from "path";
4185
+ import { join as join2, resolve as resolve6 } from "path";
4186
+
4187
+ // kernel/util/skill-map-paths.ts
4188
+ import { join } from "path";
4186
4189
  var SKILL_MAP_DIR = ".skill-map";
4190
+ var KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;
4191
+ var SETTINGS_FILENAME = "settings.json";
4192
+ var LOCAL_SETTINGS_FILENAME = "settings.local.json";
4193
+ function kernelSettingsPath(scopeRoot) {
4194
+ return join(scopeRoot, KERNEL_SKILL_MAP_DIR, SETTINGS_FILENAME);
4195
+ }
4196
+ function kernelLocalSettingsPath(scopeRoot) {
4197
+ return join(scopeRoot, KERNEL_SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);
4198
+ }
4199
+
4200
+ // core/paths/db-path.ts
4187
4201
  var DB_FILENAME = "skill-map.db";
4188
4202
  var JOBS_DIRNAME = "jobs";
4189
4203
  var PLUGINS_DIRNAME = "plugins";
4190
- var SETTINGS_FILENAME = "settings.json";
4191
- var LOCAL_SETTINGS_FILENAME = "settings.local.json";
4204
+ var SETTINGS_FILENAME2 = "settings.json";
4205
+ var LOCAL_SETTINGS_FILENAME2 = "settings.local.json";
4192
4206
  var IGNORE_FILENAME = ".skillmapignore";
4193
4207
  var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
4194
4208
  var GITIGNORE_ENTRIES = [
4195
- `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
4209
+ `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME2}`,
4196
4210
  `${SKILL_MAP_DIR}/${DB_FILENAME}`
4197
4211
  ];
4198
4212
  function resolveDbPath(options) {
@@ -4209,23 +4223,23 @@ function defaultProjectPluginsDir(ctx) {
4209
4223
  return resolve6(ctx.cwd, SKILL_MAP_DIR, PLUGINS_DIRNAME);
4210
4224
  }
4211
4225
  function defaultDbPath(scopeRoot) {
4212
- return join(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);
4226
+ return join2(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);
4213
4227
  }
4214
4228
  function defaultSettingsPath(scopeRoot) {
4215
- return join(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME);
4229
+ return join2(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME2);
4216
4230
  }
4217
4231
  function defaultLocalSettingsPath(scopeRoot) {
4218
- return join(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);
4232
+ return join2(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME2);
4219
4233
  }
4220
4234
  function defaultIgnoreFilePath(scopeRoot) {
4221
- return join(scopeRoot, IGNORE_FILENAME);
4235
+ return join2(scopeRoot, IGNORE_FILENAME);
4222
4236
  }
4223
4237
 
4224
4238
  // cli/util/user-settings-store.ts
4225
4239
  var FILENAME = "settings.json";
4226
4240
  var SCHEMA_VERSION = 1;
4227
4241
  function userSettingsFilePath() {
4228
- return join2(homedir(), SKILL_MAP_DIR, FILENAME);
4242
+ return join3(homedir(), SKILL_MAP_DIR, FILENAME);
4229
4243
  }
4230
4244
  function defaultSettings() {
4231
4245
  return { schemaVersion: SCHEMA_VERSION, updateCheck: {}, telemetry: {} };
@@ -4264,7 +4278,7 @@ function backfillSubObjects(settings) {
4264
4278
  };
4265
4279
  }
4266
4280
  function writeUserSettings(patch) {
4267
- const dir = join2(homedir(), SKILL_MAP_DIR);
4281
+ const dir = join3(homedir(), SKILL_MAP_DIR);
4268
4282
  const path = userSettingsFilePath();
4269
4283
  try {
4270
4284
  const current = readUserSettings();
@@ -5361,18 +5375,6 @@ var CONFIG_LOADER_TEXTS = {
5361
5375
  projectLocalOnlyStripped: "[config:{{layer}}] key {{key}} is project-local only; stripped from the committed project layer. Move it to .skill-map/settings.local.json (gitignored, per-checkout)."
5362
5376
  };
5363
5377
 
5364
- // kernel/util/skill-map-paths.ts
5365
- import { join as join3 } from "path";
5366
- var KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;
5367
- var SETTINGS_FILENAME2 = "settings.json";
5368
- var LOCAL_SETTINGS_FILENAME2 = "settings.local.json";
5369
- function kernelSettingsPath(scopeRoot) {
5370
- return join3(scopeRoot, KERNEL_SKILL_MAP_DIR, SETTINGS_FILENAME2);
5371
- }
5372
- function kernelLocalSettingsPath(scopeRoot) {
5373
- return join3(scopeRoot, KERNEL_SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME2);
5374
- }
5375
-
5376
5378
  // config/defaults.json
5377
5379
  var defaults_default = {
5378
5380
  schemaVersion: 1,
@@ -5830,11 +5832,20 @@ var FilesystemSidecarStore = class {
5830
5832
  * files in the repo and entries are tiny).
5831
5833
  */
5832
5834
  #locks = /* @__PURE__ */ new Map();
5835
+ /** Injected consent gate, see {@link TSidecarConsentGate}. */
5836
+ #consentGate;
5837
+ /**
5838
+ * @param consentGate the write-consent gate, invoked first inside
5839
+ * `applyPatch`. Production wires
5840
+ * `core/config/sidecar-consent.ts:ensureSidecarWritesAllowed`; tests
5841
+ * wire the same function (to exercise the real config-backed gate)
5842
+ * or a stub.
5843
+ */
5844
+ constructor(consentGate) {
5845
+ this.#consentGate = consentGate;
5846
+ }
5833
5847
  async applyPatch(sidecarAbsPath, changes, consent) {
5834
- ensureSidecarWritesAllowed({
5835
- confirm: consent.confirm,
5836
- cwd: consent.cwd
5837
- });
5848
+ this.#consentGate(consent);
5838
5849
  const prev = this.#locks.get(sidecarAbsPath) ?? Promise.resolve();
5839
5850
  let release;
5840
5851
  const settled = new Promise((res) => {
@@ -7858,7 +7869,7 @@ function rowToContribution(row) {
7858
7869
  };
7859
7870
  }
7860
7871
 
7861
- // core/sqlite/schema-fingerprint.ts
7872
+ // kernel/adapters/sqlite/schema-fingerprint.ts
7862
7873
  import { createHash } from "crypto";
7863
7874
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
7864
7875
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
@@ -9334,7 +9345,7 @@ var BumpCommand = class extends SmCommand {
9334
9345
  * the staging missed).
9335
9346
  */
9336
9347
  async #executePending(plan, cwd, ansi) {
9337
- const store = new FilesystemSidecarStore();
9348
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
9338
9349
  const ctx = defaultRuntimeContext();
9339
9350
  const consent = {
9340
9351
  confirm: this.yes,
@@ -9480,7 +9491,7 @@ function buildBumpedOutcome(item, sidecarPath) {
9480
9491
  if (item.report.createdSidecar === true) outcome.createdSidecar = true;
9481
9492
  return outcome;
9482
9493
  }
9483
- async function applyBumpWrites(item, consent, store = new FilesystemSidecarStore()) {
9494
+ async function applyBumpWrites(item, consent, store = new FilesystemSidecarStore(ensureSidecarWritesAllowed)) {
9484
9495
  let sidecarPath;
9485
9496
  try {
9486
9497
  for (const w of item.writes) {
@@ -11423,20 +11434,9 @@ function trimRedundantPath(message, primary) {
11423
11434
  import { existsSync as existsSync16 } from "fs";
11424
11435
  import { Command as Command4, Option as Option4 } from "clipanion";
11425
11436
 
11426
- // core/config/active-provider.ts
11437
+ // kernel/scan/detect-providers.ts
11427
11438
  import { existsSync as existsSync15 } from "fs";
11428
11439
  import { join as join10 } from "path";
11429
- function resolveActiveProvider(cwd, providers = []) {
11430
- const detected = detectProvidersFromFilesystem(cwd, providers);
11431
- const fromConfig = readConfigValue("activeProvider", { cwd });
11432
- if (typeof fromConfig === "string" && fromConfig.length > 0) {
11433
- return { resolved: fromConfig, source: "config", detected };
11434
- }
11435
- if (detected.length > 0) {
11436
- return { resolved: detected[0], source: "autodetect", detected };
11437
- }
11438
- return { resolved: null, source: "none", detected };
11439
- }
11440
11440
  function detectProvidersFromFilesystem(cwd, providers) {
11441
11441
  const seen = /* @__PURE__ */ new Set();
11442
11442
  const out = [];
@@ -11451,6 +11451,19 @@ function detectProvidersFromFilesystem(cwd, providers) {
11451
11451
  return out;
11452
11452
  }
11453
11453
 
11454
+ // core/config/active-provider.ts
11455
+ function resolveActiveProvider(cwd, providers = []) {
11456
+ const detected = detectProvidersFromFilesystem(cwd, providers);
11457
+ const fromConfig = readConfigValue("activeProvider", { cwd });
11458
+ if (typeof fromConfig === "string" && fromConfig.length > 0) {
11459
+ return { resolved: fromConfig, source: "config", detected };
11460
+ }
11461
+ if (detected.length > 0) {
11462
+ return { resolved: detected[0], source: "autodetect", detected };
11463
+ }
11464
+ return { resolved: null, source: "none", detected };
11465
+ }
11466
+
11454
11467
  // cli/util/path-display.ts
11455
11468
  import { isAbsolute as isAbsolute4, relative as pathRelative } from "path";
11456
11469
  function relativeIfBelow(path, cwd) {
@@ -16982,7 +16995,7 @@ function resolveActiveProviderOption(optionValue, roots, providers) {
16982
16995
  for (const root of roots) {
16983
16996
  const absRoot = isAbsolute7(root) ? root : resolve28(root);
16984
16997
  if (!existsSync23(absRoot)) continue;
16985
- const detected = resolveActiveProvider(absRoot, providers).resolved;
16998
+ const detected = detectProvidersFromFilesystem(absRoot, providers)[0] ?? null;
16986
16999
  if (detected !== null) return detected;
16987
17000
  }
16988
17001
  return null;
@@ -22272,7 +22285,7 @@ var IntentionalFailCommand = class extends SmCommand {
22272
22285
  throw new Error(INTENTIONAL_FAIL_TEXTS.errorMessage);
22273
22286
  }, 0);
22274
22287
  await new Promise((resolve40) => setTimeout(resolve40, 5e3));
22275
- return 1;
22288
+ return ExitCode.Issues;
22276
22289
  }
22277
22290
  };
22278
22291
 
@@ -22341,6 +22354,25 @@ var SCAN_TEXTS = {
22341
22354
  persistedTo: " {{dbPath}}\n",
22342
22355
  /** Body line for dry-run mode, same indent, marker tail. */
22343
22356
  wouldPersist: " would persist to {{dbPath}} (dry-run)\n",
22357
+ /**
22358
+ * Count-row nouns for the `{{counts}}` block in `scannedSummary`.
22359
+ * The caller selects the singular / plural form on `count === 1`
22360
+ * (English plural rule), so both forms live in the catalog instead of
22361
+ * being hand-suffixed with `s` at the call site (per the i18n
22362
+ * contract: catalog strings, no `${word}s` interpolation). `info` is
22363
+ * uncountable in English (no `infos`), so it carries a single form;
22364
+ * `countNoIssues` is the all-clean placeholder.
22365
+ */
22366
+ countNodeNounSingular: "node",
22367
+ countNodeNounPlural: "nodes",
22368
+ countLinkNounSingular: "link",
22369
+ countLinkNounPlural: "links",
22370
+ countErrorNounSingular: "error",
22371
+ countErrorNounPlural: "errors",
22372
+ countWarningNounSingular: "warning",
22373
+ countWarningNounPlural: "warnings",
22374
+ countInfoNoun: "info",
22375
+ countNoIssues: "0 issues",
22344
22376
  /**
22345
22377
  * Cap-hit notice, printed when the walker stopped accepting nodes
22346
22378
  * because `--max-nodes` (or the `scan.maxNodes` setting) was reached.
@@ -23422,27 +23454,29 @@ function fillSeverityBucket(bucket, nodeIds) {
23422
23454
  function formatScanCounts(opts) {
23423
23455
  const { nodes, links, severities, ansi } = opts;
23424
23456
  const parts = [
23425
- `${nodes} ${plural(nodes, "node")}`,
23426
- `${links} ${plural(links, "link")}`
23457
+ `${nodes} ${countNoun(nodes, SCAN_TEXTS.countNodeNounSingular, SCAN_TEXTS.countNodeNounPlural)}`,
23458
+ `${links} ${countNoun(links, SCAN_TEXTS.countLinkNounSingular, SCAN_TEXTS.countLinkNounPlural)}`
23427
23459
  ];
23428
23460
  const total = severities.errors + severities.warns + severities.info;
23429
23461
  if (total === 0) {
23430
- parts.push(ansi.dim("0 issues"));
23462
+ parts.push(ansi.dim(SCAN_TEXTS.countNoIssues));
23431
23463
  } else {
23432
23464
  if (severities.errors > 0) {
23433
- parts.push(ansi.red(`${severities.errors} ${plural(severities.errors, "error")}`));
23465
+ const noun = countNoun(severities.errors, SCAN_TEXTS.countErrorNounSingular, SCAN_TEXTS.countErrorNounPlural);
23466
+ parts.push(ansi.red(`${severities.errors} ${noun}`));
23434
23467
  }
23435
23468
  if (severities.warns > 0) {
23436
- parts.push(ansi.yellow(`${severities.warns} ${plural(severities.warns, "warning")}`));
23469
+ const noun = countNoun(severities.warns, SCAN_TEXTS.countWarningNounSingular, SCAN_TEXTS.countWarningNounPlural);
23470
+ parts.push(ansi.yellow(`${severities.warns} ${noun}`));
23437
23471
  }
23438
23472
  if (severities.info > 0) {
23439
- parts.push(ansi.dim(`${severities.info} info`));
23473
+ parts.push(ansi.dim(`${severities.info} ${SCAN_TEXTS.countInfoNoun}`));
23440
23474
  }
23441
23475
  }
23442
23476
  return parts.join(" \xB7 ");
23443
23477
  }
23444
- function plural(count, word) {
23445
- return count === 1 ? word : `${word}s`;
23478
+ function countNoun(count, singular, plural) {
23479
+ return count === 1 ? singular : plural;
23446
23480
  }
23447
23481
 
23448
23482
  // cli/commands/scan-compare.ts
@@ -23888,11 +23922,10 @@ var SERVER_TEXTS = {
23888
23922
  // here (after static + SPA fallback have had their turn).
23889
23923
  unknownPath: "Not found: {{path}}.",
23890
23924
  // ---- sidecar bump route (routes/sidecar.ts) ------------------------------
23891
- // 409 refusal when a fresh node is bumped without `force`. The
23892
- // `sidecar-fresh:` prefix is load-bearing, the UI pattern-matches
23893
- // it (the global `app.onError` already maps HTTP 409 to the
23894
- // `sidecar-fresh` envelope `code`, so the prefix is for log-grep
23895
- // affinity with the CLI's bump verb).
23925
+ // 409 refusal when a fresh node is bumped without `force`. Dispatch
23926
+ // is via the typed `ConflictError` (`code: 'sidecar-fresh'`), so the
23927
+ // `sidecar-fresh:` prefix is NOT load-bearing; it stays only for
23928
+ // log-grep affinity with the CLI's bump verb.
23896
23929
  sidecarFreshRefusal: "sidecar-fresh: Node is fresh; pass force:true to bump anyway.",
23897
23930
  // 400 envelopes thrown by `parseBody` when the request payload is
23898
23931
  // malformed. Each branch has its own key so the UI / log can
@@ -23920,9 +23953,9 @@ var SERVER_TEXTS = {
23920
23953
  // dropped half the pipeline. Same gate the `?fresh=1` GET applies.
23921
23954
  scanPostRequiresFullPipeline: "POST /api/scan cannot run while the server was started with --no-built-ins or --no-plugins (would persist a partial DB).",
23922
23955
  // 409, another scan (watcher batch or another POST) is in flight.
23923
- // The `scan-busy:` prefix is load-bearing: HTTP 409 maps to
23924
- // `scan-busy` in `app.onError`'s `codeForStatus`, but the prefix
23925
- // keeps log-grep affinity with the CLI's `sm scan` verb.
23956
+ // Dispatch is via the typed `ConflictError` (`code: 'scan-busy'`), so
23957
+ // the `scan-busy:` prefix is NOT load-bearing; it stays only for
23958
+ // log-grep affinity with the CLI's `sm scan` verb.
23926
23959
  scanPostBusy: "scan-busy: Another scan is already in flight; retry once it finishes.",
23927
23960
  // 500, DB missing on a write path. Read paths degrade to empty
23928
23961
  // shapes; mutations cannot persist without a DB so they fail fast.
@@ -24535,6 +24568,7 @@ function registerHealthRoute(app, deps) {
24535
24568
  var DEFAULT_LIMIT = 100;
24536
24569
  var MAX_LIMIT = 1e3;
24537
24570
  var BFF_MAX_BULK_CONTRIBUTIONS = 200;
24571
+ var MAX_WS_CLIENTS = 64;
24538
24572
 
24539
24573
  // server/routes/issues.ts
24540
24574
  function registerIssuesRoute(app, deps) {
@@ -24767,7 +24801,7 @@ function registerNodesRoutes(app, deps) {
24767
24801
  const tags = result?.tags ?? [];
24768
24802
  if (!bundle) {
24769
24803
  throw new HTTPException7(404, {
24770
- message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
24804
+ message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
24771
24805
  });
24772
24806
  }
24773
24807
  const decoratedNode = { ...bundle.node, isFavorite, contributions, tags };
@@ -26057,7 +26091,7 @@ async function runPersistedScan(c, deps) {
26057
26091
  });
26058
26092
  } catch (err) {
26059
26093
  if (err instanceof ScanBusyError) {
26060
- throw new HTTPException14(409, { message: SERVER_TEXTS.scanPostBusy });
26094
+ throw new ConflictError({ code: "scan-busy", message: SERVER_TEXTS.scanPostBusy });
26061
26095
  }
26062
26096
  throw err;
26063
26097
  }
@@ -26250,7 +26284,7 @@ function registerSidecarRoutes(app, deps) {
26250
26284
  }
26251
26285
  const result = invokeBump2(node, absPath, body);
26252
26286
  if (result.report.ok === false && result.report.reason === "fresh") {
26253
- throw new HTTPException15(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
26287
+ throw new ConflictError({ code: "sidecar-fresh", message: SERVER_TEXTS.sidecarFreshRefusal });
26254
26288
  }
26255
26289
  if (result.report.ok === true && result.report.noop === true) {
26256
26290
  const envelope2 = {
@@ -26265,7 +26299,7 @@ function registerSidecarRoutes(app, deps) {
26265
26299
  };
26266
26300
  return c.json(envelope2);
26267
26301
  }
26268
- const store = new FilesystemSidecarStore();
26302
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
26269
26303
  try {
26270
26304
  for (const w of result.writes ?? []) {
26271
26305
  if (w.kind === "sidecar") {
@@ -26533,6 +26567,14 @@ var LoopbackGateError = class extends HTTPException16 {
26533
26567
  this.code = init.code;
26534
26568
  }
26535
26569
  };
26570
+ var ConflictError = class extends HTTPException16 {
26571
+ code;
26572
+ constructor(init) {
26573
+ super(409, { message: init.message });
26574
+ this.name = "ConflictError";
26575
+ this.code = init.code;
26576
+ }
26577
+ };
26536
26578
  function createApp(deps) {
26537
26579
  const app = new Hono();
26538
26580
  const configService = new ConfigService({
@@ -26609,16 +26651,12 @@ function createApp(deps) {
26609
26651
  });
26610
26652
  return app;
26611
26653
  }
26612
- function codeForStatus(status, message) {
26654
+ function codeForStatus(status) {
26613
26655
  if (status === 404) return "not-found";
26614
26656
  if (status === 400) return "bad-query";
26615
26657
  if (status === 403) return "locked";
26616
26658
  if (status === 412) return "confirm-required";
26617
26659
  if (status === 413) return "payload-too-large";
26618
- if (status === 409) {
26619
- if (message.startsWith("scan-busy:")) return "scan-busy";
26620
- return "sidecar-fresh";
26621
- }
26622
26660
  return "internal";
26623
26661
  }
26624
26662
  function formatError2(err, c) {
@@ -26655,12 +26693,23 @@ function formatError2(err, c) {
26655
26693
  };
26656
26694
  return c.json(envelope, 403);
26657
26695
  }
26696
+ if (err instanceof ConflictError) {
26697
+ const envelope = {
26698
+ ok: false,
26699
+ error: {
26700
+ code: err.code,
26701
+ message: err.message,
26702
+ details: null
26703
+ }
26704
+ };
26705
+ return c.json(envelope, 409);
26706
+ }
26658
26707
  if (err instanceof HTTPException16) {
26659
26708
  const status = err.status;
26660
26709
  const envelope = {
26661
26710
  ok: false,
26662
26711
  error: {
26663
- code: codeForStatus(status, err.message),
26712
+ code: codeForStatus(status),
26664
26713
  message: err.message,
26665
26714
  details: null
26666
26715
  }
@@ -26711,6 +26760,7 @@ function formatInternalErrorFallThrough(err, c) {
26711
26760
  var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
26712
26761
  var CLOSE_CODE_GOING_AWAY = 1001;
26713
26762
  var CLOSE_CODE_MESSAGE_TOO_BIG = 1009;
26763
+ var CLOSE_CODE_TRY_AGAIN_LATER = 1013;
26714
26764
  var READY_STATE_OPEN = 1;
26715
26765
  var WsBroadcaster = class {
26716
26766
  #clients = /* @__PURE__ */ new Set();
@@ -26733,6 +26783,13 @@ var WsBroadcaster = class {
26733
26783
  }
26734
26784
  return;
26735
26785
  }
26786
+ if (this.#clients.size >= MAX_WS_CLIENTS) {
26787
+ try {
26788
+ ws.close(CLOSE_CODE_TRY_AGAIN_LATER, "too many connections");
26789
+ } catch {
26790
+ }
26791
+ return;
26792
+ }
26736
26793
  this.#clients.add(ws);
26737
26794
  }
26738
26795
  /**
@@ -26979,14 +27036,19 @@ function validatePort(port) {
26979
27036
  return null;
26980
27037
  }
26981
27038
  function validateHost(host, devCors) {
26982
- if (devCors && !isLoopbackHost(host)) {
27039
+ if (isLoopbackHost(host)) return null;
27040
+ if (devCors) {
26983
27041
  return {
26984
27042
  code: "host-dev-cors-rejected",
26985
27043
  message: `--dev-cors requires a loopback --host (got ${host})`,
26986
27044
  value: host
26987
27045
  };
26988
27046
  }
26989
- return null;
27047
+ return {
27048
+ code: "host-not-loopback",
27049
+ message: `--host must be a loopback address; multi-host serve is not supported pre-1.0 (got ${host})`,
27050
+ value: host
27051
+ };
26990
27052
  }
26991
27053
  function validateWatcher(noWatcher, noBuiltIns, _noPlugins) {
26992
27054
  if (noWatcher) return null;
@@ -27274,6 +27336,14 @@ var SERVE_TEXTS = {
27274
27336
  */
27275
27337
  hostDevCorsRejected: "{{glyph}} sm serve: --dev-cors requires a loopback --host (got {{host}}).\n {{hint}}\n",
27276
27338
  hostDevCorsRejectedHint: "Use --host 127.0.0.1 (or ::1) when --dev-cors is set. Multi-host serve reopens after v0.6.0 (Decision #119).",
27339
+ /**
27340
+ * §3.1b error block when `--host` is any non-loopback address (without
27341
+ * `--dev-cors`). The BFF is loopback-only and unauthenticated pre-1.0
27342
+ * (Decision #119), so binding off-loopback is refused outright rather
27343
+ * than relying on the DNS-rebinding gate as the sole control.
27344
+ */
27345
+ hostNotLoopback: "{{glyph}} sm serve: --host must be a loopback address (got {{host}}).\n {{hint}}\n",
27346
+ hostNotLoopbackHint: "Use --host 127.0.0.1 (or ::1). The server has no auth and is loopback-only; multi-host serve reopens after v0.6.0 (Decision #119).",
27277
27347
  /**
27278
27348
  * §3.1b error block when `--port` falls outside the [0, 65535] range.
27279
27349
  * Hint names the accepted range so the operator can re-run.
@@ -27822,6 +27892,12 @@ function formatValidationError(err, ansi) {
27822
27892
  host: sanitizeForTerminal(err.value),
27823
27893
  hint: ansi.dim(SERVE_TEXTS.hostDevCorsRejectedHint)
27824
27894
  });
27895
+ case "host-not-loopback":
27896
+ return tx(SERVE_TEXTS.hostNotLoopback, {
27897
+ glyph: errGlyph,
27898
+ host: sanitizeForTerminal(err.value),
27899
+ hint: ansi.dim(SERVE_TEXTS.hostNotLoopbackHint)
27900
+ });
27825
27901
  case "port-out-of-range":
27826
27902
  return tx(SERVE_TEXTS.portOutOfRange, {
27827
27903
  glyph: errGlyph,
@@ -28414,7 +28490,7 @@ var SidecarRefreshCommand = class extends SmCommand {
28414
28490
  );
28415
28491
  return ExitCode.Ok;
28416
28492
  }
28417
- const store = new FilesystemSidecarStore();
28493
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
28418
28494
  try {
28419
28495
  await store.applyPatch(
28420
28496
  sidecarAbsPath,
@@ -28701,7 +28777,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
28701
28777
  return ExitCode.Error;
28702
28778
  }
28703
28779
  }
28704
- const store = new FilesystemSidecarStore();
28780
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
28705
28781
  try {
28706
28782
  await store.applyPatch(
28707
28783
  sidecarAbsPath,
@@ -29490,4 +29566,4 @@ function resolveBareDefault() {
29490
29566
  process.exit(ExitCode.Error);
29491
29567
  }
29492
29568
  //# sourceMappingURL=cli.js.map
29493
- //# debugId=d01bcaaf-2cc8-5f97-915b-f8aac50c50c0
29569
+ //# debugId=1a42a4ca-6765-5a66-abc9-6b57464bc9c6
@@ -1,10 +1,10 @@
1
1
  // conformance/index.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e3097683-993f-599d-92ef-d1e724637586")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cc85d6cc-8968-5fbd-b7b7-eab3d298263d")}catch(e){}}();
4
4
  import { spawnSync } from "child_process";
5
5
  import { cpSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from "fs";
6
6
  import { tmpdir } from "os";
7
- import { isAbsolute, join as join3, relative, resolve as resolve2 } from "path";
7
+ import { isAbsolute, join as join2, relative, resolve } from "path";
8
8
 
9
9
  // kernel/util/format-error.ts
10
10
  function formatErrorMessage(err) {
@@ -12,20 +12,8 @@ function formatErrorMessage(err) {
12
12
  }
13
13
 
14
14
  // kernel/util/skill-map-paths.ts
15
- import { join as join2 } from "path";
16
-
17
- // core/paths/db-path.ts
18
- import { join, resolve } from "path";
15
+ import { join } from "path";
19
16
  var SKILL_MAP_DIR = ".skill-map";
20
- var DB_FILENAME = "skill-map.db";
21
- var LOCAL_SETTINGS_FILENAME = "settings.local.json";
22
- var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
23
- var GITIGNORE_ENTRIES = [
24
- `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
25
- `${SKILL_MAP_DIR}/${DB_FILENAME}`
26
- ];
27
-
28
- // kernel/util/skill-map-paths.ts
29
17
  var KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;
30
18
 
31
19
  // kernel/util/tx.ts
@@ -127,9 +115,9 @@ function pickSafeEnv(source) {
127
115
  function runConformanceCase(options) {
128
116
  const raw = readFileSync(options.casePath, "utf8");
129
117
  const c = JSON.parse(raw);
130
- const fixturesRoot = options.fixturesRoot ?? join3(options.specRoot, "conformance", "fixtures");
118
+ const fixturesRoot = options.fixturesRoot ?? join2(options.specRoot, "conformance", "fixtures");
131
119
  const safeId = c.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 32);
132
- const scope = mkdtempSync(join3(tmpdir(), `sm-conformance-${safeId}-`));
120
+ const scope = mkdtempSync(join2(tmpdir(), `sm-conformance-${safeId}-`));
133
121
  const setupEnv = disableEnv(c.setup);
134
122
  try {
135
123
  const priorFailure = runPriorScansSetup(c, options, scope, fixturesRoot, setupEnv);
@@ -201,9 +189,9 @@ function replaceFixture(scope, fixturesRoot, fixture) {
201
189
  assertContained(fixturesRoot, fixture, "fixture");
202
190
  for (const entry of readdirSync(scope)) {
203
191
  if (entry === KERNEL_SKILL_MAP_DIR) continue;
204
- rmSync(join3(scope, entry), { recursive: true, force: true });
192
+ rmSync(join2(scope, entry), { recursive: true, force: true });
205
193
  }
206
- const src = join3(fixturesRoot, fixture);
194
+ const src = join2(fixturesRoot, fixture);
207
195
  cpSync(src, scope, { recursive: true });
208
196
  }
209
197
  function assertContained(root, rel, label) {
@@ -212,7 +200,7 @@ function assertContained(root, rel, label) {
212
200
  tx(CONFORMANCE_RUNNER_TEXTS.pathMustBeRelative, { label, path: rel, anchor: root })
213
201
  );
214
202
  }
215
- const abs = resolve2(root, rel);
203
+ const abs = resolve(root, rel);
216
204
  const r = relative(root, abs);
217
205
  if (r.startsWith("..") || isAbsolute(r)) {
218
206
  throw new Error(
@@ -239,7 +227,7 @@ function evaluateAssertion(a, ctx) {
239
227
  } catch (err) {
240
228
  return { ok: false, type: a.type, reason: formatErrorMessage(err) };
241
229
  }
242
- const abs = resolve2(ctx.scope, a.path);
230
+ const abs = resolve(ctx.scope, a.path);
243
231
  return existsSync(abs) ? { ok: true, type: a.type } : {
244
232
  ok: false,
245
233
  type: a.type,
@@ -253,8 +241,8 @@ function evaluateAssertion(a, ctx) {
253
241
  } catch (err) {
254
242
  return { ok: false, type: a.type, reason: formatErrorMessage(err) };
255
243
  }
256
- const fixturePath = join3(ctx.fixturesRoot, a.fixture);
257
- const targetPath = resolve2(ctx.scope, a.path);
244
+ const fixturePath = join2(ctx.fixturesRoot, a.fixture);
245
+ const targetPath = resolve(ctx.scope, a.path);
258
246
  if (!existsSync(targetPath)) {
259
247
  return {
260
248
  ok: false,
@@ -415,7 +403,7 @@ function deepEqual(a, b) {
415
403
  return false;
416
404
  }
417
405
  function assertSpecRoot(specRoot) {
418
- const indexPath = join3(specRoot, "index.json");
406
+ const indexPath = join2(specRoot, "index.json");
419
407
  if (!existsSync(indexPath) || !statSync(indexPath).isFile()) {
420
408
  throw new Error(tx(CONFORMANCE_RUNNER_TEXTS.specRootMissingIndex, { specRoot }));
421
409
  }
@@ -425,4 +413,4 @@ export {
425
413
  runConformanceCase
426
414
  };
427
415
  //# sourceMappingURL=index.js.map
428
- //# debugId=e3097683-993f-599d-92ef-d1e724637586
416
+ //# debugId=cc85d6cc-8968-5fbd-b7b7-eab3d298263d