@skill-map/cli 0.46.0 → 0.47.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,7 +1,7 @@
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]="7c131b78-b7ad-5f03-b19c-1f70afdfeefb")}catch(e){}}();
4
- import { existsSync as existsSync32 } from "fs";
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]="926243f9-3707-54bc-9aa6-c68955118161")}catch(e){}}();
4
+ import { existsSync as existsSync33 } from "fs";
5
5
  import { Builtins, Cli as Cli2 } from "clipanion";
6
6
 
7
7
  // kernel/adapters/in-memory-progress.ts
@@ -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.46.0",
249
+ version: "0.47.0",
250
250
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
251
251
  license: "MIT",
252
252
  type: "module",
@@ -6220,12 +6220,12 @@ var SmCommand = class extends Command {
6220
6220
  };
6221
6221
 
6222
6222
  // core/sqlite/with-sqlite.ts
6223
- import { existsSync as existsSync10 } from "fs";
6223
+ import { existsSync as existsSync11 } from "fs";
6224
6224
 
6225
6225
  // kernel/adapters/sqlite/storage-adapter.ts
6226
6226
  import { mkdirSync as mkdirSync4 } from "fs";
6227
6227
  import { dirname as dirname8, resolve as resolve12 } from "path";
6228
- import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
6228
+ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
6229
6229
  import { CamelCasePlugin, Kysely, sql as sql3 } from "kysely";
6230
6230
 
6231
6231
  // kernel/i18n/storage.texts.ts
@@ -7513,6 +7513,12 @@ async function loadScanResult(db) {
7513
7513
  roots: parseJsonArray(metaRow.rootsJson),
7514
7514
  providers: parseJsonArray(metaRow.providersJson),
7515
7515
  scannedBy,
7516
+ // Resolved encoder of the prior scan (see project-config.schema.json
7517
+ // §tokenizer). NULL column → `undefined` domain field; the
7518
+ // orchestrator's tokenizer-change check compares this against the
7519
+ // freshly-resolved encoder and treats a missing prior value as a
7520
+ // change (forcing a token recompute).
7521
+ ...metaRow.tokenizer !== null ? { tokenizer: metaRow.tokenizer } : {},
7516
7522
  recommendedNodeLimit: metaRow.recommendedNodeLimit,
7517
7523
  overrideMaxNodes: metaRow.overrideMaxNodes,
7518
7524
  oversizedFiles,
@@ -7846,6 +7852,58 @@ function rowToContribution(row) {
7846
7852
  };
7847
7853
  }
7848
7854
 
7855
+ // core/sqlite/schema-fingerprint.ts
7856
+ import { createHash } from "crypto";
7857
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
7858
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
7859
+ var memoized = null;
7860
+ function schemaFingerprint(files) {
7861
+ if (files === void 0 && memoized !== null) return memoized;
7862
+ const migrations = files ?? discoverMigrations();
7863
+ const hash = createHash("sha256");
7864
+ for (const m of migrations) {
7865
+ const sql4 = existsSync10(m.filePath) ? readFileSync10(m.filePath, "utf8") : "";
7866
+ hash.update(`${m.version}\0${m.description}\0${Buffer.byteLength(sql4)}\0`);
7867
+ hash.update(sql4);
7868
+ }
7869
+ const digest = hash.digest("hex");
7870
+ if (files === void 0) memoized = digest;
7871
+ return digest;
7872
+ }
7873
+ function readStoredFingerprint(dbPath) {
7874
+ if (dbPath === ":memory:" || !existsSync10(dbPath)) return { kind: "no-meta" };
7875
+ let raw = null;
7876
+ try {
7877
+ raw = new DatabaseSync3(dbPath, { readOnly: true });
7878
+ return readStoredFingerprintFrom(raw);
7879
+ } catch {
7880
+ return { kind: "no-meta" };
7881
+ } finally {
7882
+ raw?.close();
7883
+ }
7884
+ }
7885
+ function readStoredFingerprintFrom(raw) {
7886
+ if (!columnExists(raw, "scan_meta", "schema_fingerprint")) {
7887
+ const hasRow = raw.prepare("SELECT 1 AS present FROM scan_meta LIMIT 1").get();
7888
+ return hasRow ? { kind: "absent" } : { kind: "no-meta" };
7889
+ }
7890
+ const row = raw.prepare("SELECT schema_fingerprint AS fp FROM scan_meta LIMIT 1").get();
7891
+ if (!row) return { kind: "no-meta" };
7892
+ const fp = row.fp;
7893
+ if (typeof fp !== "string" || fp.length === 0) return { kind: "absent" };
7894
+ return { kind: "value", fingerprint: fp };
7895
+ }
7896
+ function columnExists(db, table, column) {
7897
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
7898
+ return rows.some((r) => r.name === column);
7899
+ }
7900
+ function classifyFingerprint(dbPath) {
7901
+ const stored = readStoredFingerprint(dbPath);
7902
+ if (stored.kind === "no-meta") return { kind: "no-meta" };
7903
+ if (stored.kind === "absent") return { kind: "drift" };
7904
+ return stored.fingerprint === schemaFingerprint() ? { kind: "ok" } : { kind: "drift" };
7905
+ }
7906
+
7849
7907
  // kernel/adapters/sqlite/tags.ts
7850
7908
  async function replaceAllScanTags(trx, records, livePaths = /* @__PURE__ */ new Set()) {
7851
7909
  if (livePaths.size > 0) {
@@ -8125,10 +8183,21 @@ function metaToRow(result) {
8125
8183
  scannedByName: result.scannedBy?.name ?? "skill-map",
8126
8184
  scannedByVersion: result.scannedBy?.version ?? "unknown",
8127
8185
  scannedBySpecVersion: result.scannedBy?.specVersion ?? "unknown",
8186
+ // Schema-drift fingerprint: sha256 over the bundled migration DDL.
8187
+ // Sibling of `scannedByVersion`, the second drift axis the next
8188
+ // write-side open compares (see spec/db-schema.md §Schema drift
8189
+ // (pre-1.0)). Internal DB metadata, never on the ScanResult wire.
8190
+ schemaFingerprint: schemaFingerprint(),
8128
8191
  providersJson: JSON.stringify(result.providers),
8129
8192
  statsFilesWalked: result.stats.filesWalked,
8130
8193
  statsFilesSkipped: result.stats.filesSkipped,
8131
8194
  statsDurationMs: result.stats.durationMs,
8195
+ // Resolved encoder that produced the token counts (see
8196
+ // project-config.schema.json §tokenizer). NULL on synthetic results
8197
+ // that bypass the orchestrator / skip tokenization; a real scan always
8198
+ // carries it. The next incremental scan reads this back to detect an
8199
+ // encoder switch and force a token recompute.
8200
+ tokenizer: result.tokenizer ?? null,
8132
8201
  ...projectNodeLimitColumns(result),
8133
8202
  ...projectOversizedColumns(result)
8134
8203
  };
@@ -8263,7 +8332,7 @@ var SqliteStorageAdapter = class {
8263
8332
  if (this.#options.autoMigrate !== false) {
8264
8333
  const files = discoverMigrations();
8265
8334
  if (files.length > 0) {
8266
- const raw = new DatabaseSync3(path);
8335
+ const raw = new DatabaseSync4(path);
8267
8336
  try {
8268
8337
  raw.exec("PRAGMA foreign_keys = ON");
8269
8338
  applyMigrations(
@@ -8650,7 +8719,7 @@ async function listFavoritePaths(db) {
8650
8719
  return new Set(rows.map((r) => r.nodePath));
8651
8720
  }
8652
8721
  function withRawDb(path, fn) {
8653
- const raw = new DatabaseSync3(path);
8722
+ const raw = new DatabaseSync4(path);
8654
8723
  try {
8655
8724
  return fn(raw);
8656
8725
  } finally {
@@ -8729,7 +8798,14 @@ var DB_VERSION_TEXTS = {
8729
8798
  dbVersionMajorMismatch: "{{glyph}} This DB was written by skill-map {{dbVersion}}; the CLI you are running ({{currentVersion}}) is on a different major series.\n {{hint}}\n",
8730
8799
  dbVersionMajorMismatchHint: "Delete the `.skill-map/` directory and re-scan, or revert to a CLI in the {{dbMajor}}.x series.",
8731
8800
  dbVersionOlder: "{{glyph}} This DB was last written by an older skill-map ({{dbVersion}}, you have {{currentVersion}}).\n {{hint}}\n",
8732
- dbVersionOlderHint: "Behaviour may differ until the next `sm scan` rewrites the metadata; downstream parse errors are likely a symptom of this skew."
8801
+ dbVersionOlderHint: "Behaviour may differ until the next `sm scan` rewrites the metadata; downstream parse errors are likely a symptom of this skew.",
8802
+ // Schema-fingerprint drift on a same-version DB (pre-1.0 greenfield:
8803
+ // a column was added inline to a migration with no version bump, so
8804
+ // the version axis reads as compatible but the on-disk schema is
8805
+ // older). WARN, `⚠` yellow, the read continues but a query may hit a
8806
+ // missing column. Has no `dbVersion` placeholder, the version matched.
8807
+ dbSchemaDrift: "{{glyph}} This DB predates a schema change in skill-map {{currentVersion}} (same version, older columns).\n {{hint}}\n",
8808
+ dbSchemaDriftHint: "Run `sm scan` to rebuild the local cache (your .sm sidecars are untouched), or `sm db reset`; some columns may be missing until then."
8733
8809
  // The defensive wrapper for `parseConfidence` / `parseLinkKind` /
8734
8810
  // `parseSeverity` failures during `loadScanResult` (when the meta
8735
8811
  // row was wiped and the version check returned `no-meta`) lives in
@@ -8744,10 +8820,17 @@ var DB_VERSION_TEXTS = {
8744
8820
  // core/sqlite/db-version-runner.ts
8745
8821
  var WARN_SEEN = /* @__PURE__ */ new Set();
8746
8822
  async function runDbVersionCheck(db, opts) {
8747
- const outcome = await detectDbVersionSkew(db, opts.currentVersion);
8823
+ const versionOutcome = await detectDbVersionSkew(db, opts.currentVersion);
8824
+ const outcome = layerFingerprintOutcome(versionOutcome, opts);
8748
8825
  applyDbVersionOutcome(outcome, opts);
8749
8826
  return outcome;
8750
8827
  }
8828
+ function layerFingerprintOutcome(versionOutcome, opts) {
8829
+ if (versionOutcome.kind !== "ok") return versionOutcome;
8830
+ const fp = classifyFingerprint(opts.dbPath);
8831
+ if (fp.kind === "drift") return { kind: "warn-schema", currentVersion: opts.currentVersion };
8832
+ return versionOutcome;
8833
+ }
8751
8834
  function applyDbVersionOutcome(outcome, opts) {
8752
8835
  switch (outcome.kind) {
8753
8836
  case "ok":
@@ -8760,6 +8843,9 @@ function applyDbVersionOutcome(outcome, opts) {
8760
8843
  case "warn-older":
8761
8844
  renderWarnOlder(outcome, opts);
8762
8845
  break;
8846
+ case "warn-schema":
8847
+ renderWarnSchema(outcome, opts);
8848
+ break;
8763
8849
  }
8764
8850
  }
8765
8851
  function renderErrorNewer(outcome, opts) {
@@ -8813,6 +8899,21 @@ function renderWarnOlder(outcome, opts) {
8813
8899
  })
8814
8900
  );
8815
8901
  }
8902
+ function renderWarnSchema(outcome, opts) {
8903
+ const seen = opts.warnSeen ?? WARN_SEEN;
8904
+ if (seen.has(opts.dbPath)) return;
8905
+ seen.add(opts.dbPath);
8906
+ if (!opts.printer) return;
8907
+ const warnGlyph = opts.style?.warnGlyph ?? "\u26A0";
8908
+ const dim = opts.style?.dim ?? ((s) => s);
8909
+ opts.printer.warn(
8910
+ tx(DB_VERSION_TEXTS.dbSchemaDrift, {
8911
+ glyph: warnGlyph,
8912
+ currentVersion: outcome.currentVersion,
8913
+ hint: dim(DB_VERSION_TEXTS.dbSchemaDriftHint)
8914
+ })
8915
+ );
8916
+ }
8816
8917
 
8817
8918
  // core/sqlite/with-sqlite.ts
8818
8919
  async function withSqlite(options, fn) {
@@ -8832,7 +8933,7 @@ async function withSqlite(options, fn) {
8832
8933
  }
8833
8934
  }
8834
8935
  async function tryWithSqlite(options, fn) {
8835
- if (options.databasePath !== ":memory:" && !existsSync10(options.databasePath)) {
8936
+ if (options.databasePath !== ":memory:" && !existsSync11(options.databasePath)) {
8836
8937
  return null;
8837
8938
  }
8838
8939
  return withSqlite(options, fn);
@@ -9433,6 +9534,19 @@ var CHECK_TEXTS = {
9433
9534
  unknownAnalyzerIds: "sm check: unknown analyzer id(s) in --analyzers: {{unknown}}.\nValid ids (qualified or short form accepted):\n{{known}}\n"
9434
9535
  };
9435
9536
 
9537
+ // cli/util/db-version-check.ts
9538
+ function buildReadVersionCheck(printer, stderrAnsi) {
9539
+ return {
9540
+ currentVersion: VERSION,
9541
+ printer,
9542
+ style: {
9543
+ warnGlyph: stderrAnsi.yellow("\u26A0"),
9544
+ errorGlyph: stderrAnsi.red("\u2715"),
9545
+ dim: stderrAnsi.dim
9546
+ }
9547
+ };
9548
+ }
9549
+
9436
9550
  // cli/util/conformance-env.ts
9437
9551
  var ENV_DISABLE_PROVIDERS = "SKILL_MAP_DISABLE_ALL_PROVIDERS";
9438
9552
  var ENV_DISABLE_EXTRACTORS = "SKILL_MAP_DISABLE_ALL_EXTRACTORS";
@@ -9477,7 +9591,7 @@ var PLUGIN_LOADER_TEXTS = {
9477
9591
 
9478
9592
  // kernel/adapters/plugin-loader/index.ts
9479
9593
  import { createRequire as createRequire5 } from "module";
9480
- import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
9594
+ import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
9481
9595
  import { join as join8, resolve as resolve17 } from "path";
9482
9596
  import { pathToFileURL } from "url";
9483
9597
  import semver from "semver";
@@ -9580,7 +9694,7 @@ function stripFunctionsAndPluginId(input) {
9580
9694
 
9581
9695
  // kernel/adapters/plugin-loader/validation.ts
9582
9696
  import * as nodeFs from "fs";
9583
- import { existsSync as existsSync11 } from "fs";
9697
+ import { existsSync as existsSync12 } from "fs";
9584
9698
  import { dirname as dirname9, join as join7 } from "path";
9585
9699
  import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
9586
9700
 
@@ -9706,7 +9820,7 @@ function validateActionFileConventions(pluginPath, pluginId, manifest, relEntry,
9706
9820
  const reportSchemaPath = join7(actionDir, "report.schema.json");
9707
9821
  const promptPath = join7(actionDir, "prompt.md");
9708
9822
  const mode = isRecord(manifestView) && typeof manifestView["mode"] === "string" ? manifestView["mode"] : "deterministic";
9709
- if (!existsSync11(reportSchemaPath)) {
9823
+ if (!existsSync12(reportSchemaPath)) {
9710
9824
  return {
9711
9825
  ...fail(
9712
9826
  pluginPath,
@@ -9717,7 +9831,7 @@ function validateActionFileConventions(pluginPath, pluginId, manifest, relEntry,
9717
9831
  manifest
9718
9832
  };
9719
9833
  }
9720
- const promptExists = existsSync11(promptPath);
9834
+ const promptExists = existsSync12(promptPath);
9721
9835
  if (mode === "probabilistic" && !promptExists) {
9722
9836
  return {
9723
9837
  ...fail(
@@ -9829,7 +9943,7 @@ function isDirectorySafe(path, statSync12) {
9829
9943
  }
9830
9944
 
9831
9945
  // kernel/adapters/plugin-loader/storage-schemas.ts
9832
- import { readFileSync as readFileSync11 } from "fs";
9946
+ import { readFileSync as readFileSync12 } from "fs";
9833
9947
  import { resolve as resolve16 } from "path";
9834
9948
  import { Ajv2020 as Ajv20206 } from "ajv/dist/2020.js";
9835
9949
 
@@ -9897,7 +10011,7 @@ function compilePluginSchema(pluginPath, relPath) {
9897
10011
  const abs = resolve16(pluginPath, relPath);
9898
10012
  let raw;
9899
10013
  try {
9900
- raw = JSON.parse(readFileSync11(abs, "utf8"));
10014
+ raw = JSON.parse(readFileSync12(abs, "utf8"));
9901
10015
  } catch (err) {
9902
10016
  return { ok: false, phase: "read", errDescription: describe(err) };
9903
10017
  }
@@ -9931,11 +10045,11 @@ var PluginLoader = class {
9931
10045
  discoverPaths() {
9932
10046
  const out = [];
9933
10047
  for (const root of this.#options.searchPaths) {
9934
- if (!existsSync12(root)) continue;
10048
+ if (!existsSync13(root)) continue;
9935
10049
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
9936
10050
  if (!entry.isDirectory()) continue;
9937
10051
  const candidate = join8(root, entry.name);
9938
- if (existsSync12(join8(candidate, "plugin.json"))) {
10052
+ if (existsSync13(join8(candidate, "plugin.json"))) {
9939
10053
  out.push(resolve17(candidate));
9940
10054
  }
9941
10055
  }
@@ -10019,7 +10133,7 @@ var PluginLoader = class {
10019
10133
  const manifestPath = join8(pluginPath, "plugin.json");
10020
10134
  let raw;
10021
10135
  try {
10022
- raw = JSON.parse(readFileSync12(manifestPath, "utf8"));
10136
+ raw = JSON.parse(readFileSync13(manifestPath, "utf8"));
10023
10137
  } catch (err) {
10024
10138
  return { ok: false, failure: fail(
10025
10139
  pluginPath,
@@ -10096,7 +10210,7 @@ var PluginLoader = class {
10096
10210
  } };
10097
10211
  }
10098
10212
  const abs = resolve17(pluginPath, relEntry);
10099
- if (!existsSync12(abs)) {
10213
+ if (!existsSync13(abs)) {
10100
10214
  return { ok: false, failure: {
10101
10215
  ...fail(
10102
10216
  pluginPath,
@@ -10294,7 +10408,7 @@ function discoverExtensionEntries(pluginPath) {
10294
10408
  }
10295
10409
  function collectKindEntries(pluginPath, kindDir, out) {
10296
10410
  const kindAbs = resolve17(pluginPath, kindDir);
10297
- if (!existsSync12(kindAbs)) return;
10411
+ if (!existsSync13(kindAbs)) return;
10298
10412
  let entries;
10299
10413
  try {
10300
10414
  entries = readdirSync4(kindAbs);
@@ -10321,7 +10435,7 @@ function isDirectorySafe2(path) {
10321
10435
  }
10322
10436
  function findIndexCandidate(entryAbs) {
10323
10437
  for (const candidate of INDEX_CANDIDATES) {
10324
- if (existsSync12(resolve17(entryAbs, candidate))) return candidate;
10438
+ if (existsSync13(resolve17(entryAbs, candidate))) return candidate;
10325
10439
  }
10326
10440
  return null;
10327
10441
  }
@@ -10329,7 +10443,7 @@ function installedSpecVersion() {
10329
10443
  const require2 = createRequire5(import.meta.url);
10330
10444
  const indexPath = require2.resolve("@skill-map/spec/index.json");
10331
10445
  const pkgPath = resolve17(indexPath, "..", "package.json");
10332
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
10446
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
10333
10447
  return pkg.version;
10334
10448
  }
10335
10449
 
@@ -10417,7 +10531,7 @@ import { readFile, readdir, lstat } from "fs/promises";
10417
10531
  import { join as join9, relative as relative2, sep as sep3 } from "path";
10418
10532
 
10419
10533
  // kernel/scan/ignore.ts
10420
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
10534
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
10421
10535
  import { dirname as dirname10, resolve as resolve18 } from "path";
10422
10536
  import { fileURLToPath as fileURLToPath2 } from "url";
10423
10537
  import ignoreFactory from "ignore";
@@ -10448,9 +10562,9 @@ function loadBundledIgnoreText() {
10448
10562
  }
10449
10563
  function readIgnoreFileText(scopeRoot) {
10450
10564
  const path = resolve18(scopeRoot, ".skillmapignore");
10451
- if (!existsSync13(path)) return void 0;
10565
+ if (!existsSync14(path)) return void 0;
10452
10566
  try {
10453
- return readFileSync13(path, "utf8");
10567
+ return readFileSync14(path, "utf8");
10454
10568
  } catch {
10455
10569
  return void 0;
10456
10570
  }
@@ -10483,9 +10597,9 @@ function readDefaultsFromDisk() {
10483
10597
  resolve18(here, "config/defaults/skillmapignore")
10484
10598
  ];
10485
10599
  for (const candidate of candidates) {
10486
- if (existsSync13(candidate)) {
10600
+ if (existsSync14(candidate)) {
10487
10601
  try {
10488
- return readFileSync13(candidate, "utf8");
10602
+ return readFileSync14(candidate, "utf8");
10489
10603
  } catch {
10490
10604
  }
10491
10605
  }
@@ -11103,27 +11217,35 @@ var CheckCommand = class extends SmCommand {
11103
11217
  const analyzerFilter = parseAnalyzersFlag(this.analyzers);
11104
11218
  const preflight = await this.#preflightAnalyzerCatalog(analyzerFilter);
11105
11219
  if (preflight.exit !== null) return preflight.exit;
11106
- return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
11107
- let issues = await adapter.issues.listAll();
11108
- if (this.node !== void 0) {
11109
- const nodePath = this.node;
11110
- issues = issues.filter((i) => i.nodeIds.includes(nodePath));
11111
- }
11112
- if (analyzerFilter !== void 0) {
11113
- issues = issues.filter((i) => matchesAnalyzerFilter(i.analyzerId, analyzerFilter));
11114
- }
11115
- const ansi = this.ansiFor("stdout");
11116
- if (this.json) {
11117
- this.printer.data(JSON.stringify(issues) + "\n");
11118
- } else if (issues.length === 0) {
11119
- this.printer.data(
11120
- tx(CHECK_TEXTS.noIssues, { glyph: ansi.green("\u2713") })
11121
- );
11122
- } else {
11123
- this.printer.data(renderHuman(issues, ansi));
11220
+ const stderrAnsi = this.ansiFor("stderr");
11221
+ return withSqlite(
11222
+ {
11223
+ databasePath: dbPath,
11224
+ autoBackup: false,
11225
+ versionCheck: buildReadVersionCheck(this.printer, stderrAnsi)
11226
+ },
11227
+ async (adapter) => {
11228
+ let issues = await adapter.issues.listAll();
11229
+ if (this.node !== void 0) {
11230
+ const nodePath = this.node;
11231
+ issues = issues.filter((i) => i.nodeIds.includes(nodePath));
11232
+ }
11233
+ if (analyzerFilter !== void 0) {
11234
+ issues = issues.filter((i) => matchesAnalyzerFilter(i.analyzerId, analyzerFilter));
11235
+ }
11236
+ const ansi = this.ansiFor("stdout");
11237
+ if (this.json) {
11238
+ this.printer.data(JSON.stringify(issues) + "\n");
11239
+ } else if (issues.length === 0) {
11240
+ this.printer.data(
11241
+ tx(CHECK_TEXTS.noIssues, { glyph: ansi.green("\u2713") })
11242
+ );
11243
+ } else {
11244
+ this.printer.data(renderHuman(issues, ansi));
11245
+ }
11246
+ return issues.some((i) => i.severity === "error") ? ExitCode.Issues : ExitCode.Ok;
11124
11247
  }
11125
- return issues.some((i) => i.severity === "error") ? ExitCode.Issues : ExitCode.Ok;
11126
- });
11248
+ );
11127
11249
  }
11128
11250
  /**
11129
11251
  * Either an explicit `--analyzers` list or `--include-prob` forces a
@@ -11292,11 +11414,11 @@ function trimRedundantPath(message, primary) {
11292
11414
  }
11293
11415
 
11294
11416
  // cli/commands/config.ts
11295
- import { existsSync as existsSync15 } from "fs";
11417
+ import { existsSync as existsSync16 } from "fs";
11296
11418
  import { Command as Command4, Option as Option4 } from "clipanion";
11297
11419
 
11298
11420
  // core/config/active-provider.ts
11299
- import { existsSync as existsSync14 } from "fs";
11421
+ import { existsSync as existsSync15 } from "fs";
11300
11422
  import { join as join10 } from "path";
11301
11423
  function resolveActiveProvider(cwd, providers = []) {
11302
11424
  const detected = detectProvidersFromFilesystem(cwd, providers);
@@ -11316,7 +11438,7 @@ function detectProvidersFromFilesystem(cwd, providers) {
11316
11438
  if (seen.has(provider.id)) continue;
11317
11439
  const markers = provider.detect?.markers;
11318
11440
  if (!markers || markers.length === 0) continue;
11319
- if (!markers.some((marker) => existsSync14(join10(cwd, marker)))) continue;
11441
+ if (!markers.some((marker) => existsSync15(join10(cwd, marker)))) continue;
11320
11442
  seen.add(provider.id);
11321
11443
  out.push(provider.id);
11322
11444
  }
@@ -11333,7 +11455,7 @@ function relativeIfBelow(path, cwd) {
11333
11455
  }
11334
11456
 
11335
11457
  // cli/util/scan-zone-drop.ts
11336
- import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
11458
+ import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
11337
11459
 
11338
11460
  // cli/commands/db/shared.ts
11339
11461
  var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -11345,7 +11467,7 @@ function assertSafeIdentifier(name) {
11345
11467
 
11346
11468
  // cli/util/scan-zone-drop.ts
11347
11469
  function dropScanZone(dbPath) {
11348
- const db = new DatabaseSync4(dbPath);
11470
+ const db = new DatabaseSync5(dbPath);
11349
11471
  try {
11350
11472
  const rows = db.prepare(
11351
11473
  "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'scan\\_%' ESCAPE '\\'"
@@ -11936,7 +12058,7 @@ var ConfigSetCommand = class extends SmCommand {
11936
12058
  announceLensSwitch(cwd, ansi) {
11937
12059
  const dbPath = resolveDbPath({ db: void 0, cwd });
11938
12060
  const okGlyph = ansi.green("\u2713");
11939
- if (!existsSync15(dbPath)) {
12061
+ if (!existsSync16(dbPath)) {
11940
12062
  this.printer.info(tx(CONFIG_TEXTS.lensSwitchedNoDb, { glyph: okGlyph }));
11941
12063
  return;
11942
12064
  }
@@ -11976,7 +12098,7 @@ var ConfigResetCommand = class extends SmCommand {
11976
12098
  const path = targetSettingsPath2(target, ctx.cwd);
11977
12099
  const ansi = this.ansiFor("stdout");
11978
12100
  const okGlyph = ansi.green("\u2713");
11979
- if (!existsSync15(path)) {
12101
+ if (!existsSync16(path)) {
11980
12102
  this.printer.data(
11981
12103
  tx(CONFIG_TEXTS.unsetNoOverride, {
11982
12104
  glyph: okGlyph,
@@ -12051,14 +12173,14 @@ var CONFIG_COMMANDS = [
12051
12173
  ];
12052
12174
 
12053
12175
  // cli/commands/conformance.ts
12054
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
12176
+ import { existsSync as existsSync19, readFileSync as readFileSync16 } from "fs";
12055
12177
  import { dirname as dirname12, resolve as resolve22 } from "path";
12056
12178
  import { fileURLToPath as fileURLToPath4 } from "url";
12057
12179
  import { Command as Command5, Option as Option5 } from "clipanion";
12058
12180
 
12059
12181
  // conformance/index.ts
12060
12182
  import { spawnSync as spawnSync2 } from "child_process";
12061
- import { cpSync, existsSync as existsSync16, mkdtempSync, readdirSync as readdirSync5, readFileSync as readFileSync14, rmSync, statSync as statSync3 } from "fs";
12183
+ import { cpSync, existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync5, readFileSync as readFileSync15, rmSync, statSync as statSync3 } from "fs";
12062
12184
  import { tmpdir } from "os";
12063
12185
  import { isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve20 } from "path";
12064
12186
 
@@ -12140,7 +12262,7 @@ function pickSafeEnv(source) {
12140
12262
  return out;
12141
12263
  }
12142
12264
  function runConformanceCase(options) {
12143
- const raw = readFileSync14(options.casePath, "utf8");
12265
+ const raw = readFileSync15(options.casePath, "utf8");
12144
12266
  const c = JSON.parse(raw);
12145
12267
  const fixturesRoot = options.fixturesRoot ?? join11(options.specRoot, "conformance", "fixtures");
12146
12268
  const safeId = c.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 32);
@@ -12255,7 +12377,7 @@ function evaluateAssertion(a, ctx) {
12255
12377
  return { ok: false, type: a.type, reason: formatErrorMessage(err) };
12256
12378
  }
12257
12379
  const abs = resolve20(ctx.scope, a.path);
12258
- return existsSync16(abs) ? { ok: true, type: a.type } : {
12380
+ return existsSync17(abs) ? { ok: true, type: a.type } : {
12259
12381
  ok: false,
12260
12382
  type: a.type,
12261
12383
  reason: tx(CONFORMANCE_RUNNER_TEXTS.fileNotFound, { path: a.path })
@@ -12270,15 +12392,15 @@ function evaluateAssertion(a, ctx) {
12270
12392
  }
12271
12393
  const fixturePath = join11(ctx.fixturesRoot, a.fixture);
12272
12394
  const targetPath = resolve20(ctx.scope, a.path);
12273
- if (!existsSync16(targetPath)) {
12395
+ if (!existsSync17(targetPath)) {
12274
12396
  return {
12275
12397
  ok: false,
12276
12398
  type: a.type,
12277
12399
  reason: tx(CONFORMANCE_RUNNER_TEXTS.targetNotFound, { path: a.path })
12278
12400
  };
12279
12401
  }
12280
- const needle = readFileSync14(fixturePath);
12281
- const haystack = readFileSync14(targetPath);
12402
+ const needle = readFileSync15(fixturePath);
12403
+ const haystack = readFileSync15(targetPath);
12282
12404
  return haystack.includes(needle) ? { ok: true, type: a.type } : {
12283
12405
  ok: false,
12284
12406
  type: a.type,
@@ -12451,7 +12573,7 @@ var CONFORMANCE_TEXTS = {
12451
12573
  };
12452
12574
 
12453
12575
  // cli/util/conformance-scopes.ts
12454
- import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
12576
+ import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
12455
12577
  import { dirname as dirname11, resolve as resolve21 } from "path";
12456
12578
  import { createRequire as createRequire6 } from "module";
12457
12579
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -12471,7 +12593,7 @@ function resolveCliWorkspaceRoot() {
12471
12593
  let cursor = here;
12472
12594
  for (let depth = 0; depth < 6; depth += 1) {
12473
12595
  const candidate = resolve21(cursor, "plugins");
12474
- if (existsSync17(candidate) && statSync4(candidate).isDirectory()) {
12596
+ if (existsSync18(candidate) && statSync4(candidate).isDirectory()) {
12475
12597
  return cursor;
12476
12598
  }
12477
12599
  const parent = dirname11(cursor);
@@ -12491,7 +12613,7 @@ function collectProviderScopes(specRoot) {
12491
12613
  return out;
12492
12614
  }
12493
12615
  const pluginsRoot = resolve21(workspaceRoot, "plugins");
12494
- if (!existsSync17(pluginsRoot)) return out;
12616
+ if (!existsSync18(pluginsRoot)) return out;
12495
12617
  for (const pluginEntry of readdirSync6(pluginsRoot)) {
12496
12618
  const pluginDir = resolve21(pluginsRoot, pluginEntry);
12497
12619
  if (!isDir(pluginDir)) continue;
@@ -12503,7 +12625,7 @@ function collectProviderScopes(specRoot) {
12503
12625
  }
12504
12626
  function isDir(path) {
12505
12627
  try {
12506
- return existsSync17(path) && statSync4(path).isDirectory();
12628
+ return existsSync18(path) && statSync4(path).isDirectory();
12507
12629
  } catch {
12508
12630
  return false;
12509
12631
  }
@@ -12513,10 +12635,10 @@ function collectPluginProviderScopes(providersRoot, specRoot, out) {
12513
12635
  const providerDir = resolve21(providersRoot, entry);
12514
12636
  if (!isDir(providerDir)) continue;
12515
12637
  const conformanceDir = resolve21(providerDir, "conformance");
12516
- if (!existsSync17(conformanceDir)) continue;
12638
+ if (!existsSync18(conformanceDir)) continue;
12517
12639
  const casesDir = resolve21(conformanceDir, "cases");
12518
12640
  const fixturesDir = resolve21(conformanceDir, "fixtures");
12519
- if (!existsSync17(casesDir) || !existsSync17(fixturesDir)) continue;
12641
+ if (!existsSync18(casesDir) || !existsSync18(fixturesDir)) continue;
12520
12642
  out.push({
12521
12643
  id: `provider:${entry}`,
12522
12644
  kind: "provider",
@@ -12554,7 +12676,7 @@ function selectConformanceScopes(scope) {
12554
12676
  return [match];
12555
12677
  }
12556
12678
  function listCaseFiles(scope) {
12557
- if (!existsSync17(scope.casesDir)) return [];
12679
+ if (!existsSync18(scope.casesDir)) return [];
12558
12680
  return readdirSync6(scope.casesDir).filter((entry) => entry.endsWith(".json")).sort().map((entry) => resolve21(scope.casesDir, entry));
12559
12681
  }
12560
12682
 
@@ -12573,7 +12695,7 @@ function resolveBinary() {
12573
12695
  let cursor = here;
12574
12696
  for (let depth = 0; depth < 6; depth += 1) {
12575
12697
  const candidate = resolve22(cursor, "bin", "sm.js");
12576
- if (existsSync18(candidate)) return candidate;
12698
+ if (existsSync19(candidate)) return candidate;
12577
12699
  const parent = dirname12(cursor);
12578
12700
  if (parent === cursor) break;
12579
12701
  cursor = parent;
@@ -12639,7 +12761,7 @@ var ConformanceRunCommand = class extends SmCommand {
12639
12761
  return ExitCode.Error;
12640
12762
  }
12641
12763
  const binary = resolveBinary();
12642
- if (!existsSync18(binary)) {
12764
+ if (!existsSync19(binary)) {
12643
12765
  if (this.json) {
12644
12766
  this.#emitJsonError(
12645
12767
  "internal",
@@ -12813,7 +12935,7 @@ function projectAssertionFailures(assertions) {
12813
12935
  }
12814
12936
  function readCaseId(casePath) {
12815
12937
  try {
12816
- const raw = readFileSync15(casePath, "utf8");
12938
+ const raw = readFileSync16(casePath, "utf8");
12817
12939
  const parsed = JSON.parse(raw);
12818
12940
  if (typeof parsed.id === "string") return parsed.id;
12819
12941
  } catch {
@@ -13068,18 +13190,18 @@ var DbRestoreCommand = class extends SmCommand {
13068
13190
  };
13069
13191
 
13070
13192
  // cli/commands/db/reset.ts
13071
- import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
13193
+ import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
13072
13194
  import { Command as Command8, Option as Option8 } from "clipanion";
13073
13195
 
13074
13196
  // core/sqlite/db-files.ts
13075
- import { existsSync as existsSync19 } from "fs";
13197
+ import { existsSync as existsSync20 } from "fs";
13076
13198
  import { rm as rm2 } from "fs/promises";
13077
13199
  var DB_FILE_SUFFIXES = ["", "-wal", "-shm"];
13078
13200
  async function removeDbFiles(dbPath) {
13079
13201
  if (dbPath === ":memory:") return;
13080
13202
  for (const suffix of DB_FILE_SUFFIXES) {
13081
13203
  const p = `${dbPath}${suffix}`;
13082
- if (existsSync19(p)) await rm2(p);
13204
+ if (existsSync20(p)) await rm2(p);
13083
13205
  }
13084
13206
  }
13085
13207
 
@@ -13165,7 +13287,7 @@ var DbResetCommand = class extends SmCommand {
13165
13287
  return ExitCode.Error;
13166
13288
  }
13167
13289
  }
13168
- const db = new DatabaseSync5(path);
13290
+ const db = new DatabaseSync6(path);
13169
13291
  try {
13170
13292
  const rows = db.prepare(
13171
13293
  "SELECT name FROM sqlite_master WHERE type='table' AND (name LIKE 'scan\\_%' ESCAPE '\\'" + (this.state ? " OR name LIKE 'state\\_%' ESCAPE '\\'" : "") + ")"
@@ -13308,7 +13430,7 @@ var DbBrowserCommand = class extends SmCommand {
13308
13430
  };
13309
13431
 
13310
13432
  // cli/commands/db/dump.ts
13311
- import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
13433
+ import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
13312
13434
  import { Command as Command11, Option as Option10 } from "clipanion";
13313
13435
  var DbDumpCommand = class extends SmCommand {
13314
13436
  static paths = [["db", "dump"]];
@@ -13350,7 +13472,7 @@ var DbDumpCommand = class extends SmCommand {
13350
13472
  }
13351
13473
  };
13352
13474
  function dumpDatabaseToStream(dbPath, out, tables) {
13353
- const db = new DatabaseSync6(dbPath, { readOnly: true });
13475
+ const db = new DatabaseSync7(dbPath, { readOnly: true });
13354
13476
  try {
13355
13477
  out.write("PRAGMA foreign_keys=OFF;\n");
13356
13478
  out.write("BEGIN TRANSACTION;\n");
@@ -14198,7 +14320,7 @@ var GraphCommand = class extends SmCommand {
14198
14320
  };
14199
14321
 
14200
14322
  // cli/commands/help.ts
14201
- import { readFileSync as readFileSync16 } from "fs";
14323
+ import { readFileSync as readFileSync17 } from "fs";
14202
14324
  import { createRequire as createRequire7 } from "module";
14203
14325
  import { resolve as resolve26 } from "path";
14204
14326
  import { Command as Command15, Option as Option14 } from "clipanion";
@@ -14499,7 +14621,7 @@ function resolveSpecVersion() {
14499
14621
  const req = createRequire7(import.meta.url);
14500
14622
  const indexPath = req.resolve("@skill-map/spec/index.json");
14501
14623
  const pkgPath = resolve26(indexPath, "..", "package.json");
14502
- const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
14624
+ const pkg = JSON.parse(readFileSync17(pkgPath, "utf8"));
14503
14625
  return pkg.version;
14504
14626
  } catch {
14505
14627
  return "unknown";
@@ -14984,10 +15106,9 @@ import { join as join17 } from "path";
14984
15106
  import { Command as Command17, Option as Option16 } from "clipanion";
14985
15107
 
14986
15108
  // kernel/orchestrator/index.ts
14987
- import { existsSync as existsSync22, statSync as statSync6 } from "fs";
15109
+ import { existsSync as existsSync23, statSync as statSync6 } from "fs";
14988
15110
  import { isAbsolute as isAbsolute7, resolve as resolve28 } from "path";
14989
15111
  import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
14990
- import cl100k_base from "js-tiktoken/ranks/cl100k_base";
14991
15112
 
14992
15113
  // kernel/i18n/orchestrator.texts.ts
14993
15114
  var ORCHESTRATOR_TEXTS = {
@@ -16031,7 +16152,7 @@ function computeDriftStatus(args2) {
16031
16152
  }
16032
16153
 
16033
16154
  // kernel/sidecar/discover-orphans.ts
16034
- import { existsSync as existsSync20, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
16155
+ import { existsSync as existsSync21, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
16035
16156
  import { join as join13, relative as relative4, sep as sep4 } from "path";
16036
16157
  function discoverOrphanSidecars(roots, shouldSkip) {
16037
16158
  const out = [];
@@ -16059,7 +16180,7 @@ function walk2(root, current, shouldSkip, out) {
16059
16180
  if (!entry.isFile()) continue;
16060
16181
  if (!entry.name.endsWith(".sm")) continue;
16061
16182
  const expectedMd = `${full.slice(0, -".sm".length)}.md`;
16062
- if (existsSync20(expectedMd) && safeIsFile(expectedMd)) continue;
16183
+ if (existsSync21(expectedMd) && safeIsFile(expectedMd)) continue;
16063
16184
  out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
16064
16185
  }
16065
16186
  }
@@ -16072,8 +16193,8 @@ function safeIsFile(path) {
16072
16193
  }
16073
16194
 
16074
16195
  // kernel/orchestrator/node-build.ts
16075
- import { createHash } from "crypto";
16076
- import { existsSync as existsSync21 } from "fs";
16196
+ import { createHash as createHash2 } from "crypto";
16197
+ import { existsSync as existsSync22 } from "fs";
16077
16198
  import { isAbsolute as isAbsolute6, resolve as resolvePath } from "path";
16078
16199
  import "js-tiktoken/lite";
16079
16200
  import yaml4 from "js-yaml";
@@ -16160,7 +16281,7 @@ function countTokens(encoder, frontmatterRaw, body) {
16160
16281
  return { frontmatter, body: bodyTokens, total: frontmatter + bodyTokens };
16161
16282
  }
16162
16283
  function sha256(input) {
16163
- return createHash("sha256").update(input, "utf8").digest("hex");
16284
+ return createHash2("sha256").update(input, "utf8").digest("hex");
16164
16285
  }
16165
16286
  function canonicalFrontmatter(parsed, raw) {
16166
16287
  const hasParsedKeys = Object.keys(parsed).length > 0;
@@ -16237,11 +16358,11 @@ function resolveSidecarOverlay(relativePath2, nodePathForIssue, roots, liveBodyH
16237
16358
  }
16238
16359
  function resolveAbsoluteMdPath(relativePath2, roots) {
16239
16360
  if (isAbsolute6(relativePath2)) {
16240
- return existsSync21(relativePath2) ? relativePath2 : null;
16361
+ return existsSync22(relativePath2) ? relativePath2 : null;
16241
16362
  }
16242
16363
  for (const root of roots) {
16243
16364
  const candidate = resolvePath(root, relativePath2);
16244
- if (existsSync21(candidate)) return candidate;
16365
+ if (existsSync22(candidate)) return candidate;
16245
16366
  }
16246
16367
  return null;
16247
16368
  }
@@ -16394,7 +16515,11 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
16394
16515
  }
16395
16516
  claimedPaths.add(raw.path);
16396
16517
  const priorNode = wctx.priorNodesByPath.get(raw.path);
16397
- const nodeHashCacheEligible = wctx.opts.enableCache && wctx.opts.prior !== null && priorNode !== void 0 && priorNode.bodyHash === bodyHash && priorNode.frontmatterHash === frontmatterHash;
16518
+ const nodeHashCacheEligible = wctx.opts.enableCache && // Tokenizer-change invalidation: when the resolved encoder differs
16519
+ // from the one that produced the prior snapshot's counts, no node is
16520
+ // cache-eligible, every node rebuilds so `buildNode` re-tokenizes
16521
+ // with the current encoder. See `tokenizerChanged` on the options.
16522
+ !wctx.opts.tokenizerChanged && wctx.opts.prior !== null && priorNode !== void 0 && priorNode.bodyHash === bodyHash && priorNode.frontmatterHash === frontmatterHash;
16398
16523
  const sidecarResolution = resolveSidecarOverlay(
16399
16524
  raw.path,
16400
16525
  raw.path,
@@ -16596,6 +16721,16 @@ function resolveSpecVersionSafe() {
16596
16721
  return "unknown";
16597
16722
  }
16598
16723
  }
16724
+ var DEFAULT_TOKENIZER = "cl100k_base";
16725
+ function resolveTokenizerName(name) {
16726
+ return name === "o200k_base" ? "o200k_base" : DEFAULT_TOKENIZER;
16727
+ }
16728
+ async function loadTokenizerRanks(name) {
16729
+ if (name === "o200k_base") {
16730
+ return (await import("js-tiktoken/ranks/o200k_base")).default;
16731
+ }
16732
+ return (await import("js-tiktoken/ranks/cl100k_base")).default;
16733
+ }
16599
16734
  async function runScanWithRenames(_kernel, options) {
16600
16735
  return runScanInternal(_kernel, options);
16601
16736
  }
@@ -16605,7 +16740,7 @@ async function runScan(_kernel, options) {
16605
16740
  }
16606
16741
  async function runScanInternal(_kernel, options) {
16607
16742
  validateRoots(options.roots);
16608
- const setup = buildScanSetup(options);
16743
+ const setup = await buildScanSetup(options);
16609
16744
  const { emitter, exts, hookDispatcher, encoder, prior, start } = setup;
16610
16745
  const scanStartedEvent = makeEvent("scan.started", { roots: options.roots });
16611
16746
  emitter.emit(scanStartedEvent);
@@ -16615,6 +16750,7 @@ async function runScanInternal(_kernel, options) {
16615
16750
  options.roots,
16616
16751
  exts.providers
16617
16752
  );
16753
+ const tokenizerChanged = encoder !== null && prior !== null && prior.tokenizer !== setup.tokenizer;
16618
16754
  const walked = await walkAndExtract({
16619
16755
  providers: exts.providers,
16620
16756
  extractors: exts.extractors,
@@ -16624,6 +16760,7 @@ async function runScanInternal(_kernel, options) {
16624
16760
  encoder,
16625
16761
  strict: setup.strict,
16626
16762
  enableCache: setup.enableCache,
16763
+ tokenizerChanged,
16627
16764
  prior,
16628
16765
  priorIndex: setup.priorIndex,
16629
16766
  priorExtractorRuns: setup.priorExtractorRuns,
@@ -16739,13 +16876,14 @@ function buildReservedNodePaths(nodes, kindRegistry, reservedNamesByProviderKind
16739
16876
  function hasEntries(set) {
16740
16877
  return set !== void 0 && set.size > 0;
16741
16878
  }
16742
- function buildScanSetup(options) {
16879
+ async function buildScanSetup(options) {
16743
16880
  const start = Date.now();
16744
16881
  const emitter = options.emitter ?? new InMemoryProgressEmitter();
16745
16882
  const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
16746
16883
  const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
16747
16884
  const tokenize = options.tokenize !== false;
16748
- const encoder = tokenize ? new Tiktoken2(cl100k_base) : null;
16885
+ const tokenizer = resolveTokenizerName(options.tokenizer);
16886
+ const encoder = tokenize ? new Tiktoken2(await loadTokenizerRanks(tokenizer)) : null;
16749
16887
  const prior = options.priorSnapshot ?? null;
16750
16888
  const priorIndex = indexPriorSnapshot(prior);
16751
16889
  const providerFrontmatter = buildProviderFrontmatterValidator(exts.providers);
@@ -16756,6 +16894,7 @@ function buildScanSetup(options) {
16756
16894
  exts,
16757
16895
  hookDispatcher,
16758
16896
  encoder,
16897
+ tokenizer,
16759
16898
  prior,
16760
16899
  priorIndex,
16761
16900
  priorExtractorRuns: options.priorExtractorRuns,
@@ -16806,6 +16945,7 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
16806
16945
  roots: options.roots,
16807
16946
  providers: setup.exts.providers.map((a) => a.id),
16808
16947
  scannedBy: SCANNED_BY,
16948
+ tokenizer: setup.tokenizer,
16809
16949
  recommendedNodeLimit: walked.recommendedNodeLimit,
16810
16950
  overrideMaxNodes: walked.overrideMaxNodes,
16811
16951
  oversizedFiles: walked.oversizedFiles,
@@ -16826,7 +16966,7 @@ function validateRoots(roots) {
16826
16966
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
16827
16967
  }
16828
16968
  for (const root of roots) {
16829
- if (!existsSync22(root) || !statSync6(root).isDirectory()) {
16969
+ if (!existsSync23(root) || !statSync6(root).isDirectory()) {
16830
16970
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
16831
16971
  }
16832
16972
  }
@@ -16835,7 +16975,7 @@ function resolveActiveProviderOption(optionValue, roots, providers) {
16835
16975
  if (optionValue !== void 0) return optionValue;
16836
16976
  for (const root of roots) {
16837
16977
  const absRoot = isAbsolute7(root) ? root : resolve28(root);
16838
- if (!existsSync22(absRoot)) continue;
16978
+ if (!existsSync23(absRoot)) continue;
16839
16979
  const detected = resolveActiveProvider(absRoot, providers).resolved;
16840
16980
  if (detected !== null) return detected;
16841
16981
  }
@@ -17475,45 +17615,61 @@ async function promptForLens(detected, stdin, stderr, warnGlyph) {
17475
17615
  }
17476
17616
 
17477
17617
  // core/sqlite/db-drift-reset.ts
17478
- import { existsSync as existsSync23 } from "fs";
17618
+ import { existsSync as existsSync24 } from "fs";
17479
17619
  import { createInterface as createInterface4 } from "readline";
17480
- import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
17620
+ import { DatabaseSync as DatabaseSync8 } from "node:sqlite";
17481
17621
 
17482
17622
  // core/sqlite/i18n/db-drift.texts.ts
17483
17623
  var DB_DRIFT_TEXTS = {
17484
17624
  // Interactive confirm (TTY `sm scan`, no `--yes`). The block is
17485
17625
  // written to stderr, then the question line drives `readline`.
17486
- driftPrompt: "{{glyph}} The local cache was built by skill-map {{dbVersion}} and you are on {{currentVersion}}.\n {{hint}}\n",
17487
- driftPromptHint: "Pre-1.0 the DB is a derived cache (your .sm sidecars hold the real data); it cannot be carried across a version change and has to be rebuilt.",
17626
+ // `{{reason}}` is one of the `driftReason*` strings below so the
17627
+ // operator sees WHY the cache is being rebuilt (version skew vs an
17628
+ // inline schema change the version did not bump).
17629
+ driftPrompt: "{{glyph}} The local cache was built by skill-map {{dbVersion}} and you are on {{currentVersion}} ({{reason}}).\n {{hint}}\n",
17630
+ driftPromptHint: "Pre-1.0 the DB is a derived cache (your .sm sidecars hold the real data); it cannot be carried across a schema change and has to be rebuilt.",
17488
17631
  driftPromptQuestion: "Delete the local cache and rebuild it on this scan? [y/N] ",
17489
17632
  // Receipt after the rebuild (printed by the scan / refresh path).
17490
- driftReset: "{{glyph}} Local cache rebuilt: it was written by skill-map {{dbVersion}}, you are on {{currentVersion}}.\n {{hint}}\n",
17633
+ driftReset: "{{glyph}} Local cache rebuilt ({{reason}}): it was written by skill-map {{dbVersion}}, you are on {{currentVersion}}.\n {{hint}}\n",
17491
17634
  driftResetHint: "The DB was deleted and is being regenerated by this scan; .sm sidecars were not touched.",
17492
17635
  // Abort headline when the operator declines (wrapped by the caller's
17493
17636
  // `sm scan: {message}` shell, so it carries no glyph / verb prefix).
17494
- driftAborted: "cache rebuild declined: the {{dbVersion}} cache cannot be reused on {{currentVersion}}. {{hint}}",
17495
- driftAbortedHint: "Re-run with --yes, or run `sm db reset --hard` then `sm scan`."
17637
+ driftAborted: "cache rebuild declined: the {{dbVersion}} cache cannot be reused on {{currentVersion}} ({{reason}}). {{hint}}",
17638
+ driftAbortedHint: "Re-run with --yes, or run `sm db reset --hard` then `sm scan`.",
17639
+ // Drift reason fragments, interpolated as `{{reason}}` above. Version
17640
+ // skew = the recorded scanned_by_version differs at major.minor.
17641
+ // Schema fingerprint = an inline migration edit (no version bump,
17642
+ // greenfield posture) changed the DDL.
17643
+ driftReasonVersion: "version skew",
17644
+ driftReasonSchema: "schema change in this version"
17496
17645
  };
17497
17646
 
17498
17647
  // core/sqlite/db-drift-reset.ts
17499
17648
  async function maybeResetOnDrift(dbPath, policy) {
17500
- const dbVersion = readScannedByVersion(dbPath);
17501
- if (dbVersion === null) return { kind: "no-drift" };
17502
- const skew = classifyVersionSkew(dbVersion, policy.currentVersion);
17503
- if (skew.kind === "ok" || skew.kind === "no-meta") return { kind: "no-drift" };
17504
- const confirmed = await confirmDriftReset(dbVersion, policy);
17649
+ const reason = detectDriftReason(dbPath, policy.currentVersion);
17650
+ if (reason === null) return { kind: "no-drift" };
17651
+ const dbVersion = readScannedByVersion(dbPath) ?? "unknown";
17652
+ const confirmed = await confirmDriftReset(dbVersion, reason, policy);
17505
17653
  if (!confirmed) {
17506
- return { kind: "aborted", dbVersion, currentVersion: policy.currentVersion };
17654
+ return { kind: "aborted", dbVersion, currentVersion: policy.currentVersion, reason };
17507
17655
  }
17508
17656
  await removeDbFiles(dbPath);
17509
- renderResetReceipt(dbVersion, policy);
17510
- return { kind: "reset", dbVersion, currentVersion: policy.currentVersion };
17657
+ renderResetReceipt(dbVersion, reason, policy);
17658
+ return { kind: "reset", dbVersion, currentVersion: policy.currentVersion, reason };
17659
+ }
17660
+ function detectDriftReason(dbPath, currentVersion) {
17661
+ const dbVersion = readScannedByVersion(dbPath);
17662
+ if (dbVersion !== null) {
17663
+ const skew = classifyVersionSkew(dbVersion, currentVersion);
17664
+ if (skew.kind !== "ok" && skew.kind !== "no-meta") return "version";
17665
+ }
17666
+ return classifyFingerprint(dbPath).kind === "drift" ? "schema" : null;
17511
17667
  }
17512
17668
  function readScannedByVersion(dbPath) {
17513
- if (dbPath === ":memory:" || !existsSync23(dbPath)) return null;
17669
+ if (dbPath === ":memory:" || !existsSync24(dbPath)) return null;
17514
17670
  let raw = null;
17515
17671
  try {
17516
- raw = new DatabaseSync7(dbPath, { readOnly: true });
17672
+ raw = new DatabaseSync8(dbPath, { readOnly: true });
17517
17673
  const row = raw.prepare("SELECT scanned_by_version AS v FROM scan_meta LIMIT 1").get();
17518
17674
  const v = row?.v;
17519
17675
  return typeof v === "string" && v.length > 0 ? v : null;
@@ -17523,16 +17679,19 @@ function readScannedByVersion(dbPath) {
17523
17679
  raw?.close();
17524
17680
  }
17525
17681
  }
17526
- async function confirmDriftReset(dbVersion, policy) {
17682
+ async function confirmDriftReset(dbVersion, reason, policy) {
17527
17683
  if (!shouldPromptForReset(policy)) return true;
17528
- return askDriftReset(dbVersion, policy);
17684
+ return askDriftReset(dbVersion, reason, policy);
17685
+ }
17686
+ function reasonText(reason) {
17687
+ return reason === "version" ? DB_DRIFT_TEXTS.driftReasonVersion : DB_DRIFT_TEXTS.driftReasonSchema;
17529
17688
  }
17530
17689
  function shouldPromptForReset(policy) {
17531
17690
  if (policy.assumeYes) return false;
17532
17691
  if (!policy.stdin || !policy.stderr) return false;
17533
17692
  return policy.stdin.isTTY === true;
17534
17693
  }
17535
- async function askDriftReset(dbVersion, policy) {
17694
+ async function askDriftReset(dbVersion, reason, policy) {
17536
17695
  const warnGlyph = policy.style?.warnGlyph ?? "\u26A0";
17537
17696
  const dim = policy.style?.dim ?? ((s) => s);
17538
17697
  policy.stderr.write(
@@ -17540,6 +17699,7 @@ async function askDriftReset(dbVersion, policy) {
17540
17699
  glyph: warnGlyph,
17541
17700
  dbVersion,
17542
17701
  currentVersion: policy.currentVersion,
17702
+ reason: reasonText(reason),
17543
17703
  hint: dim(DB_DRIFT_TEXTS.driftPromptHint)
17544
17704
  })
17545
17705
  );
@@ -17553,7 +17713,7 @@ async function askDriftReset(dbVersion, policy) {
17553
17713
  rl.close();
17554
17714
  }
17555
17715
  }
17556
- function renderResetReceipt(dbVersion, policy) {
17716
+ function renderResetReceipt(dbVersion, reason, policy) {
17557
17717
  if (!policy.printer) return;
17558
17718
  const warnGlyph = policy.style?.warnGlyph ?? "\u26A0";
17559
17719
  const dim = policy.style?.dim ?? ((s) => s);
@@ -17562,6 +17722,7 @@ function renderResetReceipt(dbVersion, policy) {
17562
17722
  glyph: warnGlyph,
17563
17723
  dbVersion,
17564
17724
  currentVersion: policy.currentVersion,
17725
+ reason: reasonText(reason),
17565
17726
  hint: dim(DB_DRIFT_TEXTS.driftResetHint)
17566
17727
  })
17567
17728
  );
@@ -17605,7 +17766,8 @@ async function runScanForCommand(opts) {
17605
17766
  ctx.cwd,
17606
17767
  activeProvider,
17607
17768
  cfg.scan.maxNodes,
17608
- cfg.scan.maxFileSizeBytes
17769
+ cfg.scan.maxFileSizeBytes,
17770
+ cfg.tokenizer
17609
17771
  );
17610
17772
  const willPersist = !opts.noBuiltIns && !opts.dryRun;
17611
17773
  const scanned = await (willPersist ? runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanWith, extensions) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith));
@@ -17719,7 +17881,7 @@ function makePriorLoader(noBuiltIns, strict) {
17719
17881
  return loaded;
17720
17882
  };
17721
17883
  }
17722
- function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd, activeProvider, recommendedNodeLimit, maxFileSizeBytes) {
17884
+ function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd, activeProvider, recommendedNodeLimit, maxFileSizeBytes, tokenizer) {
17723
17885
  return async (prior, priorExtractorRuns, orphanJobFiles) => {
17724
17886
  if (opts.changed && prior === null) {
17725
17887
  opts.stderr.write(SCAN_RUNNER_TEXTS.changedNoPriorWarning);
@@ -17736,6 +17898,7 @@ function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, exte
17736
17898
  activeProvider,
17737
17899
  recommendedNodeLimit,
17738
17900
  maxFileSizeBytes,
17901
+ tokenizer,
17739
17902
  ...priorExtractorRuns ? { priorExtractorRuns } : {},
17740
17903
  ...orphanJobFiles ? { orphanJobFiles } : {}
17741
17904
  });
@@ -17747,6 +17910,7 @@ function buildRunScanOptions(args2) {
17747
17910
  const runOptions = {
17748
17911
  roots: args2.effectiveRoots.slice(),
17749
17912
  tokenize: !opts.noTokens,
17913
+ tokenizer: args2.tokenizer,
17750
17914
  ignoreFilter: args2.ignoreFilter,
17751
17915
  strict: args2.strict,
17752
17916
  emitter: buildRunScanEmitter(opts),
@@ -17792,6 +17956,7 @@ async function rebuildOnDrift(opts, dbPath) {
17792
17956
  message: tx(DB_DRIFT_TEXTS.driftAborted, {
17793
17957
  dbVersion: drift.dbVersion,
17794
17958
  currentVersion: drift.currentVersion,
17959
+ reason: drift.reason === "version" ? DB_DRIFT_TEXTS.driftReasonVersion : DB_DRIFT_TEXTS.driftReasonSchema,
17795
17960
  hint: dim(DB_DRIFT_TEXTS.driftAbortedHint)
17796
17961
  })
17797
17962
  };
@@ -18961,7 +19126,11 @@ var ListCommand = class extends SmCommand {
18961
19126
  const exit = requireDbOrExit(dbPath, this.context.stderr);
18962
19127
  if (exit !== null) return exit;
18963
19128
  return withSqlite(
18964
- { databasePath: dbPath, autoBackup: false },
19129
+ {
19130
+ databasePath: dbPath,
19131
+ autoBackup: false,
19132
+ versionCheck: buildReadVersionCheck(this.printer, stderrAnsi)
19133
+ },
18965
19134
  (adapter) => this.#runQuery(adapter, flags)
18966
19135
  );
18967
19136
  }
@@ -21121,7 +21290,7 @@ function resolveBareToggle(id, catalogue) {
21121
21290
  }
21122
21291
 
21123
21292
  // cli/commands/plugins/create.ts
21124
- import { existsSync as existsSync24, mkdirSync as mkdirSync5, writeFileSync } from "fs";
21293
+ import { existsSync as existsSync25, mkdirSync as mkdirSync5, writeFileSync } from "fs";
21125
21294
  import { join as join18, resolve as resolve33 } from "path";
21126
21295
  import { Command as Command26, Option as Option25 } from "clipanion";
21127
21296
  var PluginsCreateCommand = class extends SmCommand {
@@ -21150,7 +21319,7 @@ var PluginsCreateCommand = class extends SmCommand {
21150
21319
  const ctx = defaultRuntimeContext();
21151
21320
  const baseDir = defaultProjectPluginsDir(ctx);
21152
21321
  const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
21153
- if (existsSync24(targetDir) && !this.force) {
21322
+ if (existsSync25(targetDir) && !this.force) {
21154
21323
  this.printer.error(
21155
21324
  tx(PLUGINS_TEXTS.createRefuseOverwrite, {
21156
21325
  glyph: errGlyph,
@@ -22035,6 +22204,7 @@ function createWatcherRuntime(opts) {
22035
22204
  const runOptions = {
22036
22205
  roots: opts.roots,
22037
22206
  tokenize,
22207
+ tokenizer: cfg.tokenizer,
22038
22208
  ignoreFilter,
22039
22209
  strict,
22040
22210
  emitter,
@@ -23185,7 +23355,7 @@ function renderDeltaIssues(issues) {
23185
23355
 
23186
23356
  // cli/commands/serve.ts
23187
23357
  import { spawn as spawn2 } from "child_process";
23188
- import { existsSync as existsSync30 } from "fs";
23358
+ import { existsSync as existsSync31 } from "fs";
23189
23359
  import { Command as Command33, Option as Option31 } from "clipanion";
23190
23360
 
23191
23361
  // kernel/util/dev-mode.ts
@@ -23982,7 +24152,7 @@ function contentTypeFor(format) {
23982
24152
  }
23983
24153
 
23984
24154
  // server/health.ts
23985
- import { existsSync as existsSync25 } from "fs";
24155
+ import { existsSync as existsSync26 } from "fs";
23986
24156
  var FALLBACK_SCHEMA_VERSION = "1";
23987
24157
  function buildHealth(deps) {
23988
24158
  const dev = isDevBuild();
@@ -23991,7 +24161,7 @@ function buildHealth(deps) {
23991
24161
  schemaVersion: FALLBACK_SCHEMA_VERSION,
23992
24162
  specVersion: deps.specVersion,
23993
24163
  implVersion: VERSION,
23994
- db: existsSync25(deps.dbPath) ? "present" : "missing",
24164
+ db: existsSync26(deps.dbPath) ? "present" : "missing",
23995
24165
  cwd: deps.cwd,
23996
24166
  dbPath: deps.dbPath,
23997
24167
  // Only emit when truthy so a published install keeps the wire
@@ -24900,15 +25070,15 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
24900
25070
  import { HTTPException as HTTPException11 } from "hono/http-exception";
24901
25071
 
24902
25072
  // server/util/skillmapignore-io.ts
24903
- import { existsSync as existsSync26, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
25073
+ import { existsSync as existsSync27, readFileSync as readFileSync18, writeFileSync as writeFileSync2 } from "fs";
24904
25074
  import { resolve as resolve35 } from "path";
24905
25075
  var IGNORE_FILENAME2 = ".skillmapignore";
24906
25076
  function readPatterns(cwd) {
24907
25077
  const path = resolve35(cwd, IGNORE_FILENAME2);
24908
- if (!existsSync26(path)) return [];
25078
+ if (!existsSync27(path)) return [];
24909
25079
  let raw;
24910
25080
  try {
24911
- raw = readFileSync17(path, "utf8");
25081
+ raw = readFileSync18(path, "utf8");
24912
25082
  } catch {
24913
25083
  return [];
24914
25084
  }
@@ -24916,13 +25086,13 @@ function readPatterns(cwd) {
24916
25086
  }
24917
25087
  function writePatterns(cwd, nextPatterns) {
24918
25088
  const path = resolve35(cwd, IGNORE_FILENAME2);
24919
- const prior = existsSync26(path) ? safeRead(path) : "";
25089
+ const prior = existsSync27(path) ? safeRead(path) : "";
24920
25090
  const content = buildContent(prior, nextPatterns);
24921
25091
  writeFileSync2(path, content, "utf8");
24922
25092
  }
24923
25093
  function safeRead(path) {
24924
25094
  try {
24925
- return readFileSync17(path, "utf8");
25095
+ return readFileSync18(path, "utf8");
24926
25096
  } catch {
24927
25097
  return "";
24928
25098
  }
@@ -25266,7 +25436,7 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
25266
25436
  });
25267
25437
 
25268
25438
  // server/routes/active-provider.ts
25269
- import { existsSync as existsSync27 } from "fs";
25439
+ import { existsSync as existsSync28 } from "fs";
25270
25440
  import { HTTPException as HTTPException13 } from "hono/http-exception";
25271
25441
  function registerActiveProviderRoute(app, deps) {
25272
25442
  app.get("/api/active-provider", (c) => {
@@ -25299,7 +25469,7 @@ function applyLensSwitch(deps, newValue) {
25299
25469
  });
25300
25470
  }
25301
25471
  const dbPath = resolveDbPath({ db: void 0, cwd });
25302
- if (!existsSync27(dbPath)) return { dropped: null };
25472
+ if (!existsSync28(dbPath)) return { dropped: null };
25303
25473
  const dropResult = dropScanZone(dbPath);
25304
25474
  return {
25305
25475
  dropped: {
@@ -25566,7 +25736,16 @@ async function buildBffResolverOverride(deps) {
25566
25736
  }
25567
25737
  async function loadPersistedScan(deps) {
25568
25738
  const opened = await tryWithSqlite(
25569
- { databasePath: deps.options.dbPath, autoBackup: false },
25739
+ {
25740
+ databasePath: deps.options.dbPath,
25741
+ autoBackup: false,
25742
+ // Read-side drift advisory (version skew + schema fingerprint).
25743
+ // The BFF has no TTY, warnings go to the server log; a newer /
25744
+ // different-major DB throws `DbVersionMismatchError`, which the
25745
+ // global `app.onError` maps to a 500 so the SPA surfaces it
25746
+ // rather than crashing on a cryptic missing-column read.
25747
+ versionCheck: { currentVersion: VERSION, printer: bffVersionCheckPrinter }
25748
+ },
25570
25749
  async (adapter) => {
25571
25750
  const [loaded, favSet] = await Promise.all([
25572
25751
  adapter.scans.load(),
@@ -25661,6 +25840,13 @@ var bffScanRunnerPrinter = {
25661
25840
  warn: (text) => log.warn(sanitizeForTerminal(text.trimEnd())),
25662
25841
  error: (text) => log.warn(sanitizeForTerminal(text.trimEnd()))
25663
25842
  };
25843
+ var bffVersionCheckPrinter = {
25844
+ data: () => {
25845
+ },
25846
+ info: (text) => log.warn(sanitizeForTerminal(text.trimEnd())),
25847
+ warn: (text) => log.warn(sanitizeForTerminal(text.trimEnd())),
25848
+ error: (text) => log.warn(sanitizeForTerminal(text.trimEnd()))
25849
+ };
25664
25850
  function emptyScanResult() {
25665
25851
  return {
25666
25852
  schemaVersion: 1,
@@ -25847,7 +26033,7 @@ function registerUpdateStatusRoute(app, deps) {
25847
26033
  }
25848
26034
 
25849
26035
  // server/static.ts
25850
- import { existsSync as existsSync28 } from "fs";
26036
+ import { existsSync as existsSync29 } from "fs";
25851
26037
  import { readFile as readFile6 } from "fs/promises";
25852
26038
  import { extname, join as join19 } from "path";
25853
26039
  import { serveStatic } from "@hono/node-server/serve-static";
@@ -25902,7 +26088,7 @@ function createSpaFallback(opts) {
25902
26088
  if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
25903
26089
  if (opts.uiDist === null) return htmlResponse(c, placeholder);
25904
26090
  const indexPath = join19(opts.uiDist, INDEX_HTML);
25905
- if (!existsSync28(indexPath)) return htmlResponse(c, placeholder);
26091
+ if (!existsSync29(indexPath)) return htmlResponse(c, placeholder);
25906
26092
  return fileResponse(c, indexPath);
25907
26093
  };
25908
26094
  }
@@ -26512,7 +26698,7 @@ function validateNoUi(noUi, uiDist) {
26512
26698
  }
26513
26699
 
26514
26700
  // server/paths.ts
26515
- import { existsSync as existsSync29, statSync as statSync10 } from "fs";
26701
+ import { existsSync as existsSync30, statSync as statSync10 } from "fs";
26516
26702
  import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
26517
26703
  import { fileURLToPath as fileURLToPath6 } from "url";
26518
26704
  var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
@@ -26527,10 +26713,10 @@ function resolveExplicitUiDist(ctx, raw) {
26527
26713
  return isAbsolute11(raw) ? raw : resolve37(ctx.cwd, raw);
26528
26714
  }
26529
26715
  function isUiBundleDir(path) {
26530
- if (!existsSync29(path)) return false;
26716
+ if (!existsSync30(path)) return false;
26531
26717
  try {
26532
26718
  if (!statSync10(path).isDirectory()) return false;
26533
- return existsSync29(join20(path, INDEX_HTML2));
26719
+ return existsSync30(join20(path, INDEX_HTML2));
26534
26720
  } catch {
26535
26721
  return false;
26536
26722
  }
@@ -26824,7 +27010,14 @@ var SERVE_TEXTS = {
26824
27010
  // Shutdown trace, printed once the listener has closed. Informational
26825
27011
  // (`ℹ` cyan) per §3.1: no failure, no action; just a marker that the
26826
27012
  // long-running daemon has wound down cleanly.
26827
- shutdown: "{{glyph}} sm serve: shutdown complete.\n"
27013
+ shutdown: "{{glyph}} sm serve: shutdown complete.\n",
27014
+ /**
27015
+ * §3.1b error block when the operator declines the pre-boot
27016
+ * schema-drift rebuild (TTY, no `--yes`). The server never starts;
27017
+ * the cache is left untouched. `{{reason}}` names the drift axis
27018
+ * (version skew vs an inline schema change).
27019
+ */
27020
+ driftDeclined: "{{glyph}} sm serve: cache rebuild declined; the {{dbVersion}} cache cannot be reused on {{currentVersion}} ({{reason}}).\n {{hint}}\n"
26828
27021
  };
26829
27022
 
26830
27023
  // cli/util/serve-banner.ts
@@ -27051,6 +27244,9 @@ var ServeCommand = class extends SmCommand {
27051
27244
  noWatcher = Option31.Boolean("--no-watcher", false, {
27052
27245
  description: "Disable the chokidar-fed scan-and-broadcast loop. Use only for CI / read-only deployments."
27053
27246
  });
27247
+ yes = Option31.Boolean("--yes", false, {
27248
+ description: "Skip the interactive prompt and rebuild the local cache when the on-disk DB has drifted (version skew or an inline schema change). Non-TTY invocations rebuild without asking regardless of this flag."
27249
+ });
27054
27250
  // `--watcher-debounce-ms` is undocumented sugar for advanced users
27055
27251
  // who want to tighten / relax the watcher's batching window without
27056
27252
  // editing settings.json. Hidden flag, the Usage block omits it.
@@ -27085,7 +27281,7 @@ var ServeCommand = class extends SmCommand {
27085
27281
  return ExitCode.Error;
27086
27282
  }
27087
27283
  const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
27088
- if (this.db !== void 0 && !existsSync30(dbPath)) {
27284
+ if (this.db !== void 0 && !existsSync31(dbPath)) {
27089
27285
  this.printer.info(
27090
27286
  tx(SERVE_TEXTS.dbNotFound, {
27091
27287
  glyph: errGlyph,
@@ -27167,6 +27363,8 @@ var ServeCommand = class extends SmCommand {
27167
27363
  this.printer.info(formatValidationError(validation.error, stderrAnsi));
27168
27364
  return ExitCode.Error;
27169
27365
  }
27366
+ const driftAbort = await this.#rebuildOnDrift(dbPath, stderrAnsi, warnGlyph);
27367
+ if (driftAbort !== null) return driftAbort;
27170
27368
  await initSentryBff(VERSION);
27171
27369
  let handle;
27172
27370
  try {
@@ -27219,6 +27417,35 @@ var ServeCommand = class extends SmCommand {
27219
27417
  this.printer.info(tx(SERVE_TEXTS.shutdown, { glyph: infoGlyph }));
27220
27418
  return ExitCode.Ok;
27221
27419
  }
27420
+ /**
27421
+ * Pre-1.0 schema-drift rebuild for `sm serve`, run before boot. Reuses
27422
+ * the shared `maybeResetOnDrift` (same prompt / `--yes` / non-TTY
27423
+ * policy as `sm scan`), threading the verb's stdin / stderr so a TTY
27424
+ * operator is asked y/N. Returns `null` to proceed (no drift, or the
27425
+ * cache was rebuilt) or an `ExitCode` to abort boot when the operator
27426
+ * declines the rebuild.
27427
+ */
27428
+ async #rebuildOnDrift(dbPath, stderrAnsi, warnGlyph) {
27429
+ const outcome = await maybeResetOnDrift(dbPath, {
27430
+ currentVersion: VERSION,
27431
+ assumeYes: this.yes,
27432
+ stdin: this.context.stdin,
27433
+ stderr: this.context.stderr,
27434
+ printer: this.printer,
27435
+ style: { warnGlyph, dim: stderrAnsi.dim }
27436
+ });
27437
+ if (outcome.kind !== "aborted") return null;
27438
+ this.printer.error(
27439
+ tx(SERVE_TEXTS.driftDeclined, {
27440
+ glyph: stderrAnsi.red("\u2715"),
27441
+ dbVersion: outcome.dbVersion,
27442
+ currentVersion: outcome.currentVersion,
27443
+ reason: outcome.reason === "version" ? DB_DRIFT_TEXTS.driftReasonVersion : DB_DRIFT_TEXTS.driftReasonSchema,
27444
+ hint: stderrAnsi.dim(DB_DRIFT_TEXTS.driftAbortedHint)
27445
+ })
27446
+ );
27447
+ return ExitCode.Error;
27448
+ }
27222
27449
  };
27223
27450
  function parsePort(raw) {
27224
27451
  if (raw === void 0) return { ok: true, port: void 0 };
@@ -27434,26 +27661,34 @@ var ShowCommand = class extends SmCommand {
27434
27661
  const dbPath = resolveDbPath({ db: this.db, ...defaultRuntimeContext() });
27435
27662
  const exit = requireDbOrExit(dbPath, this.context.stderr);
27436
27663
  if (exit !== null) return exit;
27437
- return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
27438
- const bundle = await adapter.scans.findNode(this.nodePath);
27439
- if (!bundle) {
27440
- this.printer.error(tx(SHOW_TEXTS.nodeNotFound, { nodePath: this.nodePath }));
27441
- return ExitCode.NotFound;
27442
- }
27443
- const doc = {
27444
- node: bundle.node,
27445
- linksOut: bundle.linksOut,
27446
- linksIn: bundle.linksIn,
27447
- issues: bundle.issues
27448
- };
27449
- if (this.json) {
27450
- this.printer.data(JSON.stringify(doc) + "\n");
27664
+ const stderrAnsi = this.ansiFor("stderr");
27665
+ return withSqlite(
27666
+ {
27667
+ databasePath: dbPath,
27668
+ autoBackup: false,
27669
+ versionCheck: buildReadVersionCheck(this.printer, stderrAnsi)
27670
+ },
27671
+ async (adapter) => {
27672
+ const bundle = await adapter.scans.findNode(this.nodePath);
27673
+ if (!bundle) {
27674
+ this.printer.error(tx(SHOW_TEXTS.nodeNotFound, { nodePath: this.nodePath }));
27675
+ return ExitCode.NotFound;
27676
+ }
27677
+ const doc = {
27678
+ node: bundle.node,
27679
+ linksOut: bundle.linksOut,
27680
+ linksIn: bundle.linksIn,
27681
+ issues: bundle.issues
27682
+ };
27683
+ if (this.json) {
27684
+ this.printer.data(JSON.stringify(doc) + "\n");
27685
+ return ExitCode.Ok;
27686
+ }
27687
+ const ansi = this.ansiFor("stdout");
27688
+ this.printer.data(renderHuman2(doc, ansi));
27451
27689
  return ExitCode.Ok;
27452
27690
  }
27453
- const ansi = this.ansiFor("stdout");
27454
- this.printer.data(renderHuman2(doc, ansi));
27455
- return ExitCode.Ok;
27456
- });
27691
+ );
27457
27692
  }
27458
27693
  };
27459
27694
  function renderHuman2(doc, ansi) {
@@ -28351,7 +28586,7 @@ var STUB_COMMANDS = [
28351
28586
  ];
28352
28587
 
28353
28588
  // cli/commands/tutorial.ts
28354
- import { cpSync as cpSync2, existsSync as existsSync31, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync11 } from "fs";
28589
+ import { cpSync as cpSync2, existsSync as existsSync32, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync11 } from "fs";
28355
28590
  import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
28356
28591
  import { createInterface as createInterface5 } from "readline";
28357
28592
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -28695,7 +28930,7 @@ function resolveSkillSourceDir(variant) {
28695
28930
  resolve39(here, "../cli/tutorial", spec.slug)
28696
28931
  ];
28697
28932
  for (const candidate of candidates) {
28698
- if (existsSync31(candidate) && statSync11(candidate).isDirectory()) {
28933
+ if (existsSync32(candidate) && statSync11(candidate).isDirectory()) {
28699
28934
  cachedSourceDirs.set(variant, candidate);
28700
28935
  return candidate;
28701
28936
  }
@@ -28896,7 +29131,7 @@ function resolveBareInvocation(rawArgs) {
28896
29131
  if (first !== void 0 && first.startsWith("-") && !passthrough.has(first)) {
28897
29132
  const isSingleDashLong = !first.startsWith("--") && first.length > 2;
28898
29133
  if (isSingleDashLong) return null;
28899
- if (existsSync32(defaultProjectDbPath(defaultRuntimeContext()))) {
29134
+ if (existsSync33(defaultProjectDbPath(defaultRuntimeContext()))) {
28900
29135
  return ["serve", ...rawArgs];
28901
29136
  }
28902
29137
  return resolveBareDefault();
@@ -28905,7 +29140,7 @@ function resolveBareInvocation(rawArgs) {
28905
29140
  }
28906
29141
  function resolveBareDefault() {
28907
29142
  const ctx = defaultRuntimeContext();
28908
- if (existsSync32(defaultProjectDbPath(ctx))) {
29143
+ if (existsSync33(defaultProjectDbPath(ctx))) {
28909
29144
  return ["serve"];
28910
29145
  }
28911
29146
  const stderr = process.stderr;
@@ -28920,4 +29155,4 @@ function resolveBareDefault() {
28920
29155
  process.exit(ExitCode.Error);
28921
29156
  }
28922
29157
  //# sourceMappingURL=cli.js.map
28923
- //# debugId=7c131b78-b7ad-5f03-b19c-1f70afdfeefb
29158
+ //# debugId=926243f9-3707-54bc-9aa6-c68955118161