@skill-map/cli 0.50.0 → 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]="0b2c1ef1-1905-525e-aa62-42c3a9c3c6a1")}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.0",
249
+ version: "0.51.0",
250
250
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
251
251
  license: "MIT",
252
252
  type: "module",
@@ -2865,11 +2865,13 @@ function isPathStyleLink(link) {
2865
2865
  // plugins/core/analyzers/reference-redundant/text.ts
2866
2866
  var REFERENCE_REDUNDANT_TEXTS = {
2867
2867
  /**
2868
- * Multi-form / multi-occurrence reference message. Lists each
2869
- * occurrence (trigger + line) so the operator sees the full
2870
- * authorial surface without having to grep the body.
2868
+ * Multi-form / multi-occurrence reference message. Short and direct:
2869
+ * names the duplicated target + count and lists each occurrence
2870
+ * (trigger + line) so the operator sees the offending spots at a
2871
+ * glance. The source node is the finding's own node, so it is not
2872
+ * repeated here.
2871
2873
  */
2872
- message: "{{source}} references {{resolvedTarget}} via {{count}} occurrences: {{occurrences}}. Consider consolidating to a single form to reduce maintenance surface and avoid duplicate inlining at runtime.",
2874
+ message: "Duplicate reference to {{resolvedTarget}} ({{count}} occurrences): {{occurrences}}.",
2873
2875
  /** Inline separator between occurrences in the message. */
2874
2876
  occurrenceSeparator: ", ",
2875
2877
  /** Per-occurrence formatting (trigger + line). */
@@ -3985,7 +3987,7 @@ var nodeSupersedeAction = {
3985
3987
  }
3986
3988
  };
3987
3989
 
3988
- // core/update-check/index.ts
3990
+ // kernel/update-check/index.ts
3989
3991
  var SEMVER_SHAPE_RE = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3990
3992
  async function fetchLatestVersion(pkg, opts) {
3991
3993
  const controller = new AbortController();
@@ -4120,9 +4122,9 @@ function ansiFor(opts) {
4120
4122
  import { randomUUID } from "crypto";
4121
4123
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4122
4124
  import { homedir } from "os";
4123
- import { join as join2 } from "path";
4125
+ import { join as join3 } from "path";
4124
4126
 
4125
- // core/config/atomic-write.ts
4127
+ // kernel/util/atomic-write.ts
4126
4128
  import {
4127
4129
  closeSync,
4128
4130
  constants as fsConstants,
@@ -4180,17 +4182,31 @@ function writeJsonAtomic(path, content) {
4180
4182
  }
4181
4183
 
4182
4184
  // core/paths/db-path.ts
4183
- 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";
4184
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
4185
4201
  var DB_FILENAME = "skill-map.db";
4186
4202
  var JOBS_DIRNAME = "jobs";
4187
4203
  var PLUGINS_DIRNAME = "plugins";
4188
- var SETTINGS_FILENAME = "settings.json";
4189
- var LOCAL_SETTINGS_FILENAME = "settings.local.json";
4204
+ var SETTINGS_FILENAME2 = "settings.json";
4205
+ var LOCAL_SETTINGS_FILENAME2 = "settings.local.json";
4190
4206
  var IGNORE_FILENAME = ".skillmapignore";
4191
4207
  var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
4192
4208
  var GITIGNORE_ENTRIES = [
4193
- `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
4209
+ `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME2}`,
4194
4210
  `${SKILL_MAP_DIR}/${DB_FILENAME}`
4195
4211
  ];
4196
4212
  function resolveDbPath(options) {
@@ -4207,23 +4223,23 @@ function defaultProjectPluginsDir(ctx) {
4207
4223
  return resolve6(ctx.cwd, SKILL_MAP_DIR, PLUGINS_DIRNAME);
4208
4224
  }
4209
4225
  function defaultDbPath(scopeRoot) {
4210
- return join(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);
4226
+ return join2(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);
4211
4227
  }
4212
4228
  function defaultSettingsPath(scopeRoot) {
4213
- return join(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME);
4229
+ return join2(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME2);
4214
4230
  }
4215
4231
  function defaultLocalSettingsPath(scopeRoot) {
4216
- return join(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);
4232
+ return join2(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME2);
4217
4233
  }
4218
4234
  function defaultIgnoreFilePath(scopeRoot) {
4219
- return join(scopeRoot, IGNORE_FILENAME);
4235
+ return join2(scopeRoot, IGNORE_FILENAME);
4220
4236
  }
4221
4237
 
4222
4238
  // cli/util/user-settings-store.ts
4223
4239
  var FILENAME = "settings.json";
4224
4240
  var SCHEMA_VERSION = 1;
4225
4241
  function userSettingsFilePath() {
4226
- return join2(homedir(), SKILL_MAP_DIR, FILENAME);
4242
+ return join3(homedir(), SKILL_MAP_DIR, FILENAME);
4227
4243
  }
4228
4244
  function defaultSettings() {
4229
4245
  return { schemaVersion: SCHEMA_VERSION, updateCheck: {}, telemetry: {} };
@@ -4262,7 +4278,7 @@ function backfillSubObjects(settings) {
4262
4278
  };
4263
4279
  }
4264
4280
  function writeUserSettings(patch) {
4265
- const dir = join2(homedir(), SKILL_MAP_DIR);
4281
+ const dir = join3(homedir(), SKILL_MAP_DIR);
4266
4282
  const path = userSettingsFilePath();
4267
4283
  try {
4268
4284
  const current = readUserSettings();
@@ -5359,18 +5375,6 @@ var CONFIG_LOADER_TEXTS = {
5359
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)."
5360
5376
  };
5361
5377
 
5362
- // kernel/util/skill-map-paths.ts
5363
- import { join as join3 } from "path";
5364
- var KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;
5365
- var SETTINGS_FILENAME2 = "settings.json";
5366
- var LOCAL_SETTINGS_FILENAME2 = "settings.local.json";
5367
- function kernelSettingsPath(scopeRoot) {
5368
- return join3(scopeRoot, KERNEL_SKILL_MAP_DIR, SETTINGS_FILENAME2);
5369
- }
5370
- function kernelLocalSettingsPath(scopeRoot) {
5371
- return join3(scopeRoot, KERNEL_SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME2);
5372
- }
5373
-
5374
5378
  // config/defaults.json
5375
5379
  var defaults_default = {
5376
5380
  schemaVersion: 1,
@@ -5828,11 +5832,20 @@ var FilesystemSidecarStore = class {
5828
5832
  * files in the repo and entries are tiny).
5829
5833
  */
5830
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
+ }
5831
5847
  async applyPatch(sidecarAbsPath, changes, consent) {
5832
- ensureSidecarWritesAllowed({
5833
- confirm: consent.confirm,
5834
- cwd: consent.cwd
5835
- });
5848
+ this.#consentGate(consent);
5836
5849
  const prev = this.#locks.get(sidecarAbsPath) ?? Promise.resolve();
5837
5850
  let release;
5838
5851
  const settled = new Promise((res) => {
@@ -7856,7 +7869,7 @@ function rowToContribution(row) {
7856
7869
  };
7857
7870
  }
7858
7871
 
7859
- // core/sqlite/schema-fingerprint.ts
7872
+ // kernel/adapters/sqlite/schema-fingerprint.ts
7860
7873
  import { createHash } from "crypto";
7861
7874
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
7862
7875
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
@@ -9332,7 +9345,7 @@ var BumpCommand = class extends SmCommand {
9332
9345
  * the staging missed).
9333
9346
  */
9334
9347
  async #executePending(plan, cwd, ansi) {
9335
- const store = new FilesystemSidecarStore();
9348
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
9336
9349
  const ctx = defaultRuntimeContext();
9337
9350
  const consent = {
9338
9351
  confirm: this.yes,
@@ -9478,7 +9491,7 @@ function buildBumpedOutcome(item, sidecarPath) {
9478
9491
  if (item.report.createdSidecar === true) outcome.createdSidecar = true;
9479
9492
  return outcome;
9480
9493
  }
9481
- async function applyBumpWrites(item, consent, store = new FilesystemSidecarStore()) {
9494
+ async function applyBumpWrites(item, consent, store = new FilesystemSidecarStore(ensureSidecarWritesAllowed)) {
9482
9495
  let sidecarPath;
9483
9496
  try {
9484
9497
  for (const w of item.writes) {
@@ -11421,20 +11434,9 @@ function trimRedundantPath(message, primary) {
11421
11434
  import { existsSync as existsSync16 } from "fs";
11422
11435
  import { Command as Command4, Option as Option4 } from "clipanion";
11423
11436
 
11424
- // core/config/active-provider.ts
11437
+ // kernel/scan/detect-providers.ts
11425
11438
  import { existsSync as existsSync15 } from "fs";
11426
11439
  import { join as join10 } from "path";
11427
- function resolveActiveProvider(cwd, providers = []) {
11428
- const detected = detectProvidersFromFilesystem(cwd, providers);
11429
- const fromConfig = readConfigValue("activeProvider", { cwd });
11430
- if (typeof fromConfig === "string" && fromConfig.length > 0) {
11431
- return { resolved: fromConfig, source: "config", detected };
11432
- }
11433
- if (detected.length > 0) {
11434
- return { resolved: detected[0], source: "autodetect", detected };
11435
- }
11436
- return { resolved: null, source: "none", detected };
11437
- }
11438
11440
  function detectProvidersFromFilesystem(cwd, providers) {
11439
11441
  const seen = /* @__PURE__ */ new Set();
11440
11442
  const out = [];
@@ -11449,6 +11451,19 @@ function detectProvidersFromFilesystem(cwd, providers) {
11449
11451
  return out;
11450
11452
  }
11451
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
+
11452
11467
  // cli/util/path-display.ts
11453
11468
  import { isAbsolute as isAbsolute4, relative as pathRelative } from "path";
11454
11469
  function relativeIfBelow(path, cwd) {
@@ -16980,7 +16995,7 @@ function resolveActiveProviderOption(optionValue, roots, providers) {
16980
16995
  for (const root of roots) {
16981
16996
  const absRoot = isAbsolute7(root) ? root : resolve28(root);
16982
16997
  if (!existsSync23(absRoot)) continue;
16983
- const detected = resolveActiveProvider(absRoot, providers).resolved;
16998
+ const detected = detectProvidersFromFilesystem(absRoot, providers)[0] ?? null;
16984
16999
  if (detected !== null) return detected;
16985
17000
  }
16986
17001
  return null;
@@ -22270,7 +22285,7 @@ var IntentionalFailCommand = class extends SmCommand {
22270
22285
  throw new Error(INTENTIONAL_FAIL_TEXTS.errorMessage);
22271
22286
  }, 0);
22272
22287
  await new Promise((resolve40) => setTimeout(resolve40, 5e3));
22273
- return 1;
22288
+ return ExitCode.Issues;
22274
22289
  }
22275
22290
  };
22276
22291
 
@@ -22339,6 +22354,25 @@ var SCAN_TEXTS = {
22339
22354
  persistedTo: " {{dbPath}}\n",
22340
22355
  /** Body line for dry-run mode, same indent, marker tail. */
22341
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",
22342
22376
  /**
22343
22377
  * Cap-hit notice, printed when the walker stopped accepting nodes
22344
22378
  * because `--max-nodes` (or the `scan.maxNodes` setting) was reached.
@@ -23420,27 +23454,29 @@ function fillSeverityBucket(bucket, nodeIds) {
23420
23454
  function formatScanCounts(opts) {
23421
23455
  const { nodes, links, severities, ansi } = opts;
23422
23456
  const parts = [
23423
- `${nodes} ${plural(nodes, "node")}`,
23424
- `${links} ${plural(links, "link")}`
23457
+ `${nodes} ${countNoun(nodes, SCAN_TEXTS.countNodeNounSingular, SCAN_TEXTS.countNodeNounPlural)}`,
23458
+ `${links} ${countNoun(links, SCAN_TEXTS.countLinkNounSingular, SCAN_TEXTS.countLinkNounPlural)}`
23425
23459
  ];
23426
23460
  const total = severities.errors + severities.warns + severities.info;
23427
23461
  if (total === 0) {
23428
- parts.push(ansi.dim("0 issues"));
23462
+ parts.push(ansi.dim(SCAN_TEXTS.countNoIssues));
23429
23463
  } else {
23430
23464
  if (severities.errors > 0) {
23431
- 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}`));
23432
23467
  }
23433
23468
  if (severities.warns > 0) {
23434
- 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}`));
23435
23471
  }
23436
23472
  if (severities.info > 0) {
23437
- parts.push(ansi.dim(`${severities.info} info`));
23473
+ parts.push(ansi.dim(`${severities.info} ${SCAN_TEXTS.countInfoNoun}`));
23438
23474
  }
23439
23475
  }
23440
23476
  return parts.join(" \xB7 ");
23441
23477
  }
23442
- function plural(count, word) {
23443
- return count === 1 ? word : `${word}s`;
23478
+ function countNoun(count, singular, plural) {
23479
+ return count === 1 ? singular : plural;
23444
23480
  }
23445
23481
 
23446
23482
  // cli/commands/scan-compare.ts
@@ -23886,11 +23922,10 @@ var SERVER_TEXTS = {
23886
23922
  // here (after static + SPA fallback have had their turn).
23887
23923
  unknownPath: "Not found: {{path}}.",
23888
23924
  // ---- sidecar bump route (routes/sidecar.ts) ------------------------------
23889
- // 409 refusal when a fresh node is bumped without `force`. The
23890
- // `sidecar-fresh:` prefix is load-bearing, the UI pattern-matches
23891
- // it (the global `app.onError` already maps HTTP 409 to the
23892
- // `sidecar-fresh` envelope `code`, so the prefix is for log-grep
23893
- // 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.
23894
23929
  sidecarFreshRefusal: "sidecar-fresh: Node is fresh; pass force:true to bump anyway.",
23895
23930
  // 400 envelopes thrown by `parseBody` when the request payload is
23896
23931
  // malformed. Each branch has its own key so the UI / log can
@@ -23918,9 +23953,9 @@ var SERVER_TEXTS = {
23918
23953
  // dropped half the pipeline. Same gate the `?fresh=1` GET applies.
23919
23954
  scanPostRequiresFullPipeline: "POST /api/scan cannot run while the server was started with --no-built-ins or --no-plugins (would persist a partial DB).",
23920
23955
  // 409, another scan (watcher batch or another POST) is in flight.
23921
- // The `scan-busy:` prefix is load-bearing: HTTP 409 maps to
23922
- // `scan-busy` in `app.onError`'s `codeForStatus`, but the prefix
23923
- // 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.
23924
23959
  scanPostBusy: "scan-busy: Another scan is already in flight; retry once it finishes.",
23925
23960
  // 500, DB missing on a write path. Read paths degrade to empty
23926
23961
  // shapes; mutations cannot persist without a DB so they fail fast.
@@ -24533,6 +24568,7 @@ function registerHealthRoute(app, deps) {
24533
24568
  var DEFAULT_LIMIT = 100;
24534
24569
  var MAX_LIMIT = 1e3;
24535
24570
  var BFF_MAX_BULK_CONTRIBUTIONS = 200;
24571
+ var MAX_WS_CLIENTS = 64;
24536
24572
 
24537
24573
  // server/routes/issues.ts
24538
24574
  function registerIssuesRoute(app, deps) {
@@ -24765,7 +24801,7 @@ function registerNodesRoutes(app, deps) {
24765
24801
  const tags = result?.tags ?? [];
24766
24802
  if (!bundle) {
24767
24803
  throw new HTTPException7(404, {
24768
- message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
24804
+ message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
24769
24805
  });
24770
24806
  }
24771
24807
  const decoratedNode = { ...bundle.node, isFavorite, contributions, tags };
@@ -26055,7 +26091,7 @@ async function runPersistedScan(c, deps) {
26055
26091
  });
26056
26092
  } catch (err) {
26057
26093
  if (err instanceof ScanBusyError) {
26058
- throw new HTTPException14(409, { message: SERVER_TEXTS.scanPostBusy });
26094
+ throw new ConflictError({ code: "scan-busy", message: SERVER_TEXTS.scanPostBusy });
26059
26095
  }
26060
26096
  throw err;
26061
26097
  }
@@ -26248,7 +26284,7 @@ function registerSidecarRoutes(app, deps) {
26248
26284
  }
26249
26285
  const result = invokeBump2(node, absPath, body);
26250
26286
  if (result.report.ok === false && result.report.reason === "fresh") {
26251
- throw new HTTPException15(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
26287
+ throw new ConflictError({ code: "sidecar-fresh", message: SERVER_TEXTS.sidecarFreshRefusal });
26252
26288
  }
26253
26289
  if (result.report.ok === true && result.report.noop === true) {
26254
26290
  const envelope2 = {
@@ -26263,7 +26299,7 @@ function registerSidecarRoutes(app, deps) {
26263
26299
  };
26264
26300
  return c.json(envelope2);
26265
26301
  }
26266
- const store = new FilesystemSidecarStore();
26302
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
26267
26303
  try {
26268
26304
  for (const w of result.writes ?? []) {
26269
26305
  if (w.kind === "sidecar") {
@@ -26531,6 +26567,14 @@ var LoopbackGateError = class extends HTTPException16 {
26531
26567
  this.code = init.code;
26532
26568
  }
26533
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
+ };
26534
26578
  function createApp(deps) {
26535
26579
  const app = new Hono();
26536
26580
  const configService = new ConfigService({
@@ -26607,16 +26651,12 @@ function createApp(deps) {
26607
26651
  });
26608
26652
  return app;
26609
26653
  }
26610
- function codeForStatus(status, message) {
26654
+ function codeForStatus(status) {
26611
26655
  if (status === 404) return "not-found";
26612
26656
  if (status === 400) return "bad-query";
26613
26657
  if (status === 403) return "locked";
26614
26658
  if (status === 412) return "confirm-required";
26615
26659
  if (status === 413) return "payload-too-large";
26616
- if (status === 409) {
26617
- if (message.startsWith("scan-busy:")) return "scan-busy";
26618
- return "sidecar-fresh";
26619
- }
26620
26660
  return "internal";
26621
26661
  }
26622
26662
  function formatError2(err, c) {
@@ -26653,12 +26693,23 @@ function formatError2(err, c) {
26653
26693
  };
26654
26694
  return c.json(envelope, 403);
26655
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
+ }
26656
26707
  if (err instanceof HTTPException16) {
26657
26708
  const status = err.status;
26658
26709
  const envelope = {
26659
26710
  ok: false,
26660
26711
  error: {
26661
- code: codeForStatus(status, err.message),
26712
+ code: codeForStatus(status),
26662
26713
  message: err.message,
26663
26714
  details: null
26664
26715
  }
@@ -26709,6 +26760,7 @@ function formatInternalErrorFallThrough(err, c) {
26709
26760
  var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
26710
26761
  var CLOSE_CODE_GOING_AWAY = 1001;
26711
26762
  var CLOSE_CODE_MESSAGE_TOO_BIG = 1009;
26763
+ var CLOSE_CODE_TRY_AGAIN_LATER = 1013;
26712
26764
  var READY_STATE_OPEN = 1;
26713
26765
  var WsBroadcaster = class {
26714
26766
  #clients = /* @__PURE__ */ new Set();
@@ -26731,6 +26783,13 @@ var WsBroadcaster = class {
26731
26783
  }
26732
26784
  return;
26733
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
+ }
26734
26793
  this.#clients.add(ws);
26735
26794
  }
26736
26795
  /**
@@ -26977,14 +27036,19 @@ function validatePort(port) {
26977
27036
  return null;
26978
27037
  }
26979
27038
  function validateHost(host, devCors) {
26980
- if (devCors && !isLoopbackHost(host)) {
27039
+ if (isLoopbackHost(host)) return null;
27040
+ if (devCors) {
26981
27041
  return {
26982
27042
  code: "host-dev-cors-rejected",
26983
27043
  message: `--dev-cors requires a loopback --host (got ${host})`,
26984
27044
  value: host
26985
27045
  };
26986
27046
  }
26987
- 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
+ };
26988
27052
  }
26989
27053
  function validateWatcher(noWatcher, noBuiltIns, _noPlugins) {
26990
27054
  if (noWatcher) return null;
@@ -27272,6 +27336,14 @@ var SERVE_TEXTS = {
27272
27336
  */
27273
27337
  hostDevCorsRejected: "{{glyph}} sm serve: --dev-cors requires a loopback --host (got {{host}}).\n {{hint}}\n",
27274
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).",
27275
27347
  /**
27276
27348
  * §3.1b error block when `--port` falls outside the [0, 65535] range.
27277
27349
  * Hint names the accepted range so the operator can re-run.
@@ -27820,6 +27892,12 @@ function formatValidationError(err, ansi) {
27820
27892
  host: sanitizeForTerminal(err.value),
27821
27893
  hint: ansi.dim(SERVE_TEXTS.hostDevCorsRejectedHint)
27822
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
+ });
27823
27901
  case "port-out-of-range":
27824
27902
  return tx(SERVE_TEXTS.portOutOfRange, {
27825
27903
  glyph: errGlyph,
@@ -28412,7 +28490,7 @@ var SidecarRefreshCommand = class extends SmCommand {
28412
28490
  );
28413
28491
  return ExitCode.Ok;
28414
28492
  }
28415
- const store = new FilesystemSidecarStore();
28493
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
28416
28494
  try {
28417
28495
  await store.applyPatch(
28418
28496
  sidecarAbsPath,
@@ -28699,7 +28777,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
28699
28777
  return ExitCode.Error;
28700
28778
  }
28701
28779
  }
28702
- const store = new FilesystemSidecarStore();
28780
+ const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
28703
28781
  try {
28704
28782
  await store.applyPatch(
28705
28783
  sidecarAbsPath,
@@ -29488,4 +29566,4 @@ function resolveBareDefault() {
29488
29566
  process.exit(ExitCode.Error);
29489
29567
  }
29490
29568
  //# sourceMappingURL=cli.js.map
29491
- //# debugId=0b2c1ef1-1905-525e-aa62-42c3a9c3c6a1
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