@skill-map/cli 0.45.1 → 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.
Files changed (44) hide show
  1. package/dist/cli/tutorial/sm-master/SKILL.md +29 -29
  2. package/dist/cli/tutorial/sm-master/references/fixture-templates.md +18 -13
  3. package/dist/cli/tutorial/sm-master/references/tour-authoring.md +35 -40
  4. package/dist/cli/tutorial/sm-master/references/tour-plugins.md +32 -32
  5. package/dist/cli/tutorial/sm-master/references/tour-settings.md +156 -75
  6. package/dist/cli/tutorial/sm-tutorial/SKILL.md +3 -3
  7. package/dist/cli.js +998 -458
  8. package/dist/conformance/index.js +4 -1
  9. package/dist/index.js +67 -22
  10. package/dist/kernel/index.d.ts +93 -13
  11. package/dist/kernel/index.js +67 -22
  12. package/dist/migrations/001_initial.sql +23 -0
  13. package/dist/ui/chunk-22CKFAEU.js +1 -0
  14. package/dist/ui/chunk-3AKR33GE.js +1 -0
  15. package/dist/ui/{chunk-QNTAOR2L.js → chunk-3HLMBEDX.js} +1 -1
  16. package/dist/ui/{chunk-T3IVIRRJ.js → chunk-7K36273M.js} +1 -1
  17. package/dist/ui/{chunk-MFLFIA7C.js → chunk-CO2ZOUSD.js} +1 -1
  18. package/dist/ui/{chunk-2RAE3FAN.js → chunk-CRWK2NFZ.js} +1 -1
  19. package/dist/ui/{chunk-VGPYYAVI.js → chunk-EPBUSS3I.js} +1 -1
  20. package/dist/ui/chunk-HAWX5WNM.js +4 -0
  21. package/dist/ui/{chunk-QDUSFOBE.js → chunk-K365TVPA.js} +1 -1
  22. package/dist/ui/chunk-PO2VZMOB.js +123 -0
  23. package/dist/ui/{chunk-X227ITGS.js → chunk-RT7E4S5B.js} +1 -1
  24. package/dist/ui/{chunk-5AD5ZV4I.js → chunk-UIUGLD7F.js} +1 -1
  25. package/dist/ui/{chunk-IYM26L3O.js → chunk-UV3QRBRR.js} +1 -1
  26. package/dist/ui/chunk-VNA3TMIO.js +1 -0
  27. package/dist/ui/{chunk-F7I6KMHX.js → chunk-VW2A6WZ3.js} +1 -1
  28. package/dist/ui/{chunk-A7PRWMQD.js → chunk-WPUUCIS3.js} +11 -11
  29. package/dist/ui/{chunk-MS6B7344.js → chunk-XWU3YFSM.js} +7 -7
  30. package/dist/ui/{chunk-I5AX4U2N.js → chunk-YOF6HQCQ.js} +1 -1
  31. package/dist/ui/chunk-ZZJ7XWDX.js +1 -0
  32. package/dist/ui/index.html +1 -1
  33. package/dist/ui/main-55GYZX6C.js +4 -0
  34. package/migrations/001_initial.sql +23 -0
  35. package/package.json +2 -2
  36. package/dist/cli.js.map +0 -1
  37. package/dist/conformance/index.js.map +0 -1
  38. package/dist/index.js.map +0 -1
  39. package/dist/kernel/index.js.map +0 -1
  40. package/dist/ui/chunk-27WQPOXP.js +0 -1
  41. package/dist/ui/chunk-555ST76V.js +0 -1
  42. package/dist/ui/chunk-PZQHB7GS.js +0 -4
  43. package/dist/ui/chunk-ZIGUUDUX.js +0 -123
  44. package/dist/ui/main-KMSUFJ6Y.js +0 -3
package/dist/cli.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // cli/entry.ts
2
- import { existsSync as existsSync32 } from "fs";
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]="926243f9-3707-54bc-9aa6-c68955118161")}catch(e){}}();
4
+ import { existsSync as existsSync33 } from "fs";
3
5
  import { Builtins, Cli as Cli2 } from "clipanion";
4
6
 
5
7
  // kernel/adapters/in-memory-progress.ts
@@ -244,7 +246,7 @@ function bucketByKind(kind, instance, bag) {
244
246
  // package.json
245
247
  var package_default = {
246
248
  name: "@skill-map/cli",
247
- version: "0.45.1",
249
+ version: "0.47.0",
248
250
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
249
251
  license: "MIT",
250
252
  type: "module",
@@ -598,7 +600,7 @@ var claudeProvider = {
598
600
  read: { extensions: [".md"], parser: "frontmatter-yaml" },
599
601
  // Per spec § A.6, defaultRefreshAction values MUST be qualified action
600
602
  // ids. The summarize-* actions are not yet implemented as registry
601
- // entries (they ship later under the Claude bundle), but the qualified
603
+ // entries (they ship later under the Claude plugin), but the qualified
602
604
  // form is the contract: when those actions land, they will register
603
605
  // under `claude/summarize-<kind>` and the Provider resolves them
604
606
  // deterministically.
@@ -1309,7 +1311,7 @@ var markdown_schema_default = {
1309
1311
  $schema: "https://json-schema.org/draft/2020-12/schema",
1310
1312
  $id: "https://skill-map.ai/providers/core/v1/frontmatter/markdown.schema.json",
1311
1313
  title: "FrontmatterMarkdown",
1312
- description: "Frontmatter shape for nodes classified as `markdown` by the built-in `core/markdown` Provider, the universal fallback for any markdown file no vendor-specific Provider claims (Claude, OpenAI Codex, agent-skills, plus the metadata-only Antigravity bundle). The kind is named after the format because the file is a generic fallback; format-named kinds apply only as the generic fallback, a TOML file that IS a Codex agent still classifies as `agent`, not `toml`. Extends the spec's universal `frontmatter/base.schema.json` via $ref-by-$id with no additional fields. Ownership relocated from the Claude Provider in spec 0.18.0, markdown is provider-agnostic and lives under `core` so adding new vendor Providers does not require choosing which one owns the universal fallback.",
1314
+ description: "Frontmatter shape for nodes classified as `markdown` by the built-in `core/markdown` Provider, the universal fallback for any markdown file no vendor-specific Provider claims (Claude, OpenAI Codex, agent-skills, plus the metadata-only Antigravity plugin). The kind is named after the format because the file is a generic fallback; format-named kinds apply only as the generic fallback, a TOML file that IS a Codex agent still classifies as `agent`, not `toml`. Extends the spec's universal `frontmatter/base.schema.json` via $ref-by-$id with no additional fields. Ownership relocated from the Claude Provider in spec 0.18.0, markdown is provider-agnostic and lives under `core` so adding new vendor Providers does not require choosing which one owns the universal fallback.",
1313
1315
  allOf: [
1314
1316
  { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
1315
1317
  ],
@@ -1340,7 +1342,7 @@ var coreMarkdownProvider = {
1340
1342
  read: { extensions: [".md"], parser: "frontmatter-yaml" },
1341
1343
  // Per spec § A.6, defaultRefreshAction values MUST be qualified
1342
1344
  // action ids. The summarize-markdown action is not yet implemented
1343
- // as a registry entry (it ships later under the core bundle), but
1345
+ // as a registry entry (it ships later under the core plugin), but
1344
1346
  // the qualified form is the contract.
1345
1347
  //
1346
1348
  // UI presentation: same neutral teal that the per-vendor Providers
@@ -4464,7 +4466,7 @@ var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: VERSION };
4464
4466
  var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: VERSION };
4465
4467
  var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: VERSION };
4466
4468
  var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: VERSION };
4467
- var builtInBundles = [
4469
+ var builtInPlugins = [
4468
4470
  {
4469
4471
  id: "claude",
4470
4472
  description: "Claude Code platform integration. Classifies files under `.claude/{agents,commands,skills}` and detects Claude-flavored slash commands and at-directives in their bodies.",
@@ -4539,8 +4541,8 @@ function builtIns() {
4539
4541
  actions: [],
4540
4542
  hooks: []
4541
4543
  };
4542
- for (const bundle of builtInBundles) {
4543
- for (const ext of bundle.extensions) {
4544
+ for (const plugin of builtInPlugins) {
4545
+ for (const ext of plugin.extensions) {
4544
4546
  bucketBuiltIn(ext, out);
4545
4547
  }
4546
4548
  }
@@ -4548,8 +4550,8 @@ function builtIns() {
4548
4550
  }
4549
4551
  function listBuiltIns() {
4550
4552
  const out = [];
4551
- for (const bundle of builtInBundles) {
4552
- for (const x of bundle.extensions) {
4553
+ for (const plugin of builtInPlugins) {
4554
+ for (const x of plugin.extensions) {
4553
4555
  out.push(toExtensionRow(x));
4554
4556
  }
4555
4557
  }
@@ -4603,6 +4605,8 @@ var ENTRY_TEXTS = {
4603
4605
  parseErrorFlagSuggestion: "Did you mean '{{suggestion}}'?",
4604
4606
  parseErrorVerbSuggestion: "Did you mean {{suggestions}}?",
4605
4607
  parseErrorVerbHelpHint: "Run 'sm help {{verb}}' for usage.",
4608
+ /** Footer for the incomplete-namespace error, points at that namespace's overview. */
4609
+ parseErrorNamespaceHelpHint: "Run 'sm help {{name}}' to see all subcommands.",
4606
4610
  parseErrorFooter: "Run 'sm help' to see the full command list."
4607
4611
  };
4608
4612
 
@@ -4891,7 +4895,8 @@ function formatParseError(params) {
4891
4895
  const suggestion2 = tx(ENTRY_TEXTS.parseErrorSubcommandList, {
4892
4896
  suggestions: formatSuggestionList(subcommands)
4893
4897
  });
4894
- return renderError(headline, suggestion2);
4898
+ const footer = tx(ENTRY_TEXTS.parseErrorNamespaceHelpHint, { name: firstToken });
4899
+ return renderError(headline, suggestion2, footer);
4895
4900
  }
4896
4901
  const candidates = closestVerbs(firstToken, verbPaths);
4897
4902
  const suggestion = candidates.length > 0 ? tx(ENTRY_TEXTS.parseErrorVerbSuggestion, { suggestions: formatSuggestionList(candidates) }) : null;
@@ -4932,10 +4937,10 @@ function matchedVerbPrefix(args2, verbPaths) {
4932
4937
  }
4933
4938
  return best.join(" ");
4934
4939
  }
4935
- function renderError(headline, suggestion) {
4940
+ function renderError(headline, suggestion, footer = ENTRY_TEXTS.parseErrorFooter) {
4936
4941
  const lines = [tx(ENTRY_TEXTS.parseErrorHeadline, { message: headline })];
4937
4942
  if (suggestion) lines.push(suggestion);
4938
- lines.push(ENTRY_TEXTS.parseErrorFooter);
4943
+ lines.push(footer);
4939
4944
  return lines.join("\n") + "\n";
4940
4945
  }
4941
4946
  function extractOffendingFlag(message) {
@@ -5365,16 +5370,13 @@ function kernelLocalSettingsPath(scopeRoot) {
5365
5370
  // config/defaults.json
5366
5371
  var defaults_default = {
5367
5372
  schemaVersion: 1,
5368
- autoMigrate: true,
5369
5373
  allowEditSmFiles: false,
5370
5374
  tokenizer: "cl100k_base",
5371
- providers: [],
5372
5375
  roots: [],
5373
5376
  ignore: [],
5374
5377
  scan: {
5375
5378
  tokenize: true,
5376
5379
  strict: false,
5377
- followSymlinks: false,
5378
5380
  maxFileSizeBytes: 1048576,
5379
5381
  maxNodes: 256,
5380
5382
  watch: {
@@ -5383,9 +5385,6 @@ var defaults_default = {
5383
5385
  referencePaths: []
5384
5386
  },
5385
5387
  plugins: {},
5386
- history: {
5387
- share: false
5388
- },
5389
5388
  jobs: {
5390
5389
  ttlSeconds: 3600,
5391
5390
  graceMultiplier: 3,
@@ -5396,9 +5395,6 @@ var defaults_default = {
5396
5395
  completed: 2592e3,
5397
5396
  failed: null
5398
5397
  }
5399
- },
5400
- i18n: {
5401
- locale: "en"
5402
5398
  }
5403
5399
  };
5404
5400
 
@@ -6224,12 +6220,12 @@ var SmCommand = class extends Command {
6224
6220
  };
6225
6221
 
6226
6222
  // core/sqlite/with-sqlite.ts
6227
- import { existsSync as existsSync10 } from "fs";
6223
+ import { existsSync as existsSync11 } from "fs";
6228
6224
 
6229
6225
  // kernel/adapters/sqlite/storage-adapter.ts
6230
6226
  import { mkdirSync as mkdirSync4 } from "fs";
6231
6227
  import { dirname as dirname8, resolve as resolve12 } from "path";
6232
- import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
6228
+ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
6233
6229
  import { CamelCasePlugin, Kysely, sql as sql3 } from "kysely";
6234
6230
 
6235
6231
  // kernel/i18n/storage.texts.ts
@@ -7510,20 +7506,29 @@ async function loadScanResult(db) {
7510
7506
  version: metaRow.scannedByVersion,
7511
7507
  specVersion: metaRow.scannedBySpecVersion
7512
7508
  };
7509
+ const oversizedFiles = parseJsonArray(metaRow.oversizedFilesJson);
7513
7510
  return {
7514
7511
  schemaVersion: 1,
7515
7512
  scannedAt: metaRow.scannedAt,
7516
7513
  roots: parseJsonArray(metaRow.rootsJson),
7517
7514
  providers: parseJsonArray(metaRow.providersJson),
7518
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 } : {},
7519
7522
  recommendedNodeLimit: metaRow.recommendedNodeLimit,
7520
7523
  overrideMaxNodes: metaRow.overrideMaxNodes,
7524
+ oversizedFiles,
7521
7525
  nodes,
7522
7526
  links,
7523
7527
  issues,
7524
7528
  stats: {
7525
7529
  filesWalked: metaRow.statsFilesWalked,
7526
7530
  filesSkipped: metaRow.statsFilesSkipped,
7531
+ filesOversized: metaRow.filesOversized,
7527
7532
  nodesCount: nodes.length,
7528
7533
  linksCount: links.length,
7529
7534
  issuesCount: issues.length,
@@ -7546,12 +7551,14 @@ async function loadScanResult(db) {
7546
7551
  // scan overwrites scan_meta with the live values on next run.
7547
7552
  recommendedNodeLimit: 256,
7548
7553
  overrideMaxNodes: null,
7554
+ oversizedFiles: [],
7549
7555
  nodes,
7550
7556
  links,
7551
7557
  issues,
7552
7558
  stats: {
7553
7559
  filesWalked: 0,
7554
7560
  filesSkipped: 0,
7561
+ filesOversized: 0,
7555
7562
  nodesCount: nodes.length,
7556
7563
  linksCount: links.length,
7557
7564
  issuesCount: issues.length,
@@ -7845,6 +7852,58 @@ function rowToContribution(row) {
7845
7852
  };
7846
7853
  }
7847
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
+
7848
7907
  // kernel/adapters/sqlite/tags.ts
7849
7908
  async function replaceAllScanTags(trx, records, livePaths = /* @__PURE__ */ new Set()) {
7850
7909
  if (livePaths.size > 0) {
@@ -8124,11 +8183,30 @@ function metaToRow(result) {
8124
8183
  scannedByName: result.scannedBy?.name ?? "skill-map",
8125
8184
  scannedByVersion: result.scannedBy?.version ?? "unknown",
8126
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(),
8127
8191
  providersJson: JSON.stringify(result.providers),
8128
8192
  statsFilesWalked: result.stats.filesWalked,
8129
8193
  statsFilesSkipped: result.stats.filesSkipped,
8130
8194
  statsDurationMs: result.stats.durationMs,
8131
- ...projectNodeLimitColumns(result)
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,
8201
+ ...projectNodeLimitColumns(result),
8202
+ ...projectOversizedColumns(result)
8203
+ };
8204
+ }
8205
+ function projectOversizedColumns(result) {
8206
+ const oversized = result.oversizedFiles ?? [];
8207
+ return {
8208
+ filesOversized: result.stats.filesOversized ?? oversized.length,
8209
+ oversizedFilesJson: oversized.length > 0 ? JSON.stringify(oversized) : null
8132
8210
  };
8133
8211
  }
8134
8212
  function projectNodeLimitColumns(result) {
@@ -8254,7 +8332,7 @@ var SqliteStorageAdapter = class {
8254
8332
  if (this.#options.autoMigrate !== false) {
8255
8333
  const files = discoverMigrations();
8256
8334
  if (files.length > 0) {
8257
- const raw = new DatabaseSync3(path);
8335
+ const raw = new DatabaseSync4(path);
8258
8336
  try {
8259
8337
  raw.exec("PRAGMA foreign_keys = ON");
8260
8338
  applyMigrations(
@@ -8641,7 +8719,7 @@ async function listFavoritePaths(db) {
8641
8719
  return new Set(rows.map((r) => r.nodePath));
8642
8720
  }
8643
8721
  function withRawDb(path, fn) {
8644
- const raw = new DatabaseSync3(path);
8722
+ const raw = new DatabaseSync4(path);
8645
8723
  try {
8646
8724
  return fn(raw);
8647
8725
  } finally {
@@ -8720,7 +8798,14 @@ var DB_VERSION_TEXTS = {
8720
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",
8721
8799
  dbVersionMajorMismatchHint: "Delete the `.skill-map/` directory and re-scan, or revert to a CLI in the {{dbMajor}}.x series.",
8722
8800
  dbVersionOlder: "{{glyph}} This DB was last written by an older skill-map ({{dbVersion}}, you have {{currentVersion}}).\n {{hint}}\n",
8723
- 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."
8724
8809
  // The defensive wrapper for `parseConfidence` / `parseLinkKind` /
8725
8810
  // `parseSeverity` failures during `loadScanResult` (when the meta
8726
8811
  // row was wiped and the version check returned `no-meta`) lives in
@@ -8735,10 +8820,17 @@ var DB_VERSION_TEXTS = {
8735
8820
  // core/sqlite/db-version-runner.ts
8736
8821
  var WARN_SEEN = /* @__PURE__ */ new Set();
8737
8822
  async function runDbVersionCheck(db, opts) {
8738
- const outcome = await detectDbVersionSkew(db, opts.currentVersion);
8823
+ const versionOutcome = await detectDbVersionSkew(db, opts.currentVersion);
8824
+ const outcome = layerFingerprintOutcome(versionOutcome, opts);
8739
8825
  applyDbVersionOutcome(outcome, opts);
8740
8826
  return outcome;
8741
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
+ }
8742
8834
  function applyDbVersionOutcome(outcome, opts) {
8743
8835
  switch (outcome.kind) {
8744
8836
  case "ok":
@@ -8751,6 +8843,9 @@ function applyDbVersionOutcome(outcome, opts) {
8751
8843
  case "warn-older":
8752
8844
  renderWarnOlder(outcome, opts);
8753
8845
  break;
8846
+ case "warn-schema":
8847
+ renderWarnSchema(outcome, opts);
8848
+ break;
8754
8849
  }
8755
8850
  }
8756
8851
  function renderErrorNewer(outcome, opts) {
@@ -8804,6 +8899,21 @@ function renderWarnOlder(outcome, opts) {
8804
8899
  })
8805
8900
  );
8806
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
+ }
8807
8917
 
8808
8918
  // core/sqlite/with-sqlite.ts
8809
8919
  async function withSqlite(options, fn) {
@@ -8823,7 +8933,7 @@ async function withSqlite(options, fn) {
8823
8933
  }
8824
8934
  }
8825
8935
  async function tryWithSqlite(options, fn) {
8826
- if (options.databasePath !== ":memory:" && !existsSync10(options.databasePath)) {
8936
+ if (options.databasePath !== ":memory:" && !existsSync11(options.databasePath)) {
8827
8937
  return null;
8828
8938
  }
8829
8939
  return withSqlite(options, fn);
@@ -9424,6 +9534,19 @@ var CHECK_TEXTS = {
9424
9534
  unknownAnalyzerIds: "sm check: unknown analyzer id(s) in --analyzers: {{unknown}}.\nValid ids (qualified or short form accepted):\n{{known}}\n"
9425
9535
  };
9426
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
+
9427
9550
  // cli/util/conformance-env.ts
9428
9551
  var ENV_DISABLE_PROVIDERS = "SKILL_MAP_DISABLE_ALL_PROVIDERS";
9429
9552
  var ENV_DISABLE_EXTRACTORS = "SKILL_MAP_DISABLE_ALL_EXTRACTORS";
@@ -9452,6 +9575,7 @@ var PLUGIN_LOADER_TEXTS = {
9452
9575
  invalidManifestDirMismatch: "directory name '{{dirName}}' does not match manifest id '{{manifestId}}'. Rename the directory to match the id, or update the manifest id to match the directory.",
9453
9576
  idCollision: "Plugin '{{id}}' at {{pathA}} collides with the plugin at {{pathB}}. Rename one and rerun.",
9454
9577
  loadErrorPluginIdMismatch: "{{relEntry}}: extension declares pluginId '{{declared}}' but its plugin.json declares id '{{manifestId}}'. Remove the explicit pluginId from the extension; the loader injects it from plugin.json#/id.",
9578
+ invalidManifestRedeclaredField: "{{relEntry}}: extension manifest declares {{fields}}, derived from the folder layout (structure-as-truth) and not a manifest field. Remove it: id is the leaf folder, kind the parent folder, provider kinds the `kinds/` catalog, formatter formatId the formatter folder name.",
9455
9579
  loadErrorStorageSchemaRead: "plugin '{{pluginId}}' failed to load schema for table '{{table}}': {{schemaPath}}: {{errDescription}}",
9456
9580
  loadErrorStorageSchemaCompile: "plugin '{{pluginId}}' failed to compile schema for table '{{table}}': {{schemaPath}}: {{errDescription}}",
9457
9581
  loadErrorStorageKvSchemaRead: "plugin '{{pluginId}}' failed to load KV schema: {{schemaPath}}: {{errDescription}}",
@@ -9467,7 +9591,7 @@ var PLUGIN_LOADER_TEXTS = {
9467
9591
 
9468
9592
  // kernel/adapters/plugin-loader/index.ts
9469
9593
  import { createRequire as createRequire5 } from "module";
9470
- 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";
9471
9595
  import { join as join8, resolve as resolve17 } from "path";
9472
9596
  import { pathToFileURL } from "url";
9473
9597
  import semver from "semver";
@@ -9556,7 +9680,7 @@ function extractDefault(mod) {
9556
9680
  if (!isRecord(mod)) return mod;
9557
9681
  return "default" in mod ? mod["default"] : mod;
9558
9682
  }
9559
- var LOADER_INJECTED_KEYS = /* @__PURE__ */ new Set(["pluginId", "id", "kind", "kinds", "formatId"]);
9683
+ var LOADER_INJECTED_KEYS = /* @__PURE__ */ new Set(["pluginId"]);
9560
9684
  function stripFunctionsAndPluginId(input) {
9561
9685
  if (!isRecord(input)) return input;
9562
9686
  const out = {};
@@ -9570,7 +9694,7 @@ function stripFunctionsAndPluginId(input) {
9570
9694
 
9571
9695
  // kernel/adapters/plugin-loader/validation.ts
9572
9696
  import * as nodeFs from "fs";
9573
- import { existsSync as existsSync11 } from "fs";
9697
+ import { existsSync as existsSync12 } from "fs";
9574
9698
  import { dirname as dirname9, join as join7 } from "path";
9575
9699
  import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
9576
9700
 
@@ -9696,7 +9820,7 @@ function validateActionFileConventions(pluginPath, pluginId, manifest, relEntry,
9696
9820
  const reportSchemaPath = join7(actionDir, "report.schema.json");
9697
9821
  const promptPath = join7(actionDir, "prompt.md");
9698
9822
  const mode = isRecord(manifestView) && typeof manifestView["mode"] === "string" ? manifestView["mode"] : "deterministic";
9699
- if (!existsSync11(reportSchemaPath)) {
9823
+ if (!existsSync12(reportSchemaPath)) {
9700
9824
  return {
9701
9825
  ...fail(
9702
9826
  pluginPath,
@@ -9707,7 +9831,7 @@ function validateActionFileConventions(pluginPath, pluginId, manifest, relEntry,
9707
9831
  manifest
9708
9832
  };
9709
9833
  }
9710
- const promptExists = existsSync11(promptPath);
9834
+ const promptExists = existsSync12(promptPath);
9711
9835
  if (mode === "probabilistic" && !promptExists) {
9712
9836
  return {
9713
9837
  ...fail(
@@ -9819,7 +9943,7 @@ function isDirectorySafe(path, statSync12) {
9819
9943
  }
9820
9944
 
9821
9945
  // kernel/adapters/plugin-loader/storage-schemas.ts
9822
- import { readFileSync as readFileSync11 } from "fs";
9946
+ import { readFileSync as readFileSync12 } from "fs";
9823
9947
  import { resolve as resolve16 } from "path";
9824
9948
  import { Ajv2020 as Ajv20206 } from "ajv/dist/2020.js";
9825
9949
 
@@ -9887,7 +10011,7 @@ function compilePluginSchema(pluginPath, relPath) {
9887
10011
  const abs = resolve16(pluginPath, relPath);
9888
10012
  let raw;
9889
10013
  try {
9890
- raw = JSON.parse(readFileSync11(abs, "utf8"));
10014
+ raw = JSON.parse(readFileSync12(abs, "utf8"));
9891
10015
  } catch (err) {
9892
10016
  return { ok: false, phase: "read", errDescription: describe(err) };
9893
10017
  }
@@ -9921,11 +10045,11 @@ var PluginLoader = class {
9921
10045
  discoverPaths() {
9922
10046
  const out = [];
9923
10047
  for (const root of this.#options.searchPaths) {
9924
- if (!existsSync12(root)) continue;
10048
+ if (!existsSync13(root)) continue;
9925
10049
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
9926
10050
  if (!entry.isDirectory()) continue;
9927
10051
  const candidate = join8(root, entry.name);
9928
- if (existsSync12(join8(candidate, "plugin.json"))) {
10052
+ if (existsSync13(join8(candidate, "plugin.json"))) {
9929
10053
  out.push(resolve17(candidate));
9930
10054
  }
9931
10055
  }
@@ -10009,7 +10133,7 @@ var PluginLoader = class {
10009
10133
  const manifestPath = join8(pluginPath, "plugin.json");
10010
10134
  let raw;
10011
10135
  try {
10012
- raw = JSON.parse(readFileSync12(manifestPath, "utf8"));
10136
+ raw = JSON.parse(readFileSync13(manifestPath, "utf8"));
10013
10137
  } catch (err) {
10014
10138
  return { ok: false, failure: fail(
10015
10139
  pluginPath,
@@ -10086,7 +10210,7 @@ var PluginLoader = class {
10086
10210
  } };
10087
10211
  }
10088
10212
  const abs = resolve17(pluginPath, relEntry);
10089
- if (!existsSync12(abs)) {
10213
+ if (!existsSync13(abs)) {
10090
10214
  return { ok: false, failure: {
10091
10215
  ...fail(
10092
10216
  pluginPath,
@@ -10177,6 +10301,21 @@ var PluginLoader = class {
10177
10301
  manifest
10178
10302
  } };
10179
10303
  }
10304
+ const redeclared = DERIVED_MANIFEST_KEYS.filter((field) => field in exported);
10305
+ if (redeclared.length > 0) {
10306
+ return { ok: false, failure: {
10307
+ ...fail(
10308
+ pluginPath,
10309
+ pluginId,
10310
+ "invalid-manifest",
10311
+ tx(PLUGIN_LOADER_TEXTS.invalidManifestRedeclaredField, {
10312
+ relEntry,
10313
+ fields: redeclared.map((field) => `\`${field}\``).join(", ")
10314
+ })
10315
+ ),
10316
+ manifest
10317
+ } };
10318
+ }
10180
10319
  const manifestView = stripFunctionsAndPluginId(exported);
10181
10320
  if (kind === "hook") {
10182
10321
  const hookFailure = validateHookTriggers(pluginPath, pluginId, manifest, relEntry, exported, manifestView);
@@ -10233,8 +10372,7 @@ var PluginLoader = class {
10233
10372
  const instance = { ...exported, pluginId, id: pathId2, kind };
10234
10373
  if (kind === "formatter") instance["formatId"] = pathId2;
10235
10374
  if (kind === "provider" && discoveredKinds) {
10236
- const inlineKinds = isRecord(exported["kinds"]) ? exported["kinds"] : {};
10237
- instance["kinds"] = { ...inlineKinds, ...discoveredKinds };
10375
+ instance["kinds"] = discoveredKinds;
10238
10376
  }
10239
10377
  return { ok: true, extension: {
10240
10378
  kind,
@@ -10247,6 +10385,7 @@ var PluginLoader = class {
10247
10385
  } };
10248
10386
  }
10249
10387
  };
10388
+ var DERIVED_MANIFEST_KEYS = ["id", "kind", "kinds", "formatId"];
10250
10389
  var KIND_DIR_NAMES = [
10251
10390
  "providers",
10252
10391
  "extractors",
@@ -10269,7 +10408,7 @@ function discoverExtensionEntries(pluginPath) {
10269
10408
  }
10270
10409
  function collectKindEntries(pluginPath, kindDir, out) {
10271
10410
  const kindAbs = resolve17(pluginPath, kindDir);
10272
- if (!existsSync12(kindAbs)) return;
10411
+ if (!existsSync13(kindAbs)) return;
10273
10412
  let entries;
10274
10413
  try {
10275
10414
  entries = readdirSync4(kindAbs);
@@ -10296,7 +10435,7 @@ function isDirectorySafe2(path) {
10296
10435
  }
10297
10436
  function findIndexCandidate(entryAbs) {
10298
10437
  for (const candidate of INDEX_CANDIDATES) {
10299
- if (existsSync12(resolve17(entryAbs, candidate))) return candidate;
10438
+ if (existsSync13(resolve17(entryAbs, candidate))) return candidate;
10300
10439
  }
10301
10440
  return null;
10302
10441
  }
@@ -10304,7 +10443,7 @@ function installedSpecVersion() {
10304
10443
  const require2 = createRequire5(import.meta.url);
10305
10444
  const indexPath = require2.resolve("@skill-map/spec/index.json");
10306
10445
  const pkgPath = resolve17(indexPath, "..", "package.json");
10307
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
10446
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
10308
10447
  return pkg.version;
10309
10448
  }
10310
10449
 
@@ -10365,11 +10504,11 @@ function makeEnabledResolver(cfg, dbOverrides) {
10365
10504
  function defaultResolveEnabled(_id) {
10366
10505
  return true;
10367
10506
  }
10368
- function isBuiltInExtensionEnabled(bundle, ext, resolveEnabled) {
10369
- return isBundleEntryEnabled(bundle, ext.id, resolveEnabled);
10507
+ function isBuiltInExtensionEnabled(plugin, ext, resolveEnabled) {
10508
+ return isPluginEntryEnabled(plugin, ext.id, resolveEnabled);
10370
10509
  }
10371
- function isBundleEntryEnabled(bundle, extId, resolveEnabled) {
10372
- return resolveEnabled(qualifiedExtensionId(bundle.id, extId));
10510
+ function isPluginEntryEnabled(plugin, extId, resolveEnabled) {
10511
+ return resolveEnabled(qualifiedExtensionId(plugin.id, extId));
10373
10512
  }
10374
10513
  function isPluginExtensionEnabled(ext, resolveEnabled) {
10375
10514
  return resolveEnabled(qualifiedExtensionId(ext.pluginId, ext.id));
@@ -10392,7 +10531,7 @@ import { readFile, readdir, lstat } from "fs/promises";
10392
10531
  import { join as join9, relative as relative2, sep as sep3 } from "path";
10393
10532
 
10394
10533
  // kernel/scan/ignore.ts
10395
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
10534
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
10396
10535
  import { dirname as dirname10, resolve as resolve18 } from "path";
10397
10536
  import { fileURLToPath as fileURLToPath2 } from "url";
10398
10537
  import ignoreFactory from "ignore";
@@ -10423,9 +10562,9 @@ function loadBundledIgnoreText() {
10423
10562
  }
10424
10563
  function readIgnoreFileText(scopeRoot) {
10425
10564
  const path = resolve18(scopeRoot, ".skillmapignore");
10426
- if (!existsSync13(path)) return void 0;
10565
+ if (!existsSync14(path)) return void 0;
10427
10566
  try {
10428
- return readFileSync13(path, "utf8");
10567
+ return readFileSync14(path, "utf8");
10429
10568
  } catch {
10430
10569
  return void 0;
10431
10570
  }
@@ -10458,9 +10597,9 @@ function readDefaultsFromDisk() {
10458
10597
  resolve18(here, "config/defaults/skillmapignore")
10459
10598
  ];
10460
10599
  for (const candidate of candidates) {
10461
- if (existsSync13(candidate)) {
10600
+ if (existsSync14(candidate)) {
10462
10601
  try {
10463
- return readFileSync13(candidate, "utf8");
10602
+ return readFileSync14(candidate, "utf8");
10464
10603
  } catch {
10465
10604
  }
10466
10605
  }
@@ -10568,8 +10707,9 @@ async function* walkContent(roots, options) {
10568
10707
  if (!parser) throw new UnknownParserError(options.parser);
10569
10708
  const filter = options.ignoreFilter ?? buildIgnoreFilter();
10570
10709
  const extensions = options.extensions;
10710
+ const sizeLimit = buildSizeLimit(options);
10571
10711
  for (const root of roots) {
10572
- for await (const file of walkRoot(root, root, filter, extensions)) {
10712
+ for await (const file of walkRoot(root, root, filter, extensions, sizeLimit)) {
10573
10713
  const relPath = relative2(root, file).split(sep3).join("/");
10574
10714
  let raw;
10575
10715
  try {
@@ -10592,7 +10732,15 @@ async function* walkContent(roots, options) {
10592
10732
  }
10593
10733
  }
10594
10734
  }
10595
- async function* walkRoot(root, current, filter, extensions) {
10735
+ function buildSizeLimit(options) {
10736
+ const sizeLimit = {};
10737
+ if (options.maxFileSizeBytes !== void 0) {
10738
+ sizeLimit.maxFileSizeBytes = options.maxFileSizeBytes;
10739
+ }
10740
+ if (options.onOversizedFile) sizeLimit.onOversizedFile = options.onOversizedFile;
10741
+ return sizeLimit;
10742
+ }
10743
+ async function* walkRoot(root, current, filter, extensions, sizeLimit) {
10596
10744
  let entries;
10597
10745
  try {
10598
10746
  entries = await readdir(current, { withFileTypes: true, encoding: "utf8" });
@@ -10606,11 +10754,16 @@ async function* walkRoot(root, current, filter, extensions) {
10606
10754
  if (filter.ignores(rel)) continue;
10607
10755
  if (entry.isSymbolicLink()) continue;
10608
10756
  if (entry.isDirectory()) {
10609
- yield* walkRoot(root, full, filter, extensions);
10757
+ yield* walkRoot(root, full, filter, extensions, sizeLimit);
10610
10758
  } else if (entry.isFile() && hasMatchingExtension(name, extensions)) {
10611
10759
  try {
10612
10760
  const s = await lstat(full);
10613
- if (s.isFile()) yield full;
10761
+ if (!s.isFile()) continue;
10762
+ if (sizeLimit.maxFileSizeBytes !== void 0 && s.size > sizeLimit.maxFileSizeBytes) {
10763
+ sizeLimit.onOversizedFile?.({ path: rel, bytes: s.size });
10764
+ continue;
10765
+ }
10766
+ yield full;
10614
10767
  } catch {
10615
10768
  }
10616
10769
  }
@@ -10640,6 +10793,10 @@ function resolveProviderWalk(provider) {
10640
10793
  parser: read.parser
10641
10794
  };
10642
10795
  if (options?.ignoreFilter) walkOptions.ignoreFilter = options.ignoreFilter;
10796
+ if (options?.maxFileSizeBytes !== void 0) {
10797
+ walkOptions.maxFileSizeBytes = options.maxFileSizeBytes;
10798
+ }
10799
+ if (options?.onOversizedFile) walkOptions.onOversizedFile = options.onOversizedFile;
10643
10800
  return walkContent(roots, walkOptions);
10644
10801
  };
10645
10802
  }
@@ -10674,19 +10831,19 @@ function collectViewContributions(pluginId, extensionId, instance, out, options
10674
10831
  }
10675
10832
 
10676
10833
  // core/runtime/plugin-runtime/bucketing.ts
10677
- function bucketLoaded(loaded, bundle) {
10834
+ function bucketLoaded(loaded, runtime) {
10678
10835
  for (const ext of loaded) {
10679
10836
  const instance = ext.instance;
10680
10837
  if (!isExtensionInstance(instance)) continue;
10681
10838
  bucketByKind(ext.kind, instance, {
10682
- provider: bundle.extensions.providers,
10683
- extractor: bundle.extensions.extractors,
10684
- analyzer: bundle.extensions.analyzers,
10685
- formatter: bundle.extensions.formatters,
10686
- hook: bundle.extensions.hooks
10839
+ provider: runtime.extensions.providers,
10840
+ extractor: runtime.extensions.extractors,
10841
+ analyzer: runtime.extensions.analyzers,
10842
+ formatter: runtime.extensions.formatters,
10843
+ hook: runtime.extensions.hooks
10687
10844
  // `action` intentionally absent, see docstring.
10688
10845
  });
10689
- bundle.manifests.push({
10846
+ runtime.manifests.push({
10690
10847
  id: ext.id,
10691
10848
  pluginId: ext.pluginId,
10692
10849
  kind: ext.kind,
@@ -10694,8 +10851,8 @@ function bucketLoaded(loaded, bundle) {
10694
10851
  description: instance.description ?? "",
10695
10852
  ...ext.entryPath ? { entry: ext.entryPath } : {}
10696
10853
  });
10697
- collectAnnotationContributions(ext.pluginId, instance, bundle.annotationContributions);
10698
- collectViewContributions(ext.pluginId, ext.id, instance, bundle.viewContributions);
10854
+ collectAnnotationContributions(ext.pluginId, instance, runtime.annotationContributions);
10855
+ collectViewContributions(ext.pluginId, ext.id, instance, runtime.viewContributions);
10699
10856
  }
10700
10857
  }
10701
10858
  function collectAnnotationContributions(pluginId, instance, out) {
@@ -10753,8 +10910,8 @@ var PLUGIN_RUNTIME_TEXTS = {
10753
10910
  // core/runtime/plugin-runtime/warnings.ts
10754
10911
  var PLUGIN_ID_DISPLAY_CAP = 200;
10755
10912
  var PLUGIN_REASON_DISPLAY_CAP = 1e3;
10756
- function emitWarnings(bundle, printer) {
10757
- for (const warn of bundle.warnings) {
10913
+ function emitWarnings(runtime, printer) {
10914
+ for (const warn of runtime.warnings) {
10758
10915
  printer.warn(`${warn}
10759
10916
  `);
10760
10917
  }
@@ -10793,7 +10950,7 @@ async function loadPluginRuntime(opts = {}) {
10793
10950
  if (resolveEnabled) loaderOpts.resolveEnabled = resolveEnabled;
10794
10951
  const loader = createPluginLoader(loaderOpts);
10795
10952
  const discovered = await loader.discoverAndLoadAll();
10796
- const bundle = {
10953
+ const runtime = {
10797
10954
  extensions: { providers: [], extractors: [], analyzers: [], formatters: [], hooks: [] },
10798
10955
  annotationContributions: [],
10799
10956
  viewContributions: [],
@@ -10807,14 +10964,14 @@ async function loadPluginRuntime(opts = {}) {
10807
10964
  };
10808
10965
  for (const plugin of discovered) {
10809
10966
  if (plugin.status === "enabled") {
10810
- bucketLoaded(plugin.extensions ?? [], bundle);
10967
+ bucketLoaded(plugin.extensions ?? [], runtime);
10811
10968
  continue;
10812
10969
  }
10813
10970
  if (plugin.status === "disabled") continue;
10814
- bundle.warnings.push(formatWarning(plugin));
10971
+ runtime.warnings.push(formatWarning(plugin));
10815
10972
  }
10816
- enforceRootExclusivity(bundle.annotationContributions);
10817
- return bundle;
10973
+ enforceRootExclusivity(runtime.annotationContributions);
10974
+ return runtime;
10818
10975
  }
10819
10976
  var AnnotationContributionConflictError = class extends Error {
10820
10977
  /** The colliding root-exclusive key. */
@@ -10848,7 +11005,7 @@ function enforceRootExclusivity(catalog) {
10848
11005
  }
10849
11006
  }
10850
11007
  function emptyPluginRuntime() {
10851
- const bundle = {
11008
+ const runtime = {
10852
11009
  extensions: { providers: [], extractors: [], analyzers: [], formatters: [], hooks: [] },
10853
11010
  annotationContributions: [],
10854
11011
  viewContributions: [],
@@ -10860,7 +11017,7 @@ function emptyPluginRuntime() {
10860
11017
  emitWarnings(this, printer);
10861
11018
  }
10862
11019
  };
10863
- return bundle;
11020
+ return runtime;
10864
11021
  }
10865
11022
 
10866
11023
  // core/runtime/plugin-runtime/catalogs.ts
@@ -10878,12 +11035,12 @@ function collectRegisteredContributionKeys(composed) {
10878
11035
  return keys;
10879
11036
  }
10880
11037
  function filterBuiltInManifests(manifests, resolveEnabled) {
10881
- const bundleByPluginId = /* @__PURE__ */ new Map();
10882
- for (const bundle of builtInBundles) bundleByPluginId.set(bundle.id, bundle);
11038
+ const pluginById = /* @__PURE__ */ new Map();
11039
+ for (const plugin of builtInPlugins) pluginById.set(plugin.id, plugin);
10883
11040
  return manifests.filter((m) => {
10884
- const bundle = bundleByPluginId.get(m.pluginId);
10885
- if (!bundle) return true;
10886
- return isBundleEntryEnabled(bundle, m.id, resolveEnabled);
11041
+ const plugin = pluginById.get(m.pluginId);
11042
+ if (!plugin) return true;
11043
+ return isPluginEntryEnabled(plugin, m.id, resolveEnabled);
10887
11044
  });
10888
11045
  }
10889
11046
 
@@ -10926,9 +11083,9 @@ function composeScanExtensions(opts) {
10926
11083
  };
10927
11084
  }
10928
11085
  function accumulateBuiltInScanExtensions(buckets, resolveEnabled) {
10929
- for (const bundle of builtInBundles) {
10930
- for (const ext of bundle.extensions) {
10931
- if (!isBuiltInExtensionEnabled(bundle, ext, resolveEnabled)) continue;
11086
+ for (const plugin of builtInPlugins) {
11087
+ for (const ext of plugin.extensions) {
11088
+ if (!isBuiltInExtensionEnabled(plugin, ext, resolveEnabled)) continue;
10932
11089
  switch (ext.kind) {
10933
11090
  case "provider":
10934
11091
  buckets.providers.push(ext);
@@ -10959,10 +11116,10 @@ function composeFormatters(opts) {
10959
11116
  const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
10960
11117
  const out = [];
10961
11118
  if (!noBuiltIns) {
10962
- for (const bundle of builtInBundles) {
10963
- for (const ext of bundle.extensions) {
11119
+ for (const plugin of builtInPlugins) {
11120
+ for (const ext of plugin.extensions) {
10964
11121
  if (ext.kind !== "formatter") continue;
10965
- if (!isBuiltInExtensionEnabled(bundle, ext, resolveEnabled)) continue;
11122
+ if (!isBuiltInExtensionEnabled(plugin, ext, resolveEnabled)) continue;
10966
11123
  out.push(ext);
10967
11124
  }
10968
11125
  }
@@ -10998,9 +11155,9 @@ function registerEnabledExtensions(kernel, pluginRuntime, options = {}) {
10998
11155
  );
10999
11156
  const merged = [...userContribs];
11000
11157
  if (!noBuiltIns) {
11001
- for (const bundle of builtInBundles) {
11002
- for (const ext of bundle.extensions) {
11003
- if (!isBundleEntryEnabled(bundle, ext.id, resolveEnabled)) continue;
11158
+ for (const plugin of builtInPlugins) {
11159
+ for (const ext of plugin.extensions) {
11160
+ if (!isPluginEntryEnabled(plugin, ext.id, resolveEnabled)) continue;
11004
11161
  collectViewContributions(ext.pluginId, ext.id, ext, merged);
11005
11162
  }
11006
11163
  }
@@ -11060,27 +11217,35 @@ var CheckCommand = class extends SmCommand {
11060
11217
  const analyzerFilter = parseAnalyzersFlag(this.analyzers);
11061
11218
  const preflight = await this.#preflightAnalyzerCatalog(analyzerFilter);
11062
11219
  if (preflight.exit !== null) return preflight.exit;
11063
- return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
11064
- let issues = await adapter.issues.listAll();
11065
- if (this.node !== void 0) {
11066
- const nodePath = this.node;
11067
- issues = issues.filter((i) => i.nodeIds.includes(nodePath));
11068
- }
11069
- if (analyzerFilter !== void 0) {
11070
- issues = issues.filter((i) => matchesAnalyzerFilter(i.analyzerId, analyzerFilter));
11071
- }
11072
- const ansi = this.ansiFor("stdout");
11073
- if (this.json) {
11074
- this.printer.data(JSON.stringify(issues) + "\n");
11075
- } else if (issues.length === 0) {
11076
- this.printer.data(
11077
- tx(CHECK_TEXTS.noIssues, { glyph: ansi.green("\u2713") })
11078
- );
11079
- } else {
11080
- 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;
11081
11247
  }
11082
- return issues.some((i) => i.severity === "error") ? ExitCode.Issues : ExitCode.Ok;
11083
- });
11248
+ );
11084
11249
  }
11085
11250
  /**
11086
11251
  * Either an explicit `--analyzers` list or `--include-prob` forces a
@@ -11249,11 +11414,11 @@ function trimRedundantPath(message, primary) {
11249
11414
  }
11250
11415
 
11251
11416
  // cli/commands/config.ts
11252
- import { existsSync as existsSync15 } from "fs";
11417
+ import { existsSync as existsSync16 } from "fs";
11253
11418
  import { Command as Command4, Option as Option4 } from "clipanion";
11254
11419
 
11255
11420
  // core/config/active-provider.ts
11256
- import { existsSync as existsSync14 } from "fs";
11421
+ import { existsSync as existsSync15 } from "fs";
11257
11422
  import { join as join10 } from "path";
11258
11423
  function resolveActiveProvider(cwd, providers = []) {
11259
11424
  const detected = detectProvidersFromFilesystem(cwd, providers);
@@ -11273,7 +11438,7 @@ function detectProvidersFromFilesystem(cwd, providers) {
11273
11438
  if (seen.has(provider.id)) continue;
11274
11439
  const markers = provider.detect?.markers;
11275
11440
  if (!markers || markers.length === 0) continue;
11276
- if (!markers.some((marker) => existsSync14(join10(cwd, marker)))) continue;
11441
+ if (!markers.some((marker) => existsSync15(join10(cwd, marker)))) continue;
11277
11442
  seen.add(provider.id);
11278
11443
  out.push(provider.id);
11279
11444
  }
@@ -11290,7 +11455,7 @@ function relativeIfBelow(path, cwd) {
11290
11455
  }
11291
11456
 
11292
11457
  // cli/util/scan-zone-drop.ts
11293
- import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
11458
+ import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
11294
11459
 
11295
11460
  // cli/commands/db/shared.ts
11296
11461
  var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -11302,7 +11467,7 @@ function assertSafeIdentifier(name) {
11302
11467
 
11303
11468
  // cli/util/scan-zone-drop.ts
11304
11469
  function dropScanZone(dbPath) {
11305
- const db = new DatabaseSync4(dbPath);
11470
+ const db = new DatabaseSync5(dbPath);
11306
11471
  try {
11307
11472
  const rows = db.prepare(
11308
11473
  "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'scan\\_%' ESCAPE '\\'"
@@ -11408,7 +11573,6 @@ var CONFIG_TEXTS = {
11408
11573
  listSectionScan: "Scan",
11409
11574
  listSectionJobs: "Jobs",
11410
11575
  listSectionRootsAndPlugins: "Roots & plugins",
11411
- listSectionHistory: "History",
11412
11576
  listSectionOther: "Other"
11413
11577
  };
11414
11578
 
@@ -11525,15 +11689,14 @@ var ConfigListCommand = class extends SmCommand {
11525
11689
  var SECTION_DEFS = [
11526
11690
  {
11527
11691
  title: CONFIG_TEXTS.listSectionGeneral,
11528
- exactKeys: ["autoMigrate", "schemaVersion", "tokenizer", "i18n.locale"]
11692
+ exactKeys: ["schemaVersion", "tokenizer"]
11529
11693
  },
11530
11694
  { title: CONFIG_TEXTS.listSectionScan, prefix: "scan.", stripPrefix: true },
11531
11695
  { title: CONFIG_TEXTS.listSectionJobs, prefix: "jobs.", stripPrefix: true },
11532
11696
  {
11533
11697
  title: CONFIG_TEXTS.listSectionRootsAndPlugins,
11534
- exactKeys: ["roots", "providers", "plugins", "ignore"]
11535
- },
11536
- { title: CONFIG_TEXTS.listSectionHistory, prefix: "history.", stripPrefix: true }
11698
+ exactKeys: ["roots", "plugins", "ignore"]
11699
+ }
11537
11700
  ];
11538
11701
  function renderConfigSections(rows, ansi) {
11539
11702
  const out = [];
@@ -11895,7 +12058,7 @@ var ConfigSetCommand = class extends SmCommand {
11895
12058
  announceLensSwitch(cwd, ansi) {
11896
12059
  const dbPath = resolveDbPath({ db: void 0, cwd });
11897
12060
  const okGlyph = ansi.green("\u2713");
11898
- if (!existsSync15(dbPath)) {
12061
+ if (!existsSync16(dbPath)) {
11899
12062
  this.printer.info(tx(CONFIG_TEXTS.lensSwitchedNoDb, { glyph: okGlyph }));
11900
12063
  return;
11901
12064
  }
@@ -11935,7 +12098,7 @@ var ConfigResetCommand = class extends SmCommand {
11935
12098
  const path = targetSettingsPath2(target, ctx.cwd);
11936
12099
  const ansi = this.ansiFor("stdout");
11937
12100
  const okGlyph = ansi.green("\u2713");
11938
- if (!existsSync15(path)) {
12101
+ if (!existsSync16(path)) {
11939
12102
  this.printer.data(
11940
12103
  tx(CONFIG_TEXTS.unsetNoOverride, {
11941
12104
  glyph: okGlyph,
@@ -12010,14 +12173,14 @@ var CONFIG_COMMANDS = [
12010
12173
  ];
12011
12174
 
12012
12175
  // cli/commands/conformance.ts
12013
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
12176
+ import { existsSync as existsSync19, readFileSync as readFileSync16 } from "fs";
12014
12177
  import { dirname as dirname12, resolve as resolve22 } from "path";
12015
12178
  import { fileURLToPath as fileURLToPath4 } from "url";
12016
12179
  import { Command as Command5, Option as Option5 } from "clipanion";
12017
12180
 
12018
12181
  // conformance/index.ts
12019
12182
  import { spawnSync as spawnSync2 } from "child_process";
12020
- 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";
12021
12184
  import { tmpdir } from "os";
12022
12185
  import { isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve20 } from "path";
12023
12186
 
@@ -12099,7 +12262,7 @@ function pickSafeEnv(source) {
12099
12262
  return out;
12100
12263
  }
12101
12264
  function runConformanceCase(options) {
12102
- const raw = readFileSync14(options.casePath, "utf8");
12265
+ const raw = readFileSync15(options.casePath, "utf8");
12103
12266
  const c = JSON.parse(raw);
12104
12267
  const fixturesRoot = options.fixturesRoot ?? join11(options.specRoot, "conformance", "fixtures");
12105
12268
  const safeId = c.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 32);
@@ -12214,7 +12377,7 @@ function evaluateAssertion(a, ctx) {
12214
12377
  return { ok: false, type: a.type, reason: formatErrorMessage(err) };
12215
12378
  }
12216
12379
  const abs = resolve20(ctx.scope, a.path);
12217
- return existsSync16(abs) ? { ok: true, type: a.type } : {
12380
+ return existsSync17(abs) ? { ok: true, type: a.type } : {
12218
12381
  ok: false,
12219
12382
  type: a.type,
12220
12383
  reason: tx(CONFORMANCE_RUNNER_TEXTS.fileNotFound, { path: a.path })
@@ -12229,15 +12392,15 @@ function evaluateAssertion(a, ctx) {
12229
12392
  }
12230
12393
  const fixturePath = join11(ctx.fixturesRoot, a.fixture);
12231
12394
  const targetPath = resolve20(ctx.scope, a.path);
12232
- if (!existsSync16(targetPath)) {
12395
+ if (!existsSync17(targetPath)) {
12233
12396
  return {
12234
12397
  ok: false,
12235
12398
  type: a.type,
12236
12399
  reason: tx(CONFORMANCE_RUNNER_TEXTS.targetNotFound, { path: a.path })
12237
12400
  };
12238
12401
  }
12239
- const needle = readFileSync14(fixturePath);
12240
- const haystack = readFileSync14(targetPath);
12402
+ const needle = readFileSync15(fixturePath);
12403
+ const haystack = readFileSync15(targetPath);
12241
12404
  return haystack.includes(needle) ? { ok: true, type: a.type } : {
12242
12405
  ok: false,
12243
12406
  type: a.type,
@@ -12410,7 +12573,7 @@ var CONFORMANCE_TEXTS = {
12410
12573
  };
12411
12574
 
12412
12575
  // cli/util/conformance-scopes.ts
12413
- 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";
12414
12577
  import { dirname as dirname11, resolve as resolve21 } from "path";
12415
12578
  import { createRequire as createRequire6 } from "module";
12416
12579
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -12430,7 +12593,7 @@ function resolveCliWorkspaceRoot() {
12430
12593
  let cursor = here;
12431
12594
  for (let depth = 0; depth < 6; depth += 1) {
12432
12595
  const candidate = resolve21(cursor, "plugins");
12433
- if (existsSync17(candidate) && statSync4(candidate).isDirectory()) {
12596
+ if (existsSync18(candidate) && statSync4(candidate).isDirectory()) {
12434
12597
  return cursor;
12435
12598
  }
12436
12599
  const parent = dirname11(cursor);
@@ -12450,32 +12613,32 @@ function collectProviderScopes(specRoot) {
12450
12613
  return out;
12451
12614
  }
12452
12615
  const pluginsRoot = resolve21(workspaceRoot, "plugins");
12453
- if (!existsSync17(pluginsRoot)) return out;
12454
- for (const bundleEntry of readdirSync6(pluginsRoot)) {
12455
- const bundleDir = resolve21(pluginsRoot, bundleEntry);
12456
- if (!isDir(bundleDir)) continue;
12457
- const providersRoot = resolve21(bundleDir, "providers");
12616
+ if (!existsSync18(pluginsRoot)) return out;
12617
+ for (const pluginEntry of readdirSync6(pluginsRoot)) {
12618
+ const pluginDir = resolve21(pluginsRoot, pluginEntry);
12619
+ if (!isDir(pluginDir)) continue;
12620
+ const providersRoot = resolve21(pluginDir, "providers");
12458
12621
  if (!isDir(providersRoot)) continue;
12459
- collectBundleProviderScopes(providersRoot, specRoot, out);
12622
+ collectPluginProviderScopes(providersRoot, specRoot, out);
12460
12623
  }
12461
12624
  return out;
12462
12625
  }
12463
12626
  function isDir(path) {
12464
12627
  try {
12465
- return existsSync17(path) && statSync4(path).isDirectory();
12628
+ return existsSync18(path) && statSync4(path).isDirectory();
12466
12629
  } catch {
12467
12630
  return false;
12468
12631
  }
12469
12632
  }
12470
- function collectBundleProviderScopes(providersRoot, specRoot, out) {
12633
+ function collectPluginProviderScopes(providersRoot, specRoot, out) {
12471
12634
  for (const entry of readdirSync6(providersRoot)) {
12472
12635
  const providerDir = resolve21(providersRoot, entry);
12473
12636
  if (!isDir(providerDir)) continue;
12474
12637
  const conformanceDir = resolve21(providerDir, "conformance");
12475
- if (!existsSync17(conformanceDir)) continue;
12638
+ if (!existsSync18(conformanceDir)) continue;
12476
12639
  const casesDir = resolve21(conformanceDir, "cases");
12477
12640
  const fixturesDir = resolve21(conformanceDir, "fixtures");
12478
- if (!existsSync17(casesDir) || !existsSync17(fixturesDir)) continue;
12641
+ if (!existsSync18(casesDir) || !existsSync18(fixturesDir)) continue;
12479
12642
  out.push({
12480
12643
  id: `provider:${entry}`,
12481
12644
  kind: "provider",
@@ -12513,7 +12676,7 @@ function selectConformanceScopes(scope) {
12513
12676
  return [match];
12514
12677
  }
12515
12678
  function listCaseFiles(scope) {
12516
- if (!existsSync17(scope.casesDir)) return [];
12679
+ if (!existsSync18(scope.casesDir)) return [];
12517
12680
  return readdirSync6(scope.casesDir).filter((entry) => entry.endsWith(".json")).sort().map((entry) => resolve21(scope.casesDir, entry));
12518
12681
  }
12519
12682
 
@@ -12532,7 +12695,7 @@ function resolveBinary() {
12532
12695
  let cursor = here;
12533
12696
  for (let depth = 0; depth < 6; depth += 1) {
12534
12697
  const candidate = resolve22(cursor, "bin", "sm.js");
12535
- if (existsSync18(candidate)) return candidate;
12698
+ if (existsSync19(candidate)) return candidate;
12536
12699
  const parent = dirname12(cursor);
12537
12700
  if (parent === cursor) break;
12538
12701
  cursor = parent;
@@ -12598,7 +12761,7 @@ var ConformanceRunCommand = class extends SmCommand {
12598
12761
  return ExitCode.Error;
12599
12762
  }
12600
12763
  const binary = resolveBinary();
12601
- if (!existsSync18(binary)) {
12764
+ if (!existsSync19(binary)) {
12602
12765
  if (this.json) {
12603
12766
  this.#emitJsonError(
12604
12767
  "internal",
@@ -12772,7 +12935,7 @@ function projectAssertionFailures(assertions) {
12772
12935
  }
12773
12936
  function readCaseId(casePath) {
12774
12937
  try {
12775
- const raw = readFileSync15(casePath, "utf8");
12938
+ const raw = readFileSync16(casePath, "utf8");
12776
12939
  const parsed = JSON.parse(raw);
12777
12940
  if (typeof parsed.id === "string") return parsed.id;
12778
12941
  } catch {
@@ -13027,18 +13190,18 @@ var DbRestoreCommand = class extends SmCommand {
13027
13190
  };
13028
13191
 
13029
13192
  // cli/commands/db/reset.ts
13030
- import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
13193
+ import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
13031
13194
  import { Command as Command8, Option as Option8 } from "clipanion";
13032
13195
 
13033
13196
  // core/sqlite/db-files.ts
13034
- import { existsSync as existsSync19 } from "fs";
13197
+ import { existsSync as existsSync20 } from "fs";
13035
13198
  import { rm as rm2 } from "fs/promises";
13036
13199
  var DB_FILE_SUFFIXES = ["", "-wal", "-shm"];
13037
13200
  async function removeDbFiles(dbPath) {
13038
13201
  if (dbPath === ":memory:") return;
13039
13202
  for (const suffix of DB_FILE_SUFFIXES) {
13040
13203
  const p = `${dbPath}${suffix}`;
13041
- if (existsSync19(p)) await rm2(p);
13204
+ if (existsSync20(p)) await rm2(p);
13042
13205
  }
13043
13206
  }
13044
13207
 
@@ -13124,7 +13287,7 @@ var DbResetCommand = class extends SmCommand {
13124
13287
  return ExitCode.Error;
13125
13288
  }
13126
13289
  }
13127
- const db = new DatabaseSync5(path);
13290
+ const db = new DatabaseSync6(path);
13128
13291
  try {
13129
13292
  const rows = db.prepare(
13130
13293
  "SELECT name FROM sqlite_master WHERE type='table' AND (name LIKE 'scan\\_%' ESCAPE '\\'" + (this.state ? " OR name LIKE 'state\\_%' ESCAPE '\\'" : "") + ")"
@@ -13267,7 +13430,7 @@ var DbBrowserCommand = class extends SmCommand {
13267
13430
  };
13268
13431
 
13269
13432
  // cli/commands/db/dump.ts
13270
- import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
13433
+ import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
13271
13434
  import { Command as Command11, Option as Option10 } from "clipanion";
13272
13435
  var DbDumpCommand = class extends SmCommand {
13273
13436
  static paths = [["db", "dump"]];
@@ -13309,7 +13472,7 @@ var DbDumpCommand = class extends SmCommand {
13309
13472
  }
13310
13473
  };
13311
13474
  function dumpDatabaseToStream(dbPath, out, tables) {
13312
- const db = new DatabaseSync6(dbPath, { readOnly: true });
13475
+ const db = new DatabaseSync7(dbPath, { readOnly: true });
13313
13476
  try {
13314
13477
  out.write("PRAGMA foreign_keys=OFF;\n");
13315
13478
  out.write("BEGIN TRANSACTION;\n");
@@ -14157,7 +14320,7 @@ var GraphCommand = class extends SmCommand {
14157
14320
  };
14158
14321
 
14159
14322
  // cli/commands/help.ts
14160
- import { readFileSync as readFileSync16 } from "fs";
14323
+ import { readFileSync as readFileSync17 } from "fs";
14161
14324
  import { createRequire as createRequire7 } from "module";
14162
14325
  import { resolve as resolve26 } from "path";
14163
14326
  import { Command as Command15, Option as Option14 } from "clipanion";
@@ -14214,6 +14377,22 @@ var HELP_TEXTS = {
14214
14377
  /** Trailing fragment for `humanFlagRow`'s `{{required}}` slot. */
14215
14378
  humanFlagRowRequiredFragment: " (required)",
14216
14379
  humanFooter: "Run `sm help {{name}} --format md` for the full reference.",
14380
+ // --- human group renderer (sm <namespace> --help, sm help <namespace>) ---
14381
+ /**
14382
+ * USAGE row for a command namespace (a prefix that owns subcommands but
14383
+ * is not itself a runnable verb, e.g. `plugins`, `db`). Mirrors
14384
+ * `humanUsageRow` but advertises the `<command>` slot instead of
14385
+ * positionals.
14386
+ */
14387
+ humanGroupUsageRow: " sm {{name}} <command> [options]",
14388
+ /** Section heading listing the subcommands of a namespace. */
14389
+ humanCommandsHeading: "COMMANDS",
14390
+ /** Aligned subcommand row; `{{name}}` is the subcommand relative to the namespace. */
14391
+ humanCommandRow: " {{name}}{{padding}} {{description}}",
14392
+ /** Footer for the namespace overview, points at per-subcommand help. */
14393
+ humanGroupFooter: "Run `sm {{name}} <command> --help` for flags and arguments.",
14394
+ /** Fallback header description when a namespace has no curated entry in `HELP_GROUPS`. */
14395
+ groupFallbackDescription: "{{category}} commands",
14217
14396
  // --- human compact overview (sm / sm --help / sm help, no verb) ---------
14218
14397
  /**
14219
14398
  * Compact-overview header. Replaces the Clipanion default ANSI banner.
@@ -14244,6 +14423,40 @@ var HELP_TEXTS = {
14244
14423
  compactExampleRow: " {{command}}{{padding}} {{description}}",
14245
14424
  compactFooter: "Run `sm <command> --help` for flags and arguments."
14246
14425
  };
14426
+ var HELP_GROUPS = {
14427
+ plugins: {
14428
+ description: "Discover, inspect, and toggle plugins",
14429
+ details: "A plugin is a directory of extensions (extractors, analyzers, actions,\nhooks, formatters, providers) discovered under the project plugins dir.\n\nUse `list` and `show` to inspect what loaded, `doctor` to diagnose load\nfailures, `enable` / `disable` to toggle extensions (persisted in the DB),\nand `create` / `upgrade` to scaffold and migrate your own."
14430
+ },
14431
+ config: {
14432
+ description: "Read and write project configuration",
14433
+ details: "Configuration is a layered merge: library defaults, the committed\n`settings.json`, the gitignored `settings.local.json`, env vars, then\nCLI flags, with later layers winning.\n\nUse `list` / `get` to read the effective values, `show --source` to see\nwhich layer set a key, and `set` / `reset` to write or revert one.\nPrivacy-sensitive keys (paths outside the project) require `--yes`."
14434
+ },
14435
+ db: {
14436
+ description: "Inspect and maintain the project database",
14437
+ details: "The project database is a single SQLite file at\n`.skill-map/skill-map.db`, holding the scan graph and plugin state.\n\nUse `backup` / `restore` around risky operations, `migrate` to apply\npending kernel and plugin migrations, `reset` to drop tables (or the\nwhole file), and `dump` / `shell` / `browser` to inspect the data."
14438
+ },
14439
+ job: {
14440
+ description: "Manage the background job queue",
14441
+ details: "Probabilistic and long-running work runs as jobs: queued, persisted in\nthe database, and resumable across restarts.\n\nUse `submit` to enqueue (or `--all` to fan out across nodes), `run` to\nexecute the claim-spawn-record loop, `status` / `list` / `show` to\ninspect, `preview` to render a job without executing it, and\n`cancel` / `prune` to clean up."
14442
+ },
14443
+ actions: {
14444
+ description: "Inspect the registered Action catalog",
14445
+ details: "An Action operates on one or more nodes and is either deterministic\n(in-process code) or probabilistic (a rendered prompt a runner executes).\n\nUse `list` for the catalog of registered action types and `show` for a\nsingle action's full manifest, including its preconditions and expected\nduration."
14446
+ },
14447
+ sidecar: {
14448
+ description: "Manage `.sm` annotation sidecars",
14449
+ details: "Skill-map's annotation layer lives in co-located `.sm` YAML sidecars\nnext to each node, leaving the vendor file untouched.\n\nUse `annotate` to scaffold an empty sidecar ready for editing, `refresh`\nto realign its drift hashes with the live node, and `prune` to delete\nsidecars whose `.md` no longer exists."
14450
+ },
14451
+ hooks: {
14452
+ description: "Install git hooks for sidecar drift",
14453
+ details: "Git hooks keep your sidecars in sync with the repo as you commit.\n\n`install` writes a pre-commit hook that auto-bumps staged sidecar drift\nbefore each commit."
14454
+ },
14455
+ conformance: {
14456
+ description: "Run the spec conformance suite",
14457
+ details: "The conformance suite checks an implementation against the spec.\n\n`run` executes the spec-owned cases plus every built-in Provider and\nreports the results."
14458
+ }
14459
+ };
14247
14460
 
14248
14461
  // cli/commands/help.ts
14249
14462
  var HelpCommand = class extends Command15 {
@@ -14282,18 +14495,23 @@ var HelpCommand = class extends Command15 {
14282
14495
  const verb = this.verbParts.join(" ").trim();
14283
14496
  if (verb) {
14284
14497
  const target = verbs.find((v) => v.name === verb);
14285
- if (!target) {
14286
- this.context.stderr.write(
14287
- tx(HELP_TEXTS.unknownVerb, {
14288
- glyph: errGlyph,
14289
- verb,
14290
- hint: ansi.dim(HELP_TEXTS.unknownVerbHint)
14291
- })
14292
- );
14293
- return ExitCode.NotFound;
14498
+ if (target) {
14499
+ this.context.stdout.write(renderSingle(target, format));
14500
+ return ExitCode.Ok;
14294
14501
  }
14295
- this.context.stdout.write(renderSingle(target, format));
14296
- return ExitCode.Ok;
14502
+ const subcommands = verbs.filter((v) => v.name.startsWith(verb + " "));
14503
+ if (subcommands.length > 0) {
14504
+ this.context.stdout.write(renderGroup(verb, subcommands, format));
14505
+ return ExitCode.Ok;
14506
+ }
14507
+ this.context.stderr.write(
14508
+ tx(HELP_TEXTS.unknownVerb, {
14509
+ glyph: errGlyph,
14510
+ verb,
14511
+ hint: ansi.dim(HELP_TEXTS.unknownVerbHint)
14512
+ })
14513
+ );
14514
+ return ExitCode.NotFound;
14297
14515
  }
14298
14516
  if (format === "human") {
14299
14517
  this.context.stdout.write(renderCompactOverview(verbs));
@@ -14403,7 +14621,7 @@ function resolveSpecVersion() {
14403
14621
  const req = createRequire7(import.meta.url);
14404
14622
  const indexPath = req.resolve("@skill-map/spec/index.json");
14405
14623
  const pkgPath = resolve26(indexPath, "..", "package.json");
14406
- const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
14624
+ const pkg = JSON.parse(readFileSync17(pkgPath, "utf8"));
14407
14625
  return pkg.version;
14408
14626
  } catch {
14409
14627
  return "unknown";
@@ -14480,6 +14698,51 @@ function renderSingle(verb, format) {
14480
14698
  }
14481
14699
  return renderSingleHuman(verb);
14482
14700
  }
14701
+ function renderGroup(group, subcommands, format) {
14702
+ if (format === "json" || format === "md") {
14703
+ const doc = {
14704
+ cliVersion: VERSION,
14705
+ specVersion: resolveSpecVersion(),
14706
+ globalFlags: [],
14707
+ verbs: subcommands
14708
+ };
14709
+ return format === "json" ? JSON.stringify(doc, null, 2) + "\n" : renderMarkdown2(doc);
14710
+ }
14711
+ return renderGroupHuman(group, subcommands);
14712
+ }
14713
+ function renderGroupHuman(group, subcommands) {
14714
+ const meta = HELP_GROUPS[group];
14715
+ const description = meta?.description ?? tx(HELP_TEXTS.groupFallbackDescription, { category: subcommands[0]?.category ?? "Other" });
14716
+ const out = [];
14717
+ out.push(tx(HELP_TEXTS.humanVerbHeader, { name: group, description }));
14718
+ out.push("");
14719
+ out.push(HELP_TEXTS.humanUsageHeading);
14720
+ out.push(tx(HELP_TEXTS.humanGroupUsageRow, { name: group }));
14721
+ if (meta?.details) out.push(...renderHumanDescription(meta.details));
14722
+ out.push(...renderGroupCommands(group, subcommands));
14723
+ out.push("");
14724
+ out.push(tx(HELP_TEXTS.humanGroupFooter, { name: group }));
14725
+ return out.join("\n") + "\n";
14726
+ }
14727
+ function renderGroupCommands(group, subcommands) {
14728
+ const out = ["", HELP_TEXTS.humanCommandsHeading];
14729
+ const sorted = [...subcommands].sort((a, b) => a.name.localeCompare(b.name));
14730
+ const labels = sorted.map((v) => v.name.slice(group.length + 1));
14731
+ const width = Math.max(...labels.map((l) => l.length));
14732
+ for (let i = 0; i < sorted.length; i++) {
14733
+ const verb = sorted[i];
14734
+ const label = labels[i];
14735
+ const { isStub, clean } = classifyDescription(verb.description);
14736
+ const description = (isStub ? HELP_TEXTS.compactStubMarker : "") + firstSentence(clean);
14737
+ const row = tx(HELP_TEXTS.humanCommandRow, {
14738
+ name: label,
14739
+ padding: padRight("", width - label.length),
14740
+ description
14741
+ });
14742
+ out.push(truncate2(row, COMPACT_ROW_MAX));
14743
+ }
14744
+ return out;
14745
+ }
14483
14746
  function renderSingleHuman(verb) {
14484
14747
  const out = [];
14485
14748
  out.push(buildHumanHeader(verb));
@@ -14611,7 +14874,7 @@ function routeHelpArgs(args2, cli2) {
14611
14874
  if (!shouldRouteHelp(args2)) return args2;
14612
14875
  const leading = leadingPositionals(args2);
14613
14876
  if (leading.length === 0) return args2;
14614
- const verbPath = longestVerbPrefix(leading, registeredVerbPaths(cli2));
14877
+ const verbPath = longestVerbOrGroupPrefix(leading, registeredVerbPaths(cli2));
14615
14878
  if (verbPath.length === 0) return args2;
14616
14879
  return ["help", ...verbPath];
14617
14880
  }
@@ -14630,14 +14893,15 @@ function leadingPositionals(args2) {
14630
14893
  }
14631
14894
  return out;
14632
14895
  }
14633
- function longestVerbPrefix(positionals, verbPaths) {
14634
- let best = [];
14635
- for (const path of verbPaths) {
14636
- if (path.length > positionals.length) continue;
14637
- const matches = path.every((tok, i) => positionals[i] === tok);
14638
- if (matches && path.length > best.length) best = path;
14896
+ function longestVerbOrGroupPrefix(positionals, verbPaths) {
14897
+ for (let len = positionals.length; len >= 1; len--) {
14898
+ const prefix = positionals.slice(0, len);
14899
+ const matches = verbPaths.some(
14900
+ (path) => path.length >= len && prefix.every((tok, i) => path[i] === tok)
14901
+ );
14902
+ if (matches) return prefix;
14639
14903
  }
14640
- return best;
14904
+ return [];
14641
14905
  }
14642
14906
  function registeredVerbPaths(cli2) {
14643
14907
  const rawDefs = cli2.definitions();
@@ -14842,10 +15106,9 @@ import { join as join17 } from "path";
14842
15106
  import { Command as Command17, Option as Option16 } from "clipanion";
14843
15107
 
14844
15108
  // kernel/orchestrator/index.ts
14845
- import { existsSync as existsSync22, statSync as statSync6 } from "fs";
15109
+ import { existsSync as existsSync23, statSync as statSync6 } from "fs";
14846
15110
  import { isAbsolute as isAbsolute7, resolve as resolve28 } from "path";
14847
15111
  import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
14848
- import cl100k_base from "js-tiktoken/ranks/cl100k_base";
14849
15112
 
14850
15113
  // kernel/i18n/orchestrator.texts.ts
14851
15114
  var ORCHESTRATOR_TEXTS = {
@@ -15889,7 +16152,7 @@ function computeDriftStatus(args2) {
15889
16152
  }
15890
16153
 
15891
16154
  // kernel/sidecar/discover-orphans.ts
15892
- 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";
15893
16156
  import { join as join13, relative as relative4, sep as sep4 } from "path";
15894
16157
  function discoverOrphanSidecars(roots, shouldSkip) {
15895
16158
  const out = [];
@@ -15917,7 +16180,7 @@ function walk2(root, current, shouldSkip, out) {
15917
16180
  if (!entry.isFile()) continue;
15918
16181
  if (!entry.name.endsWith(".sm")) continue;
15919
16182
  const expectedMd = `${full.slice(0, -".sm".length)}.md`;
15920
- if (existsSync20(expectedMd) && safeIsFile(expectedMd)) continue;
16183
+ if (existsSync21(expectedMd) && safeIsFile(expectedMd)) continue;
15921
16184
  out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
15922
16185
  }
15923
16186
  }
@@ -15930,8 +16193,8 @@ function safeIsFile(path) {
15930
16193
  }
15931
16194
 
15932
16195
  // kernel/orchestrator/node-build.ts
15933
- import { createHash } from "crypto";
15934
- import { existsSync as existsSync21 } from "fs";
16196
+ import { createHash as createHash2 } from "crypto";
16197
+ import { existsSync as existsSync22 } from "fs";
15935
16198
  import { isAbsolute as isAbsolute6, resolve as resolvePath } from "path";
15936
16199
  import "js-tiktoken/lite";
15937
16200
  import yaml4 from "js-yaml";
@@ -16018,7 +16281,7 @@ function countTokens(encoder, frontmatterRaw, body) {
16018
16281
  return { frontmatter, body: bodyTokens, total: frontmatter + bodyTokens };
16019
16282
  }
16020
16283
  function sha256(input) {
16021
- return createHash("sha256").update(input, "utf8").digest("hex");
16284
+ return createHash2("sha256").update(input, "utf8").digest("hex");
16022
16285
  }
16023
16286
  function canonicalFrontmatter(parsed, raw) {
16024
16287
  const hasParsedKeys = Object.keys(parsed).length > 0;
@@ -16095,11 +16358,11 @@ function resolveSidecarOverlay(relativePath2, nodePathForIssue, roots, liveBodyH
16095
16358
  }
16096
16359
  function resolveAbsoluteMdPath(relativePath2, roots) {
16097
16360
  if (isAbsolute6(relativePath2)) {
16098
- return existsSync21(relativePath2) ? relativePath2 : null;
16361
+ return existsSync22(relativePath2) ? relativePath2 : null;
16099
16362
  }
16100
16363
  for (const root of roots) {
16101
16364
  const candidate = resolvePath(root, relativePath2);
16102
- if (existsSync21(candidate)) return candidate;
16365
+ if (existsSync22(candidate)) return candidate;
16103
16366
  }
16104
16367
  return null;
16105
16368
  }
@@ -16157,7 +16420,18 @@ async function walkAndExtract(opts) {
16157
16420
  const accum = createWalkAccumulators();
16158
16421
  const wctx = buildWalkContext(opts);
16159
16422
  const claimedPaths = /* @__PURE__ */ new Set();
16160
- const walkOptions = opts.ignoreFilter ? { ignoreFilter: opts.ignoreFilter } : {};
16423
+ const oversizedFiles = [];
16424
+ const oversizedSeen = /* @__PURE__ */ new Set();
16425
+ const onOversizedFile = (info) => {
16426
+ if (oversizedSeen.has(info.path)) return;
16427
+ oversizedSeen.add(info.path);
16428
+ oversizedFiles.push(info);
16429
+ };
16430
+ const walkOptions = {
16431
+ ...opts.ignoreFilter ? { ignoreFilter: opts.ignoreFilter } : {},
16432
+ onOversizedFile,
16433
+ ...opts.maxFileSizeBytes !== void 0 ? { maxFileSizeBytes: opts.maxFileSizeBytes } : {}
16434
+ };
16161
16435
  let filesWalked = 0;
16162
16436
  let index = 0;
16163
16437
  const effectiveMaxNodes = opts.overrideMaxNodes ?? opts.recommendedNodeLimit;
@@ -16190,6 +16464,7 @@ async function walkAndExtract(opts) {
16190
16464
  cachedPaths: accum.cachedPaths,
16191
16465
  frontmatterIssues: accum.frontmatterIssues,
16192
16466
  filesWalked,
16467
+ oversizedFiles,
16193
16468
  recommendedNodeLimit: opts.recommendedNodeLimit,
16194
16469
  overrideMaxNodes: opts.overrideMaxNodes,
16195
16470
  capReached,
@@ -16240,7 +16515,11 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
16240
16515
  }
16241
16516
  claimedPaths.add(raw.path);
16242
16517
  const priorNode = wctx.priorNodesByPath.get(raw.path);
16243
- 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;
16244
16523
  const sidecarResolution = resolveSidecarOverlay(
16245
16524
  raw.path,
16246
16525
  raw.path,
@@ -16442,6 +16721,16 @@ function resolveSpecVersionSafe() {
16442
16721
  return "unknown";
16443
16722
  }
16444
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
+ }
16445
16734
  async function runScanWithRenames(_kernel, options) {
16446
16735
  return runScanInternal(_kernel, options);
16447
16736
  }
@@ -16451,7 +16740,7 @@ async function runScan(_kernel, options) {
16451
16740
  }
16452
16741
  async function runScanInternal(_kernel, options) {
16453
16742
  validateRoots(options.roots);
16454
- const setup = buildScanSetup(options);
16743
+ const setup = await buildScanSetup(options);
16455
16744
  const { emitter, exts, hookDispatcher, encoder, prior, start } = setup;
16456
16745
  const scanStartedEvent = makeEvent("scan.started", { roots: options.roots });
16457
16746
  emitter.emit(scanStartedEvent);
@@ -16461,6 +16750,7 @@ async function runScanInternal(_kernel, options) {
16461
16750
  options.roots,
16462
16751
  exts.providers
16463
16752
  );
16753
+ const tokenizerChanged = encoder !== null && prior !== null && prior.tokenizer !== setup.tokenizer;
16464
16754
  const walked = await walkAndExtract({
16465
16755
  providers: exts.providers,
16466
16756
  extractors: exts.extractors,
@@ -16470,6 +16760,7 @@ async function runScanInternal(_kernel, options) {
16470
16760
  encoder,
16471
16761
  strict: setup.strict,
16472
16762
  enableCache: setup.enableCache,
16763
+ tokenizerChanged,
16473
16764
  prior,
16474
16765
  priorIndex: setup.priorIndex,
16475
16766
  priorExtractorRuns: setup.priorExtractorRuns,
@@ -16477,7 +16768,8 @@ async function runScanInternal(_kernel, options) {
16477
16768
  pluginStores: options.pluginStores,
16478
16769
  activeProvider: activeProviderId,
16479
16770
  recommendedNodeLimit: options.recommendedNodeLimit ?? 256,
16480
- overrideMaxNodes: options.overrideMaxNodes ?? null
16771
+ overrideMaxNodes: options.overrideMaxNodes ?? null,
16772
+ ...options.maxFileSizeBytes !== void 0 ? { maxFileSizeBytes: options.maxFileSizeBytes } : {}
16481
16773
  });
16482
16774
  const activeProvider = activeProviderId ? exts.providers.find((p) => p.id === activeProviderId) ?? null : null;
16483
16775
  const resolved = resolveSignals({
@@ -16584,13 +16876,14 @@ function buildReservedNodePaths(nodes, kindRegistry, reservedNamesByProviderKind
16584
16876
  function hasEntries(set) {
16585
16877
  return set !== void 0 && set.size > 0;
16586
16878
  }
16587
- function buildScanSetup(options) {
16879
+ async function buildScanSetup(options) {
16588
16880
  const start = Date.now();
16589
16881
  const emitter = options.emitter ?? new InMemoryProgressEmitter();
16590
16882
  const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
16591
16883
  const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
16592
16884
  const tokenize = options.tokenize !== false;
16593
- const encoder = tokenize ? new Tiktoken2(cl100k_base) : null;
16885
+ const tokenizer = resolveTokenizerName(options.tokenizer);
16886
+ const encoder = tokenize ? new Tiktoken2(await loadTokenizerRanks(tokenizer)) : null;
16594
16887
  const prior = options.priorSnapshot ?? null;
16595
16888
  const priorIndex = indexPriorSnapshot(prior);
16596
16889
  const providerFrontmatter = buildProviderFrontmatterValidator(exts.providers);
@@ -16601,6 +16894,7 @@ function buildScanSetup(options) {
16601
16894
  exts,
16602
16895
  hookDispatcher,
16603
16896
  encoder,
16897
+ tokenizer,
16604
16898
  prior,
16605
16899
  priorIndex,
16606
16900
  priorExtractorRuns: options.priorExtractorRuns,
@@ -16636,6 +16930,7 @@ function buildScanStats(walked, issues, start) {
16636
16930
  // Providers compete.
16637
16931
  filesWalked: walked.filesWalked,
16638
16932
  filesSkipped: 0,
16933
+ filesOversized: walked.oversizedFiles.length,
16639
16934
  nodesCount: walked.nodes.length,
16640
16935
  linksCount: walked.internalLinks.length,
16641
16936
  issuesCount: issues.length,
@@ -16650,8 +16945,10 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
16650
16945
  roots: options.roots,
16651
16946
  providers: setup.exts.providers.map((a) => a.id),
16652
16947
  scannedBy: SCANNED_BY,
16948
+ tokenizer: setup.tokenizer,
16653
16949
  recommendedNodeLimit: walked.recommendedNodeLimit,
16654
16950
  overrideMaxNodes: walked.overrideMaxNodes,
16951
+ oversizedFiles: walked.oversizedFiles,
16655
16952
  nodes: walked.nodes,
16656
16953
  links: walked.internalLinks,
16657
16954
  issues,
@@ -16669,7 +16966,7 @@ function validateRoots(roots) {
16669
16966
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
16670
16967
  }
16671
16968
  for (const root of roots) {
16672
- if (!existsSync22(root) || !statSync6(root).isDirectory()) {
16969
+ if (!existsSync23(root) || !statSync6(root).isDirectory()) {
16673
16970
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
16674
16971
  }
16675
16972
  }
@@ -16678,7 +16975,7 @@ function resolveActiveProviderOption(optionValue, roots, providers) {
16678
16975
  if (optionValue !== void 0) return optionValue;
16679
16976
  for (const root of roots) {
16680
16977
  const absRoot = isAbsolute7(root) ? root : resolve28(root);
16681
- if (!existsSync22(absRoot)) continue;
16978
+ if (!existsSync23(absRoot)) continue;
16682
16979
  const detected = resolveActiveProvider(absRoot, providers).resolved;
16683
16980
  if (detected !== null) return detected;
16684
16981
  }
@@ -17047,13 +17344,13 @@ var SCAN_RUNNER_TEXTS = {
17047
17344
  activeProviderAmbiguousUnderYes: "{{glyph}} Multiple provider markers detected ({{candidates}}) and --yes is set.\n {{hint}}\n",
17048
17345
  activeProviderAmbiguousUnderYesHint: "Set the lens explicitly with `sm config set activeProvider <id>` and re-run, or omit --yes for interactive selection.",
17049
17346
  /**
17050
- * Active lens points at a bundle the operator has disabled (via
17347
+ * Active lens points at a plugin the operator has disabled (via
17051
17348
  * `sm plugins disable <id>` or the Settings UI). Classification keeps
17052
17349
  * running because it's provider-driven, but the lens-gated extractors
17053
- * for the disabled bundle silently no-op. Without this warning the
17350
+ * for the disabled plugin silently no-op. Without this warning the
17054
17351
  * graph quietly differs from what the lens implies.
17055
17352
  */
17056
- activeProviderBundleDisabledWarning: 'activeProvider = "{{id}}" but the "{{id}}" plugin bundle is currently disabled; provider-specific extractors will not run. Re-enable the bundle with `sm plugins enable {{id}}` or switch the lens with `sm config set activeProvider <id>` to silence this warning.',
17353
+ activeProviderPluginDisabledWarning: 'activeProvider = "{{id}}" but the "{{id}}" plugin is currently disabled; provider-specific extractors will not run. Re-enable the plugin with `sm plugins enable {{id}}` or switch the lens with `sm config set activeProvider <id>` to silence this warning.',
17057
17354
  /**
17058
17355
  * Active-provider drift: the snapshot of provider markers persisted
17059
17356
  * when `activeProvider` was set (`activeProviderMarkers`) no longer
@@ -17222,7 +17519,6 @@ function persistActiveProvider(cwd, id, markers, printer) {
17222
17519
  target: "project",
17223
17520
  cwd
17224
17521
  });
17225
- printer.info(tx(SCAN_RUNNER_TEXTS.activeProviderAutodetected, { id }));
17226
17522
  } catch (err) {
17227
17523
  const message = err instanceof Error ? err.message : String(err);
17228
17524
  printer.warn(
@@ -17279,11 +17575,11 @@ function diffMarkers(snapshot, current) {
17279
17575
  }
17280
17576
  return { added, removed };
17281
17577
  }
17282
- function warnIfLensBundleDisabled(args2) {
17578
+ function warnIfLensPluginDisabled(args2) {
17283
17579
  if (args2.activeProvider === null) return;
17284
17580
  if (args2.resolveEnabled(args2.activeProvider)) return;
17285
17581
  args2.printer.warn(
17286
- tx(SCAN_RUNNER_TEXTS.activeProviderBundleDisabledWarning, {
17582
+ tx(SCAN_RUNNER_TEXTS.activeProviderPluginDisabledWarning, {
17287
17583
  id: args2.activeProvider
17288
17584
  })
17289
17585
  );
@@ -17319,45 +17615,61 @@ async function promptForLens(detected, stdin, stderr, warnGlyph) {
17319
17615
  }
17320
17616
 
17321
17617
  // core/sqlite/db-drift-reset.ts
17322
- import { existsSync as existsSync23 } from "fs";
17618
+ import { existsSync as existsSync24 } from "fs";
17323
17619
  import { createInterface as createInterface4 } from "readline";
17324
- import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
17620
+ import { DatabaseSync as DatabaseSync8 } from "node:sqlite";
17325
17621
 
17326
17622
  // core/sqlite/i18n/db-drift.texts.ts
17327
17623
  var DB_DRIFT_TEXTS = {
17328
17624
  // Interactive confirm (TTY `sm scan`, no `--yes`). The block is
17329
17625
  // written to stderr, then the question line drives `readline`.
17330
- driftPrompt: "{{glyph}} The local cache was built by skill-map {{dbVersion}} and you are on {{currentVersion}}.\n {{hint}}\n",
17331
- 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.",
17332
17631
  driftPromptQuestion: "Delete the local cache and rebuild it on this scan? [y/N] ",
17333
17632
  // Receipt after the rebuild (printed by the scan / refresh path).
17334
- 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",
17335
17634
  driftResetHint: "The DB was deleted and is being regenerated by this scan; .sm sidecars were not touched.",
17336
17635
  // Abort headline when the operator declines (wrapped by the caller's
17337
17636
  // `sm scan: {message}` shell, so it carries no glyph / verb prefix).
17338
- driftAborted: "cache rebuild declined: the {{dbVersion}} cache cannot be reused on {{currentVersion}}. {{hint}}",
17339
- 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"
17340
17645
  };
17341
17646
 
17342
17647
  // core/sqlite/db-drift-reset.ts
17343
17648
  async function maybeResetOnDrift(dbPath, policy) {
17344
- const dbVersion = readScannedByVersion(dbPath);
17345
- if (dbVersion === null) return { kind: "no-drift" };
17346
- const skew = classifyVersionSkew(dbVersion, policy.currentVersion);
17347
- if (skew.kind === "ok" || skew.kind === "no-meta") return { kind: "no-drift" };
17348
- 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);
17349
17653
  if (!confirmed) {
17350
- return { kind: "aborted", dbVersion, currentVersion: policy.currentVersion };
17654
+ return { kind: "aborted", dbVersion, currentVersion: policy.currentVersion, reason };
17351
17655
  }
17352
17656
  await removeDbFiles(dbPath);
17353
- renderResetReceipt(dbVersion, policy);
17354
- 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;
17355
17667
  }
17356
17668
  function readScannedByVersion(dbPath) {
17357
- if (dbPath === ":memory:" || !existsSync23(dbPath)) return null;
17669
+ if (dbPath === ":memory:" || !existsSync24(dbPath)) return null;
17358
17670
  let raw = null;
17359
17671
  try {
17360
- raw = new DatabaseSync7(dbPath, { readOnly: true });
17672
+ raw = new DatabaseSync8(dbPath, { readOnly: true });
17361
17673
  const row = raw.prepare("SELECT scanned_by_version AS v FROM scan_meta LIMIT 1").get();
17362
17674
  const v = row?.v;
17363
17675
  return typeof v === "string" && v.length > 0 ? v : null;
@@ -17367,16 +17679,19 @@ function readScannedByVersion(dbPath) {
17367
17679
  raw?.close();
17368
17680
  }
17369
17681
  }
17370
- async function confirmDriftReset(dbVersion, policy) {
17682
+ async function confirmDriftReset(dbVersion, reason, policy) {
17371
17683
  if (!shouldPromptForReset(policy)) return true;
17372
- 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;
17373
17688
  }
17374
17689
  function shouldPromptForReset(policy) {
17375
17690
  if (policy.assumeYes) return false;
17376
17691
  if (!policy.stdin || !policy.stderr) return false;
17377
17692
  return policy.stdin.isTTY === true;
17378
17693
  }
17379
- async function askDriftReset(dbVersion, policy) {
17694
+ async function askDriftReset(dbVersion, reason, policy) {
17380
17695
  const warnGlyph = policy.style?.warnGlyph ?? "\u26A0";
17381
17696
  const dim = policy.style?.dim ?? ((s) => s);
17382
17697
  policy.stderr.write(
@@ -17384,6 +17699,7 @@ async function askDriftReset(dbVersion, policy) {
17384
17699
  glyph: warnGlyph,
17385
17700
  dbVersion,
17386
17701
  currentVersion: policy.currentVersion,
17702
+ reason: reasonText(reason),
17387
17703
  hint: dim(DB_DRIFT_TEXTS.driftPromptHint)
17388
17704
  })
17389
17705
  );
@@ -17397,7 +17713,7 @@ async function askDriftReset(dbVersion, policy) {
17397
17713
  rl.close();
17398
17714
  }
17399
17715
  }
17400
- function renderResetReceipt(dbVersion, policy) {
17716
+ function renderResetReceipt(dbVersion, reason, policy) {
17401
17717
  if (!policy.printer) return;
17402
17718
  const warnGlyph = policy.style?.warnGlyph ?? "\u26A0";
17403
17719
  const dim = policy.style?.dim ?? ((s) => s);
@@ -17406,6 +17722,7 @@ function renderResetReceipt(dbVersion, policy) {
17406
17722
  glyph: warnGlyph,
17407
17723
  dbVersion,
17408
17724
  currentVersion: policy.currentVersion,
17725
+ reason: reasonText(reason),
17409
17726
  hint: dim(DB_DRIFT_TEXTS.driftResetHint)
17410
17727
  })
17411
17728
  );
@@ -17448,10 +17765,13 @@ async function runScanForCommand(opts) {
17448
17765
  referenceablePaths,
17449
17766
  ctx.cwd,
17450
17767
  activeProvider,
17451
- cfg.scan.maxNodes
17768
+ cfg.scan.maxNodes,
17769
+ cfg.scan.maxFileSizeBytes,
17770
+ cfg.tokenizer
17452
17771
  );
17453
17772
  const willPersist = !opts.noBuiltIns && !opts.dryRun;
17454
- return willPersist ? runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanWith, extensions) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith);
17773
+ const scanned = await (willPersist ? runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanWith, extensions) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith));
17774
+ return scanned.kind === "ok" ? { ...scanned, lensAutoDetected: lens.autoDetected } : scanned;
17455
17775
  }
17456
17776
  function detectionProviders(extensions) {
17457
17777
  return extensions?.providers ?? [];
@@ -17480,12 +17800,20 @@ async function resolveActiveLens(opts, ctx, effectiveRoots, pluginRuntime, provi
17480
17800
  })
17481
17801
  };
17482
17802
  }
17483
- warnIfLensBundleDisabled({
17803
+ warnIfLensPluginDisabled({
17484
17804
  activeProvider: bootstrap.activeProvider,
17485
17805
  resolveEnabled: opts.resolveEnabledOverride ?? pluginRuntime.resolveEnabled,
17486
17806
  printer: opts.printer
17487
17807
  });
17488
- return { kind: "ok", activeProvider: bootstrap.activeProvider };
17808
+ return {
17809
+ kind: "ok",
17810
+ activeProvider: bootstrap.activeProvider,
17811
+ // Only when the lens was freshly auto-detected (not read from
17812
+ // config) does the caller announce it. The bootstrap no longer
17813
+ // prints it itself, to avoid interleaving stderr with the
17814
+ // stdout scan summary on a tty.
17815
+ autoDetected: bootstrap.source === "autodetect" ? bootstrap.activeProvider : null
17816
+ };
17489
17817
  }
17490
17818
  function emitReferenceWalkAdvisory(walk3, opts) {
17491
17819
  if (walk3.truncated) {
@@ -17553,7 +17881,7 @@ function makePriorLoader(noBuiltIns, strict) {
17553
17881
  return loaded;
17554
17882
  };
17555
17883
  }
17556
- function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd, activeProvider, recommendedNodeLimit) {
17884
+ function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd, activeProvider, recommendedNodeLimit, maxFileSizeBytes, tokenizer) {
17557
17885
  return async (prior, priorExtractorRuns, orphanJobFiles) => {
17558
17886
  if (opts.changed && prior === null) {
17559
17887
  opts.stderr.write(SCAN_RUNNER_TEXTS.changedNoPriorWarning);
@@ -17569,6 +17897,8 @@ function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, exte
17569
17897
  prior,
17570
17898
  activeProvider,
17571
17899
  recommendedNodeLimit,
17900
+ maxFileSizeBytes,
17901
+ tokenizer,
17572
17902
  ...priorExtractorRuns ? { priorExtractorRuns } : {},
17573
17903
  ...orphanJobFiles ? { orphanJobFiles } : {}
17574
17904
  });
@@ -17580,6 +17910,7 @@ function buildRunScanOptions(args2) {
17580
17910
  const runOptions = {
17581
17911
  roots: args2.effectiveRoots.slice(),
17582
17912
  tokenize: !opts.noTokens,
17913
+ tokenizer: args2.tokenizer,
17583
17914
  ignoreFilter: args2.ignoreFilter,
17584
17915
  strict: args2.strict,
17585
17916
  emitter: buildRunScanEmitter(opts),
@@ -17590,7 +17921,8 @@ function buildRunScanOptions(args2) {
17590
17921
  orphanJobFiles: orphanJobFiles ?? [],
17591
17922
  activeProvider: args2.activeProvider,
17592
17923
  recommendedNodeLimit: args2.recommendedNodeLimit,
17593
- overrideMaxNodes: opts.maxNodes ?? null
17924
+ overrideMaxNodes: opts.maxNodes ?? null,
17925
+ maxFileSizeBytes: args2.maxFileSizeBytes
17594
17926
  };
17595
17927
  if (args2.extensions) runOptions.extensions = args2.extensions;
17596
17928
  if (prior) {
@@ -17624,6 +17956,7 @@ async function rebuildOnDrift(opts, dbPath) {
17624
17956
  message: tx(DB_DRIFT_TEXTS.driftAborted, {
17625
17957
  dbVersion: drift.dbVersion,
17626
17958
  currentVersion: drift.currentVersion,
17959
+ reason: drift.reason === "version" ? DB_DRIFT_TEXTS.driftReasonVersion : DB_DRIFT_TEXTS.driftReasonSchema,
17627
17960
  hint: dim(DB_DRIFT_TEXTS.driftAbortedHint)
17628
17961
  })
17629
17962
  };
@@ -17962,6 +18295,11 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, stdin, ansi) {
17962
18295
  return ExitCode.Ok;
17963
18296
  }
17964
18297
  const result = outcome.result;
18298
+ if (outcome.lensAutoDetected) {
18299
+ printer.info(
18300
+ tx(SCAN_RUNNER_TEXTS.activeProviderAutodetected, { id: outcome.lensAutoDetected })
18301
+ );
18302
+ }
17965
18303
  const hasErrors = result.issues.some((i) => i.severity === "error");
17966
18304
  printer.info(
17967
18305
  tx(INIT_TEXTS.firstScanSummary, {
@@ -18788,7 +19126,11 @@ var ListCommand = class extends SmCommand {
18788
19126
  const exit = requireDbOrExit(dbPath, this.context.stderr);
18789
19127
  if (exit !== null) return exit;
18790
19128
  return withSqlite(
18791
- { databasePath: dbPath, autoBackup: false },
19129
+ {
19130
+ databasePath: dbPath,
19131
+ autoBackup: false,
19132
+ versionCheck: buildReadVersionCheck(this.printer, stderrAnsi)
19133
+ },
18792
19134
  (adapter) => this.#runQuery(adapter, flags)
18793
19135
  );
18794
19136
  }
@@ -19480,10 +19822,10 @@ var PLUGINS_TEXTS = {
19480
19822
  pluginNotFoundHint: "Run `sm plugins list` for discovered ids and the qualified extension ids.",
19481
19823
  pluginLocked: '{{glyph}} Plugin "{{id}}" is locked by the host and cannot be toggled.\n {{hint}}\n',
19482
19824
  pluginLockedHint: "Locked plugins are mandatory for correct operation. To remove the lock, edit `src/kernel/config/locked-plugins.ts`.",
19483
- qualifiedIdNotFound: "{{glyph}} Qualified extension id not found: {{id}}\n The owning bundle '{{bundleId}}' does not declare an extension with id '{{extId}}'.\n {{hint}}\n",
19484
- qualifiedIdNotFoundHint: "Run `sm plugins list` to see what each bundle ships.",
19485
- qualifiedIdUnknownBundle: "{{glyph}} Qualified extension id references unknown bundle: {{bundleId}}\n {{hint}}\n",
19486
- qualifiedIdUnknownBundleHint: "Run `sm plugins list` for known bundle ids.",
19825
+ qualifiedIdNotFound: "{{glyph}} Qualified extension id not found: {{id}}\n The owning plugin '{{pluginId}}' does not declare an extension with id '{{extId}}'.\n {{hint}}\n",
19826
+ qualifiedIdNotFoundHint: "Run `sm plugins list` to see what each plugin ships.",
19827
+ qualifiedIdUnknownPlugin: "{{glyph}} Qualified extension id references unknown plugin: {{pluginId}}\n {{hint}}\n",
19828
+ qualifiedIdUnknownPluginHint: "Run `sm plugins list` for known plugin ids.",
19487
19829
  // Spec § A.10, `applicableKinds` filter on Extractors. When an extractor
19488
19830
  // declares a kind that no installed Provider emits, the load succeeds
19489
19831
  // (the Provider may arrive later) but `sm plugins doctor` surfaces a
@@ -19508,7 +19850,7 @@ var PLUGINS_TEXTS = {
19508
19850
  // --- doctor verb -----------------------------------------------------
19509
19851
  /**
19510
19852
  * One-line summary that opens the human doctor output. `enabled` is
19511
- * the count of enabled extensions across every bundle (every
19853
+ * the count of enabled extensions across every plugin (every
19512
19854
  * extension is independently toggle-able by its qualified id); the
19513
19855
  * value matches the row count rendered by `sm plugins list` once
19514
19856
  * disabled extensions are filtered out.
@@ -19552,10 +19894,10 @@ var PLUGINS_TEXTS = {
19552
19894
  /**
19553
19895
  * Macro expansion summary printed on stderr before the confirm
19554
19896
  * prompt (or before the `--yes` rejection). The block lists every
19555
- * qualified extension id the bare bundle id resolves to, so the
19897
+ * qualified extension id the bare plugin id resolves to, so the
19556
19898
  * user sees the exact set that would flip.
19557
19899
  */
19558
- bundleMacroHeader: "sm plugins {{verb}} {{bundleId}}: this will affect {{count}} extensions:\n",
19900
+ bundleMacroHeader: "sm plugins {{verb}} {{pluginId}}: this will affect {{count}} extensions:\n",
19559
19901
  bundleMacroRow: " - {{id}}\n",
19560
19902
  /**
19561
19903
  * Interactive prompt rendered to a TTY by the macro path. The
@@ -19575,7 +19917,7 @@ var PLUGINS_TEXTS = {
19575
19917
  * the directed re-run hint.
19576
19918
  */
19577
19919
  bundleMacroRequiresYes: "{{glyph}} Refusing to {{verb}} multiple extensions without confirmation.\n {{hint}}\n",
19578
- bundleMacroRequiresYesHint: "Re-run with --yes to apply, or pass a qualified id `<bundle>/<extension>` for a single extension.",
19920
+ bundleMacroRequiresYesHint: "Re-run with --yes to apply, or pass a qualified id `<plugin>/<extension>` for a single extension.",
19579
19921
  // --- list / show renderers ------------------------------------------
19580
19922
  rowStatusOk: "ok",
19581
19923
  rowStatusOff: "off",
@@ -19588,18 +19930,18 @@ var PLUGINS_TEXTS = {
19588
19930
  sourceBuiltIn: "built-in",
19589
19931
  sourceUser: "user",
19590
19932
  /**
19591
- * Compact bundle row: ` GLYPH ID(pad) N ext SOURCE`.
19933
+ * Compact plugin row: ` GLYPH ID(pad) N ext SOURCE`.
19592
19934
  * Padding for `id` and `count` is computed at render time so all rows
19593
19935
  * align regardless of length. The glyph is wrapped in color before the
19594
19936
  * template substitution.
19595
19937
  */
19596
- bundleRow: " {{glyph}} {{id}}{{count}} ext {{source}}",
19938
+ pluginRow: " {{glyph}} {{id}}{{count}} ext {{source}}",
19597
19939
  /**
19598
- * Indent applied to the names / reason lines under each bundle row.
19940
+ * Indent applied to the names / reason lines under each plugin row.
19599
19941
  * Kept as a single source of truth so the wrap math (`wrapNames`) and
19600
19942
  * the visible output stay in sync.
19601
19943
  */
19602
- bundleSubIndent: " ",
19944
+ pluginSubIndent: " ",
19603
19945
  listTipShow: "\nTip: `sm plugins show <id>` for kinds, versions, and per-extension status.\n",
19604
19946
  /** Show command, built-in header (no version row, no path). */
19605
19947
  detailHeaderBuiltIn: " {{glyph}} {{id}} {{source}} {{count}} extension{{plural}}\n",
@@ -19621,11 +19963,11 @@ var PLUGINS_TEXTS = {
19621
19963
  /** Extensions block heading, separated from the header by a blank line. */
19622
19964
  detailExtensionsBlock: "\n",
19623
19965
  /**
19624
- * Extension row inside the bundle detail. Every extension is
19966
+ * Extension row inside the plugin detail. Every extension is
19625
19967
  * independently toggle-able, so every row carries its own glyph
19626
19968
  * (✓ / ✕). Padding for {{kind}} and {{name}} is computed at render
19627
19969
  * time so columns align inside the block. `{{versionSuffix}}` is
19628
- * either ` v<x.y.z>` (user plugins) or empty (built-in bundles,
19970
+ * either ` v<x.y.z>` (user plugins) or empty (built-in plugins,
19629
19971
  * which inherit the CLI version and do not maintain per-extension
19630
19972
  * versions of their own).
19631
19973
  */
@@ -19633,7 +19975,7 @@ var PLUGINS_TEXTS = {
19633
19975
  detailVersionUnknown: "?",
19634
19976
  detailCompatUnknown: "?",
19635
19977
  /**
19636
- * Show command, single-extension header (qualified `<bundle>/<ext>` id
19978
+ * Show command, single-extension header (qualified `<plugin>/<ext>` id
19637
19979
  * shape). Mirrors `detailHeaderBuiltIn` but the count slot is replaced
19638
19980
  * by the kind so the reader sees at a glance whether they are looking
19639
19981
  * at an extractor, analyzer, etc. Version moves down into the field
@@ -19680,19 +20022,19 @@ var PLUGINS_TEXTS = {
19680
20022
  };
19681
20023
 
19682
20024
  // plugins/presentation-order.ts
19683
- var BUILT_IN_BUNDLE_PRESENTATION_ORDER = [
20025
+ var BUILT_IN_PLUGIN_PRESENTATION_ORDER = [
19684
20026
  "core",
19685
20027
  "claude",
19686
20028
  "antigravity",
19687
20029
  "openai",
19688
20030
  "agent-skills"
19689
20031
  ];
19690
- function sortBundlesForPresentation(bundles) {
20032
+ function sortPluginsForPresentation(plugins) {
19691
20033
  const orderIndex = (id) => {
19692
- const idx = BUILT_IN_BUNDLE_PRESENTATION_ORDER.indexOf(id);
19693
- return idx >= 0 ? idx : BUILT_IN_BUNDLE_PRESENTATION_ORDER.length;
20034
+ const idx = BUILT_IN_PLUGIN_PRESENTATION_ORDER.indexOf(id);
20035
+ return idx >= 0 ? idx : BUILT_IN_PLUGIN_PRESENTATION_ORDER.length;
19694
20036
  };
19695
- return [...bundles].sort((a, b) => {
20037
+ return [...plugins].sort((a, b) => {
19696
20038
  const ai = orderIndex(a.id);
19697
20039
  const bi = orderIndex(b.id);
19698
20040
  if (ai !== bi) return ai - bi;
@@ -19729,24 +20071,24 @@ async function loadAll(opts) {
19729
20071
  return loader.discoverAndLoadAll();
19730
20072
  }
19731
20073
  function builtInRows(resolveEnabled) {
19732
- return sortBundlesForPresentation(builtInBundles).map((bundle) => {
19733
- const extensions = bundle.extensions.map((ext) => extensionRowFromBuiltIn(ext, bundle, resolveEnabled));
19734
- const manifestSummary = bundle.extensions.map((ext) => `${ext.kind}:${qualifiedExtensionId(bundle.id, ext.id)}@${ext.version}`).join(", ");
20074
+ return sortPluginsForPresentation(builtInPlugins).map((plugin) => {
20075
+ const extensions = plugin.extensions.map((ext) => extensionRowFromBuiltIn(ext, plugin, resolveEnabled));
20076
+ const manifestSummary = plugin.extensions.map((ext) => `${ext.kind}:${qualifiedExtensionId(plugin.id, ext.id)}@${ext.version}`).join(", ");
19735
20077
  return {
19736
- id: bundle.id,
20078
+ id: plugin.id,
19737
20079
  enabled: extensions.some((e) => e.enabled),
19738
- description: bundle.description,
20080
+ description: plugin.description,
19739
20081
  extensions,
19740
20082
  manifestSummary
19741
20083
  };
19742
20084
  });
19743
20085
  }
19744
- function extensionRowFromBuiltIn(ext, bundle, resolveEnabled) {
20086
+ function extensionRowFromBuiltIn(ext, plugin, resolveEnabled) {
19745
20087
  const row = {
19746
20088
  id: ext.id,
19747
20089
  kind: ext.kind,
19748
20090
  version: ext.version,
19749
- enabled: resolveEnabled(qualifiedExtensionId(bundle.id, ext.id)),
20091
+ enabled: resolveEnabled(qualifiedExtensionId(plugin.id, ext.id)),
19750
20092
  description: ext.description ?? ""
19751
20093
  };
19752
20094
  if (ext.entry !== void 0) row.entry = ext.entry;
@@ -19782,7 +20124,7 @@ var PluginsListCommand = class extends SmCommand {
19782
20124
  static usage = Command22.Usage({
19783
20125
  category: "Plugins",
19784
20126
  description: "List discovered plugins and their load status.",
19785
- details: "Scans <cwd>/.skill-map/plugins (or --plugin-dir <path>). Built-in bundles (claude, core) are listed alongside user plugins."
20127
+ details: "Scans <cwd>/.skill-map/plugins (or --plugin-dir <path>). Built-in plugins (claude, core) are listed alongside user plugins."
19786
20128
  });
19787
20129
  pluginDir = Option21.String("--plugin-dir", { required: false });
19788
20130
  async run() {
@@ -19819,14 +20161,14 @@ function renderListHuman(builtIns2, plugins, resolveEnabled, ansi) {
19819
20161
  const idCol = row.id.padEnd(idWidth);
19820
20162
  const countCol = String(row.names.length).padStart(countWidth);
19821
20163
  lines.push(
19822
- tx(PLUGINS_TEXTS.bundleRow, {
20164
+ tx(PLUGINS_TEXTS.pluginRow, {
19823
20165
  glyph,
19824
20166
  id: idCol,
19825
20167
  count: ` ${countCol}`,
19826
20168
  source: ansi.dim(row.source)
19827
20169
  })
19828
20170
  );
19829
- const indent = PLUGINS_TEXTS.bundleSubIndent;
20171
+ const indent = PLUGINS_TEXTS.pluginSubIndent;
19830
20172
  if (row.reason) {
19831
20173
  lines.push(`${indent}${ansi.dim(row.reason)}`);
19832
20174
  } else if (row.names.length > 0) {
@@ -19890,11 +20232,11 @@ var PluginsShowCommand = class extends SmCommand {
19890
20232
  category: "Plugins",
19891
20233
  description: "Show a single plugin's manifest + loaded extensions.",
19892
20234
  details: `
19893
- Accepts a bundle / plugin id (\`core\`, \`claude\`, \`my-plugin\`)
20235
+ Accepts a plugin id (\`core\`, \`claude\`, \`my-plugin\`)
19894
20236
  or a qualified extension id (\`core/<ext-id>\`,
19895
20237
  \`<plugin>/<ext-id>\`). When given a qualified id, validates the
19896
20238
  extension exists and renders a single-extension detail block.
19897
- The bare form renders the parent bundle's detail with per-extension
20239
+ The bare form renders the parent plugin's detail with per-extension
19898
20240
  status. The same id shapes \`sm plugins enable\` and
19899
20241
  \`sm plugins disable\` accept resolve cleanly here too.
19900
20242
  `
@@ -19911,9 +20253,9 @@ var PluginsShowCommand = class extends SmCommand {
19911
20253
  this.printer.error(lookupResult.error);
19912
20254
  return ExitCode.NotFound;
19913
20255
  }
19914
- const { bundleId, extId } = lookupResult;
19915
- const builtIn = builtIns2.find((b) => b.id === bundleId);
19916
- const match = plugins.find((p) => p.id === bundleId);
20256
+ const { pluginId, extId } = lookupResult;
20257
+ const builtIn = builtIns2.find((b) => b.id === pluginId);
20258
+ const match = plugins.find((p) => p.id === pluginId);
19917
20259
  if (!builtIn && !match) {
19918
20260
  this.printer.error(
19919
20261
  tx(PLUGINS_TEXTS.pluginNotFound, {
@@ -19925,7 +20267,7 @@ var PluginsShowCommand = class extends SmCommand {
19925
20267
  return ExitCode.NotFound;
19926
20268
  }
19927
20269
  if (extId !== void 0) {
19928
- return this.renderExtensionDetail({ extId, bundleId, builtIn, match });
20270
+ return this.renderExtensionDetail({ extId, pluginId, builtIn, match });
19929
20271
  }
19930
20272
  if (this.json) {
19931
20273
  const payload = builtIn ?? match;
@@ -19939,23 +20281,23 @@ var PluginsShowCommand = class extends SmCommand {
19939
20281
  }
19940
20282
  /**
19941
20283
  * Render the single-extension detail block, the path taken when the
19942
- * user supplies a qualified `<bundle>/<ext>` id. `--json` emits the
19943
- * single extension row (no surrounding bundle envelope) so tooling
20284
+ * user supplies a qualified `<plugin>/<ext>` id. `--json` emits the
20285
+ * single extension row (no surrounding plugin envelope) so tooling
19944
20286
  * can pipe straight into `jq`; human mode renders a focused header
19945
20287
  * plus a Kind / Version / Stability / Description / Preconditions /
19946
20288
  * Entry field block.
19947
20289
  */
19948
20290
  renderExtensionDetail(args2) {
19949
- const { extId, bundleId, builtIn, match } = args2;
20291
+ const { extId, pluginId, builtIn, match } = args2;
19950
20292
  const ansi = this.ansiFor("stdout");
19951
20293
  if (builtIn) {
19952
20294
  const ext = builtIn.extensions.find((e) => e.id === extId);
19953
20295
  if (!ext) return ExitCode.NotFound;
19954
20296
  if (this.json) {
19955
- this.printer.data(JSON.stringify({ pluginId: bundleId, ...ext }, omitModule, 2) + "\n");
20297
+ this.printer.data(JSON.stringify({ pluginId, ...ext }, omitModule, 2) + "\n");
19956
20298
  return ExitCode.Ok;
19957
20299
  }
19958
- this.printer.data(renderBuiltInExtensionDetail(bundleId, ext, ansi));
20300
+ this.printer.data(renderBuiltInExtensionDetail(pluginId, ext, ansi));
19959
20301
  return ExitCode.Ok;
19960
20302
  }
19961
20303
  const userExt = match?.extensions?.find((e) => e.id === extId);
@@ -19964,53 +20306,53 @@ var PluginsShowCommand = class extends SmCommand {
19964
20306
  this.printer.data(JSON.stringify(userExt, omitModule, 2) + "\n");
19965
20307
  return ExitCode.Ok;
19966
20308
  }
19967
- this.printer.data(renderUserExtensionDetail(bundleId, userExt, ansi));
20309
+ this.printer.data(renderUserExtensionDetail(pluginId, userExt, ansi));
19968
20310
  return ExitCode.Ok;
19969
20311
  }
19970
20312
  };
19971
20313
  function resolveShowLookupId(id, builtIns2, plugins, ansi) {
19972
- if (!id.includes("/")) return { bundleId: id };
20314
+ if (!id.includes("/")) return { pluginId: id };
19973
20315
  const parsed = parseQualifiedId(id);
19974
20316
  if ("error" in parsed) return { error: malformedQualifiedError(id, ansi) };
19975
- const { bundleId, extId } = parsed;
19976
- const knownExts = collectKnownExtensions(bundleId, builtIns2, plugins);
19977
- if (knownExts === null) return { error: unknownBundleError(bundleId, ansi) };
20317
+ const { pluginId, extId } = parsed;
20318
+ const knownExts = collectKnownExtensions(pluginId, builtIns2, plugins);
20319
+ if (knownExts === null) return { error: unknownPluginError(pluginId, ansi) };
19978
20320
  if (!knownExts.includes(extId)) {
19979
- return { error: unknownExtensionError(id, bundleId, extId, ansi) };
20321
+ return { error: unknownExtensionError(id, pluginId, extId, ansi) };
19980
20322
  }
19981
- return { bundleId, extId };
20323
+ return { pluginId, extId };
19982
20324
  }
19983
20325
  function parseQualifiedId(id) {
19984
- const [bundleId, extId, ...rest] = id.split("/");
19985
- if (!bundleId || !extId || rest.length > 0) return { error: true };
19986
- return { bundleId, extId };
20326
+ const [pluginId, extId, ...rest] = id.split("/");
20327
+ if (!pluginId || !extId || rest.length > 0) return { error: true };
20328
+ return { pluginId, extId };
19987
20329
  }
19988
- function collectKnownExtensions(bundleId, builtIns2, plugins) {
19989
- const builtIn = builtIns2.find((b) => b.id === bundleId);
20330
+ function collectKnownExtensions(pluginId, builtIns2, plugins) {
20331
+ const builtIn = builtIns2.find((b) => b.id === pluginId);
19990
20332
  if (builtIn) return builtIn.extensions.map((e) => e.id);
19991
- const userPlugin = plugins.find((p) => p.id === bundleId);
20333
+ const userPlugin = plugins.find((p) => p.id === pluginId);
19992
20334
  if (userPlugin) return userPlugin.extensions?.map((e) => e.id) ?? [];
19993
20335
  return null;
19994
20336
  }
19995
20337
  function malformedQualifiedError(id, ansi) {
19996
- return tx(PLUGINS_TEXTS.qualifiedIdUnknownBundle, {
20338
+ return tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
19997
20339
  glyph: ansi.red("\u2715"),
19998
- bundleId: sanitizeForTerminal(id),
19999
- hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownBundleHint)
20340
+ pluginId: sanitizeForTerminal(id),
20341
+ hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
20000
20342
  });
20001
20343
  }
20002
- function unknownBundleError(bundleId, ansi) {
20003
- return tx(PLUGINS_TEXTS.qualifiedIdUnknownBundle, {
20344
+ function unknownPluginError(pluginId, ansi) {
20345
+ return tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
20004
20346
  glyph: ansi.red("\u2715"),
20005
- bundleId: sanitizeForTerminal(bundleId),
20006
- hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownBundleHint)
20347
+ pluginId: sanitizeForTerminal(pluginId),
20348
+ hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
20007
20349
  });
20008
20350
  }
20009
- function unknownExtensionError(id, bundleId, extId, ansi) {
20351
+ function unknownExtensionError(id, pluginId, extId, ansi) {
20010
20352
  return tx(PLUGINS_TEXTS.qualifiedIdNotFound, {
20011
20353
  glyph: ansi.red("\u2715"),
20012
20354
  id: sanitizeForTerminal(id),
20013
- bundleId: sanitizeForTerminal(bundleId),
20355
+ pluginId: sanitizeForTerminal(pluginId),
20014
20356
  extId: sanitizeForTerminal(extId),
20015
20357
  hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdNotFoundHint)
20016
20358
  });
@@ -20101,18 +20443,18 @@ function renderPluginDetailFields(match) {
20101
20443
  function collectPluginExtensionItems(match, ansi) {
20102
20444
  const enabled = match.status === "enabled";
20103
20445
  if (!enabled || !match.extensions) return [];
20104
- const safeBundleId = sanitizeForTerminal(match.id);
20446
+ const safePluginId = sanitizeForTerminal(match.id);
20105
20447
  const sorted = sortExtensionsCanonical(match.extensions);
20106
20448
  return sorted.map((ext) => {
20107
20449
  const safeExtId = sanitizeForTerminal(ext.id);
20108
20450
  return {
20109
20451
  // User plugins surfaced via `loadAll` already filter on the
20110
20452
  // resolver, so a reachable extension on this surface is enabled
20111
- // by construction. The disabled path goes through the bundle
20453
+ // by construction. The disabled path goes through the plugin
20112
20454
  // status header above (✕ on the row).
20113
20455
  glyph: ansi.green(PLUGINS_TEXTS.rowGlyphOk),
20114
20456
  kind: sanitizeForTerminal(ext.kind),
20115
- name: `${safeBundleId}/${safeExtId}`,
20457
+ name: `${safePluginId}/${safeExtId}`,
20116
20458
  version: sanitizeForTerminal(ext.version)
20117
20459
  };
20118
20460
  });
@@ -20138,11 +20480,11 @@ function renderExtensionItems(items) {
20138
20480
  }
20139
20481
  return out.join("");
20140
20482
  }
20141
- function renderBuiltInExtensionDetail(bundleId, ext, ansi) {
20483
+ function renderBuiltInExtensionDetail(pluginId, ext, ansi) {
20142
20484
  const glyph = ext.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
20143
20485
  const header = tx(PLUGINS_TEXTS.detailHeaderExtensionBuiltIn, {
20144
20486
  glyph,
20145
- qualifiedId: sanitizeForTerminal(`${bundleId}/${ext.id}`),
20487
+ qualifiedId: sanitizeForTerminal(`${pluginId}/${ext.id}`),
20146
20488
  source: ansi.dim(PLUGINS_TEXTS.sourceBuiltIn)
20147
20489
  });
20148
20490
  const meta = { kind: ext.kind };
@@ -20150,11 +20492,11 @@ function renderBuiltInExtensionDetail(bundleId, ext, ansi) {
20150
20492
  if (ext.entry !== void 0) meta.entry = ext.entry;
20151
20493
  return header + "\n" + renderExtensionFields(meta);
20152
20494
  }
20153
- function renderUserExtensionDetail(bundleId, ext, ansi) {
20495
+ function renderUserExtensionDetail(pluginId, ext, ansi) {
20154
20496
  const glyph = ansi.green(PLUGINS_TEXTS.rowGlyphOk);
20155
20497
  const header = tx(PLUGINS_TEXTS.detailHeaderExtensionUser, {
20156
20498
  glyph,
20157
- qualifiedId: sanitizeForTerminal(`${bundleId}/${ext.id}`),
20499
+ qualifiedId: sanitizeForTerminal(`${pluginId}/${ext.id}`),
20158
20500
  source: ansi.dim(PLUGINS_TEXTS.sourceUser)
20159
20501
  });
20160
20502
  const meta = readInstanceMeta(ext.instance);
@@ -20408,13 +20750,13 @@ function forEachProviderInstance(plugins, callback) {
20408
20750
  forEachUserPluginProvider(plugins, callback);
20409
20751
  }
20410
20752
  function forEachBuiltInProvider(callback) {
20411
- for (const bundle of builtInBundles) {
20412
- for (const ext of bundle.extensions) {
20753
+ for (const plugin of builtInPlugins) {
20754
+ for (const ext of plugin.extensions) {
20413
20755
  if (ext.kind !== "provider") continue;
20414
20756
  const provider = ext;
20415
20757
  callback({
20416
20758
  id: provider.id,
20417
- pluginId: bundle.id,
20759
+ pluginId: plugin.id,
20418
20760
  instance: provider
20419
20761
  });
20420
20762
  }
@@ -20457,15 +20799,15 @@ function collectApplicableKindWarnings(plugins, knownKinds) {
20457
20799
  return out;
20458
20800
  }
20459
20801
  function collectBuiltInApplicableKindWarnings(out, knownKinds) {
20460
- for (const bundle of builtInBundles) {
20461
- for (const ext of bundle.extensions) {
20802
+ for (const plugin of builtInPlugins) {
20803
+ for (const ext of plugin.extensions) {
20462
20804
  if (ext.kind !== "extractor") continue;
20463
20805
  const extractor = ext;
20464
20806
  const kinds = extractor.precondition?.kind;
20465
20807
  if (!kinds || kinds.length === 0) continue;
20466
20808
  appendUnknownKindWarnings(
20467
20809
  out,
20468
- qualifiedExtensionId(bundle.id, extractor.id),
20810
+ qualifiedExtensionId(plugin.id, extractor.id),
20469
20811
  kinds,
20470
20812
  knownKinds
20471
20813
  );
@@ -20508,11 +20850,11 @@ function collectUnknownSlotWarnings(plugins, knownSlots) {
20508
20850
  return out;
20509
20851
  }
20510
20852
  function collectBuiltInUnknownSlotWarnings(out, knownSlots) {
20511
- for (const bundle of builtInBundles) {
20512
- for (const ext of bundle.extensions) {
20853
+ for (const plugin of builtInPlugins) {
20854
+ for (const ext of plugin.extensions) {
20513
20855
  const vc = ext.viewContributions;
20514
20856
  if (!vc) continue;
20515
- appendUnknownSlotWarnings(out, qualifiedExtensionId(bundle.id, ext.id), vc, knownSlots);
20857
+ appendUnknownSlotWarnings(out, qualifiedExtensionId(plugin.id, ext.id), vc, knownSlots);
20516
20858
  }
20517
20859
  }
20518
20860
  }
@@ -20594,7 +20936,7 @@ import { Command as Command25, Option as Option24 } from "clipanion";
20594
20936
  var TogglePluginsBase = class extends SmCommand {
20595
20937
  all = Option24.Boolean("--all", false);
20596
20938
  yes = Option24.Boolean("--yes,-y", false, {
20597
- description: "Skip the interactive confirm when a bare bundle id (or --all) fans the toggle out across multiple extensions."
20939
+ description: "Skip the interactive confirm when a bare plugin id (or --all) fans the toggle out across multiple extensions."
20598
20940
  });
20599
20941
  ids = Option24.Rest({ name: "ids" });
20600
20942
  async toggle(enabled) {
@@ -20603,7 +20945,7 @@ var TogglePluginsBase = class extends SmCommand {
20603
20945
  const argError = this.#validateArgs(stderrAnsi, verb);
20604
20946
  if (argError !== null) return argError;
20605
20947
  const plugins = await loadAll({ pluginDir: void 0 });
20606
- const catalogue = bundleCatalogue(plugins);
20948
+ const catalogue = pluginCatalogue(plugins);
20607
20949
  const targetsResult = this.#pickTargets(catalogue, stderrAnsi);
20608
20950
  if (typeof targetsResult === "number") return targetsResult;
20609
20951
  let targets = targetsResult;
@@ -20661,7 +21003,7 @@ var TogglePluginsBase = class extends SmCommand {
20661
21003
  if (this.all) {
20662
21004
  return catalogue.map((b) => ({
20663
21005
  origin: "bare",
20664
- bundleId: b.id,
21006
+ pluginId: b.id,
20665
21007
  keys: b.extensionIds.map((extId) => qualifiedExtensionId(b.id, extId))
20666
21008
  }));
20667
21009
  }
@@ -20682,8 +21024,8 @@ var TogglePluginsBase = class extends SmCommand {
20682
21024
  }
20683
21025
  /**
20684
21026
  * Macro gate: when the request would fan a single user input out
20685
- * across more than one extension (either `--all` or a bare bundle
20686
- * id whose bundle holds ≥2 extensions), confirm the intent.
21027
+ * across more than one extension (either `--all` or a bare plugin
21028
+ * id whose plugin holds ≥2 extensions), confirm the intent.
20687
21029
  *
20688
21030
  * Resolution order:
20689
21031
  * 1. `--yes` flag: skip the prompt entirely.
@@ -20692,7 +21034,7 @@ var TogglePluginsBase = class extends SmCommand {
20692
21034
  * message that names the extensions and points at `--yes`.
20693
21035
  *
20694
21036
  * Returns `true` when the verb should proceed, `false` when it
20695
- * should abort. Single-extension targets (bare bundle id mapping to
21037
+ * should abort. Single-extension targets (bare plugin id mapping to
20696
21038
  * one child, or qualified ids) skip the gate uniformly.
20697
21039
  */
20698
21040
  // Cyclomatic count comes from the three-stage gate (--yes shortcut,
@@ -20706,11 +21048,11 @@ var TogglePluginsBase = class extends SmCommand {
20706
21048
  if (this.yes) return true;
20707
21049
  const isTty = Boolean(this.context.stdin && "isTTY" in this.context.stdin && this.context.stdin.isTTY);
20708
21050
  for (const target of macroTargets) {
20709
- const bundleLabel = target.origin === "bare" ? target.bundleId ?? "--all" : "--all";
21051
+ const pluginLabel = target.origin === "bare" ? target.pluginId ?? "--all" : "--all";
20710
21052
  this.printer.info(
20711
21053
  tx(PLUGINS_TEXTS.bundleMacroHeader, {
20712
21054
  verb,
20713
- bundleId: sanitizeForTerminal(bundleLabel),
21055
+ pluginId: sanitizeForTerminal(pluginLabel),
20714
21056
  count: target.keys.length
20715
21057
  })
20716
21058
  );
@@ -20766,7 +21108,7 @@ var TogglePluginsBase = class extends SmCommand {
20766
21108
  * Persist every qualified id in `config_plugins`. On disable, also
20767
21109
  * purge the plugin's `scan_contributions` rows immediately (matches
20768
21110
  * the BFF route, see `server/routes/plugins.ts:applyChangeToAdapter`).
20769
- * Every key is `<bundle>/<ext>` shape so the contribution purge can
21111
+ * Every key is `<plugin>/<ext>` shape so the contribution purge can
20770
21112
  * split into `(pluginId, extensionId)` cleanly.
20771
21113
  */
20772
21114
  async #persistKeys(keys, enabled) {
@@ -20829,11 +21171,11 @@ var PluginsEnableCommand = class extends TogglePluginsBase {
20829
21171
  flip; sm config reset plugins.<id>.enabled drops the settings.json
20830
21172
  baseline.
20831
21173
 
20832
- Accepts qualified ids (\`claude/at-directive\`) and bare bundle
21174
+ Accepts qualified ids (\`claude/at-directive\`) and bare plugin
20833
21175
  ids (\`claude\`, which fans the toggle out across every extension
20834
- inside the bundle). Multi-extension bundles need --yes (or an
21176
+ inside the plugin). Multi-extension plugins need --yes (or an
20835
21177
  interactive TTY confirm) to avoid flipping 27 core extensions by
20836
- accident. Single-extension bundles (openai, agent-skills,
21178
+ accident. Single-extension plugins (openai, agent-skills,
20837
21179
  antigravity) apply without prompting.
20838
21180
 
20839
21181
  Batches are all-or-nothing: a single unknown id aborts before
@@ -20856,11 +21198,11 @@ var PluginsDisableCommand = class extends TogglePluginsBase {
20856
21198
  sm plugins list, but with status=disabled; the kernel will not
20857
21199
  run any of its disabled extensions.
20858
21200
 
20859
- Accepts qualified ids (\`core/markdown-link\`) and bare bundle
21201
+ Accepts qualified ids (\`core/markdown-link\`) and bare plugin
20860
21202
  ids (\`core\`, which fans the toggle out across every extension
20861
- inside the bundle). Multi-extension bundles need --yes (or an
21203
+ inside the plugin). Multi-extension plugins need --yes (or an
20862
21204
  interactive TTY confirm) to avoid flipping 27 core extensions by
20863
- accident. Single-extension bundles (openai, agent-skills,
21205
+ accident. Single-extension plugins (openai, agent-skills,
20864
21206
  antigravity) apply without prompting.
20865
21207
 
20866
21208
  Batches are all-or-nothing: a single unknown id aborts before
@@ -20872,12 +21214,12 @@ var PluginsDisableCommand = class extends TogglePluginsBase {
20872
21214
  return this.toggle(false);
20873
21215
  }
20874
21216
  };
20875
- function bundleCatalogue(plugins) {
21217
+ function pluginCatalogue(plugins) {
20876
21218
  const out = [];
20877
- for (const bundle of builtInBundles) {
21219
+ for (const plugin of builtInPlugins) {
20878
21220
  out.push({
20879
- id: bundle.id,
20880
- extensionIds: bundle.extensions.map((e) => e.id)
21221
+ id: plugin.id,
21222
+ extensionIds: plugin.extensions.map((e) => e.id)
20881
21223
  });
20882
21224
  }
20883
21225
  for (const p of plugins) {
@@ -20893,32 +21235,32 @@ function resolveToggleTarget(id, catalogue, ansi) {
20893
21235
  }
20894
21236
  function resolveQualifiedToggle(id, catalogue, ansi) {
20895
21237
  const errGlyph = ansi.red("\u2715");
20896
- const [bundleId, extId, ...rest] = id.split("/");
20897
- if (!bundleId || !extId || rest.length > 0) {
21238
+ const [pluginId, extId, ...rest] = id.split("/");
21239
+ if (!pluginId || !extId || rest.length > 0) {
20898
21240
  return {
20899
- error: tx(PLUGINS_TEXTS.qualifiedIdUnknownBundle, {
21241
+ error: tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
20900
21242
  glyph: errGlyph,
20901
- bundleId: sanitizeForTerminal(id),
20902
- hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownBundleHint)
21243
+ pluginId: sanitizeForTerminal(id),
21244
+ hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
20903
21245
  })
20904
21246
  };
20905
21247
  }
20906
- const bundle = catalogue.find((b) => b.id === bundleId);
20907
- if (!bundle) {
21248
+ const plugin = catalogue.find((b) => b.id === pluginId);
21249
+ if (!plugin) {
20908
21250
  return {
20909
- error: tx(PLUGINS_TEXTS.qualifiedIdUnknownBundle, {
21251
+ error: tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
20910
21252
  glyph: errGlyph,
20911
- bundleId: sanitizeForTerminal(bundleId),
20912
- hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownBundleHint)
21253
+ pluginId: sanitizeForTerminal(pluginId),
21254
+ hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
20913
21255
  })
20914
21256
  };
20915
21257
  }
20916
- if (!bundle.extensionIds.includes(extId)) {
21258
+ if (!plugin.extensionIds.includes(extId)) {
20917
21259
  return {
20918
21260
  error: tx(PLUGINS_TEXTS.qualifiedIdNotFound, {
20919
21261
  glyph: errGlyph,
20920
21262
  id: sanitizeForTerminal(id),
20921
- bundleId: sanitizeForTerminal(bundleId),
21263
+ pluginId: sanitizeForTerminal(pluginId),
20922
21264
  extId: sanitizeForTerminal(extId),
20923
21265
  hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdNotFoundHint)
20924
21266
  })
@@ -20926,12 +21268,12 @@ function resolveQualifiedToggle(id, catalogue, ansi) {
20926
21268
  }
20927
21269
  return {
20928
21270
  origin: "qualified",
20929
- keys: [qualifiedExtensionId(bundleId, extId)]
21271
+ keys: [qualifiedExtensionId(pluginId, extId)]
20930
21272
  };
20931
21273
  }
20932
21274
  function resolveBareToggle(id, catalogue) {
20933
- const bundle = catalogue.find((b) => b.id === id);
20934
- if (!bundle) {
21275
+ const plugin = catalogue.find((b) => b.id === id);
21276
+ if (!plugin) {
20935
21277
  return {
20936
21278
  error: tx(PLUGINS_TEXTS.pluginNotFound, {
20937
21279
  glyph: "\u2715",
@@ -20942,13 +21284,13 @@ function resolveBareToggle(id, catalogue) {
20942
21284
  }
20943
21285
  return {
20944
21286
  origin: "bare",
20945
- bundleId: bundle.id,
20946
- keys: bundle.extensionIds.map((extId) => qualifiedExtensionId(bundle.id, extId))
21287
+ pluginId: plugin.id,
21288
+ keys: plugin.extensionIds.map((extId) => qualifiedExtensionId(plugin.id, extId))
20947
21289
  };
20948
21290
  }
20949
21291
 
20950
21292
  // cli/commands/plugins/create.ts
20951
- import { existsSync as existsSync24, mkdirSync as mkdirSync5, writeFileSync } from "fs";
21293
+ import { existsSync as existsSync25, mkdirSync as mkdirSync5, writeFileSync } from "fs";
20952
21294
  import { join as join18, resolve as resolve33 } from "path";
20953
21295
  import { Command as Command26, Option as Option25 } from "clipanion";
20954
21296
  var PluginsCreateCommand = class extends SmCommand {
@@ -20977,7 +21319,7 @@ var PluginsCreateCommand = class extends SmCommand {
20977
21319
  const ctx = defaultRuntimeContext();
20978
21320
  const baseDir = defaultProjectPluginsDir(ctx);
20979
21321
  const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
20980
- if (existsSync24(targetDir) && !this.force) {
21322
+ if (existsSync25(targetDir) && !this.force) {
20981
21323
  this.printer.error(
20982
21324
  tx(PLUGINS_TEXTS.createRefuseOverwrite, {
20983
21325
  glyph: errGlyph,
@@ -20991,20 +21333,10 @@ var PluginsCreateCommand = class extends SmCommand {
20991
21333
  mkdirSync5(join18(targetDir, "extractors", extractorName), { recursive: true });
20992
21334
  const specVersion = installedSpecVersion();
20993
21335
  const manifest = {
20994
- id: this.pluginId,
20995
21336
  version: "0.1.0",
20996
21337
  specCompat: `^${specVersion}`,
20997
21338
  catalogCompat: "^1.0.0",
20998
- description: "Generated by `sm plugins create`. Edit to taste.",
20999
- settings: {
21000
- keywords: {
21001
- type: "string-list",
21002
- label: "Keywords to track",
21003
- description: "Words counted across each scanned node body.",
21004
- default: ["TODO", "FIXME"],
21005
- min: 1
21006
- }
21007
- }
21339
+ description: "Generated by `sm plugins create`. Edit to taste."
21008
21340
  };
21009
21341
  writeFileSync(
21010
21342
  join18(targetDir, "plugin.json"),
@@ -21029,36 +21361,44 @@ function scaffolderExtractorStub(extractorId) {
21029
21361
  * Generated by \`sm plugins create\`. Edit the extract() body.
21030
21362
  *
21031
21363
  * Loader contract: the plugin loader resolves the extension via the
21032
- * MODULE'S DEFAULT EXPORT (\`export default { ... }\`). Renaming or
21033
- * splitting into a named export will surface as \`load-error: default
21034
- * export missing a string \\\`kind\\\` field\`.
21364
+ * MODULE'S DEFAULT EXPORT (\`export default { ... }\`). It must be an
21365
+ * object literal; renaming or splitting into a named export surfaces as
21366
+ * a \`load-error\`.
21035
21367
  *
21036
21368
  * Folder convention: this file lives at
21037
- * \`extractors/${extractorId}/index.js\`. The bundle's plugin.json#/id
21038
- * provides the qualified id \`<plugin-id>/${extractorId}\`; the loader
21039
- * injects \`pluginId\` automatically, do NOT hardcode it here.
21369
+ * \`extractors/${extractorId}/index.js\`. The folder layout is the
21370
+ * source of truth (structure-as-truth): the loader derives \`kind\`
21371
+ * (\`extractor\`) from the parent folder and the id (\`${extractorId}\`)
21372
+ * from the leaf folder, and injects \`pluginId\` from the plugin, so none
21373
+ * of them are declared here. Re-declaring \`kind\` / \`id\` is rejected as
21374
+ * \`invalid-manifest\`.
21040
21375
  *
21041
- * Declared view contributions (in plugin.json):
21376
+ * Declared view contributions (\`ui\`):
21042
21377
  * - 'count' \u2192 slot \`card.footer.left\` (renders as a chip
21043
21378
  * in the left footer of the node card)
21044
21379
  *
21045
- * Declared settings:
21380
+ * Declared settings (\`settings\`):
21046
21381
  * - 'keywords' (string-list) \u2192 exposed as ctx.settings.keywords
21047
21382
  *
21048
21383
  * See: spec/plugin-author-guide.md \xA7View contributions
21049
21384
  * spec/view-slots.md
21050
21385
  */
21051
21386
  export default {
21052
- id: '${extractorId}',
21053
- kind: 'extractor',
21054
21387
  version: '0.1.0',
21055
21388
  description: 'Counts configured keywords per node.',
21056
- stability: 'experimental',
21057
- emitsLinkKinds: [],
21058
- defaultConfidence: 'high',
21059
21389
  scope: 'body',
21060
21390
 
21061
- viewContributions: {
21391
+ settings: {
21392
+ keywords: {
21393
+ type: 'string-list',
21394
+ label: 'Keywords to track',
21395
+ description: 'Words counted across each scanned node body.',
21396
+ default: ['TODO', 'FIXME'],
21397
+ min: 1,
21398
+ },
21399
+ },
21400
+
21401
+ ui: {
21062
21402
  count: {
21063
21403
  slot: 'card.footer.left',
21064
21404
  icon: '\u{1F50D}',
@@ -21084,7 +21424,7 @@ export default {
21084
21424
  function scaffolderReadme(pluginId) {
21085
21425
  return `# ${pluginId}
21086
21426
 
21087
- Generated by \`sm plugins create\`. Edit \`extensions/extractor.js\` to taste.
21427
+ Generated by \`sm plugins create\`. Edit \`extractors/${pluginId}-extractor/index.js\` to taste.
21088
21428
 
21089
21429
  ## Verbs
21090
21430
 
@@ -21604,6 +21944,31 @@ var IntentionalFailCommand = class extends SmCommand {
21604
21944
  // cli/commands/scan.ts
21605
21945
  import { Command as Command31, Option as Option29 } from "clipanion";
21606
21946
 
21947
+ // kernel/util/format-bytes.ts
21948
+ var UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
21949
+ function formatBytes(bytes) {
21950
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
21951
+ if (bytes < 1024) return `${Math.round(bytes)} B`;
21952
+ let value = bytes;
21953
+ let unitIndex = 0;
21954
+ while (value >= 1024 && unitIndex < UNITS.length - 1) {
21955
+ value /= 1024;
21956
+ unitIndex += 1;
21957
+ }
21958
+ const rounded = Math.round(value * 10) / 10;
21959
+ const text = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
21960
+ return `${text} ${UNITS[unitIndex]}`;
21961
+ }
21962
+
21963
+ // kernel/util/format-oversized.ts
21964
+ function formatOversizedFilePair(file) {
21965
+ return `${file.path} (${formatBytes(file.bytes)})`;
21966
+ }
21967
+ function formatOversizedFileRows(files) {
21968
+ return files.map((file) => ` - ${formatOversizedFilePair(file)}
21969
+ `);
21970
+ }
21971
+
21607
21972
  // cli/i18n/scan.texts.ts
21608
21973
  var SCAN_TEXTS = {
21609
21974
  // --- scan command ----------------------------------------------------
@@ -21651,6 +22016,20 @@ var SCAN_TEXTS = {
21651
22016
  */
21652
22017
  scanCappedNotice: "{{glyph}} Scan capped at {{limit}} nodes ({{source}}).\n {{hint}}\n",
21653
22018
  scanCappedNoticeHint: "Trim .skillmapignore to exclude noisy paths (preferred), or re-run with --max-nodes <N> to raise the cap. Past the recommended limit the graph is hard to read and analyzer signal drops.",
22019
+ /**
22020
+ * File-size skip notice, printed (WARN, stderr) when the walker
22021
+ * skipped one or more files for exceeding `scan.maxFileSizeBytes`.
22022
+ * `{{glyph}}` is the yellow warning glyph, `{{count}}`/`{{noun}}` the
22023
+ * skipped-file tally, `{{files}}` the pre-rendered list of
22024
+ * `path (size)` rows, `{{hint}}` the dim escape-route line.
22025
+ */
22026
+ scanSkippedFilesNotice: "{{glyph}} Skipped {{count}} {{noun}} over the size limit (scan.maxFileSizeBytes):\n{{files}} {{hint}}\n",
22027
+ // The per-file ` - path (size)\n` rows that fill `{{files}}` are
22028
+ // rendered by `kernel/util/format-oversized.ts:formatOversizedFileRows`,
22029
+ // shared with `sm watch` / `sm serve` so the three surfaces never drift.
22030
+ scanSkippedFileNounSingular: "file",
22031
+ scanSkippedFileNounPlural: "files",
22032
+ scanSkippedFilesNoticeHint: "Raise scan.maxFileSizeBytes to include these, or add them to .skillmapignore to skip them on purpose.",
21654
22033
  /**
21655
22034
  * Validation message for an invalid `--max-nodes` value. Surfaced as a
21656
22035
  * §3.1b two-line block.
@@ -21825,11 +22204,13 @@ function createWatcherRuntime(opts) {
21825
22204
  const runOptions = {
21826
22205
  roots: opts.roots,
21827
22206
  tokenize,
22207
+ tokenizer: cfg.tokenizer,
21828
22208
  ignoreFilter,
21829
22209
  strict,
21830
22210
  emitter,
21831
22211
  recommendedNodeLimit: cfg.scan.maxNodes,
21832
- overrideMaxNodes: opts.maxNodesOverride ?? null
22212
+ overrideMaxNodes: opts.maxNodesOverride ?? null,
22213
+ maxFileSizeBytes: cfg.scan.maxFileSizeBytes
21833
22214
  };
21834
22215
  if (cfg.scan.referencePaths.length > 0) {
21835
22216
  const walk3 = walkReferencePaths(cfg.scan.referencePaths, cwd);
@@ -22047,7 +22428,20 @@ var WATCH_TEXTS = {
22047
22428
  * §3.1b two-line block. Validation rejection for `--max-nodes`.
22048
22429
  */
22049
22430
  maxNodesInvalid: "{{glyph}} sm watch: --max-nodes must be an integer >= 1 (got {{raw}}).\n {{hint}}\n",
22050
- maxNodesInvalidHint: "Pass a positive integer, e.g. --max-nodes 256."
22431
+ maxNodesInvalidHint: "Pass a positive integer, e.g. --max-nodes 256.",
22432
+ /**
22433
+ * File-size skip WARN, emitted per batch (stderr) when the walker
22434
+ * skipped one or more files for exceeding `scan.maxFileSizeBytes`.
22435
+ * Mirrors `sm scan`'s notice. `{{files}}` is the pre-rendered list of
22436
+ * `path (size)` rows.
22437
+ */
22438
+ skippedFilesNotice: "{{glyph}} Skipped {{count}} {{noun}} over the size limit (scan.maxFileSizeBytes):\n{{files}} {{hint}}\n",
22439
+ // The per-file ` - path (size)\n` rows that fill `{{files}}` are
22440
+ // rendered by `kernel/util/format-oversized.ts:formatOversizedFileRows`,
22441
+ // shared with `sm scan` / `sm serve` so the three surfaces never drift.
22442
+ skippedFileNounSingular: "file",
22443
+ skippedFileNounPlural: "files",
22444
+ skippedFilesNoticeHint: "Raise scan.maxFileSizeBytes to include these, or add them to .skillmapignore to skip them on purpose."
22051
22445
  };
22052
22446
 
22053
22447
  // cli/commands/watch.ts
@@ -22088,6 +22482,21 @@ async function runWatchLoop(opts) {
22088
22482
  })
22089
22483
  );
22090
22484
  }
22485
+ renderOversizedWarning(result);
22486
+ };
22487
+ const renderOversizedWarning = (result) => {
22488
+ const oversized = result.oversizedFiles ?? [];
22489
+ if ((result.stats.filesOversized ?? oversized.length) <= 0) return;
22490
+ const files = formatOversizedFileRows(oversized).join("");
22491
+ context.stderr.write(
22492
+ tx(WATCH_TEXTS.skippedFilesNotice, {
22493
+ glyph: stderrAnsi.yellow("\u26A0"),
22494
+ count: oversized.length,
22495
+ noun: oversized.length === 1 ? WATCH_TEXTS.skippedFileNounSingular : WATCH_TEXTS.skippedFileNounPlural,
22496
+ files,
22497
+ hint: stderrAnsi.dim(WATCH_TEXTS.skippedFilesNoticeHint)
22498
+ })
22499
+ );
22091
22500
  };
22092
22501
  const runtimeOpts = {
22093
22502
  dbPath,
@@ -22414,7 +22823,13 @@ var ScanCommand = class extends SmCommand {
22414
22823
  });
22415
22824
  if (outcome.kind === "ok") {
22416
22825
  setScanExtensions(buildScanExtensionSet(outcome.executedExtensionIds));
22417
- return this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict);
22826
+ return this.renderOutcome(
22827
+ outcome.result,
22828
+ outcome.persistedTo,
22829
+ outcome.dbPath,
22830
+ outcome.strict,
22831
+ outcome.lensAutoDetected
22832
+ );
22418
22833
  }
22419
22834
  return this.renderFailure(outcome);
22420
22835
  }
@@ -22523,12 +22938,13 @@ var ScanCommand = class extends SmCommand {
22523
22938
  * the exit code. Exit 1 only when at least one issue is at `error`
22524
22939
  * severity (mirrors `sm check`, per spec § Exit codes).
22525
22940
  */
22526
- renderOutcome(result, persistedTo, dbPath, strict) {
22941
+ renderOutcome(result, persistedTo, dbPath, strict, lensAutoDetected) {
22527
22942
  const exitCode2 = result.issues.some((i) => i.severity === "error") ? ExitCode.Issues : ExitCode.Ok;
22528
22943
  if (this.json) {
22529
22944
  return this.#renderJsonOutcome(result, exitCode2, strict);
22530
22945
  }
22531
22946
  const ansi = this.ansiFor("stdout");
22947
+ this.#announceAutoDetectedLens(lensAutoDetected);
22532
22948
  const cwd = defaultRuntimeContext().cwd;
22533
22949
  const hasErrors = exitCode2 === ExitCode.Issues;
22534
22950
  const severityCounts = countBySeverity(result.issues);
@@ -22558,8 +22974,32 @@ var ScanCommand = class extends SmCommand {
22558
22974
  );
22559
22975
  }
22560
22976
  this.maybePrintCapNotice(result, ansi);
22977
+ this.maybePrintSkippedFilesNotice(result, ansi);
22561
22978
  return exitCode2;
22562
22979
  }
22980
+ /**
22981
+ * Surface a WARN when the walker skipped one or more files for
22982
+ * exceeding `scan.maxFileSizeBytes`. Lists every skipped file as
22983
+ * `path (humanSize)` and points the user at the two levers
22984
+ * (`scan.maxFileSizeBytes` to raise the limit, `.skillmapignore` to
22985
+ * exclude the path). Routed through `printer.warn` (stderr) because a
22986
+ * silently dropped file is degraded state the operator should read,
22987
+ * not a mid-flight progress line.
22988
+ */
22989
+ maybePrintSkippedFilesNotice(result, ansi) {
22990
+ const oversized = result.oversizedFiles ?? [];
22991
+ if ((result.stats.filesOversized ?? oversized.length) <= 0) return;
22992
+ const lines = formatOversizedFileRows(oversized).join("");
22993
+ this.printer.warn(
22994
+ tx(SCAN_TEXTS.scanSkippedFilesNotice, {
22995
+ glyph: ansi.yellow("\u26A0"),
22996
+ count: String(oversized.length),
22997
+ noun: oversized.length === 1 ? SCAN_TEXTS.scanSkippedFileNounSingular : SCAN_TEXTS.scanSkippedFileNounPlural,
22998
+ files: lines,
22999
+ hint: ansi.dim(SCAN_TEXTS.scanSkippedFilesNoticeHint)
23000
+ })
23001
+ );
23002
+ }
22563
23003
  /**
22564
23004
  * Surface the §Node cap notice when the walker actually stopped
22565
23005
  * accepting files because of the cap. Derivation: `filesWalked >
@@ -22583,6 +23023,21 @@ var ScanCommand = class extends SmCommand {
22583
23023
  })
22584
23024
  );
22585
23025
  }
23026
+ /**
23027
+ * Print the lens auto-detect line on stdout (the SAME stream as the
23028
+ * scan summary) so the two never interleave on a tty. The bootstrap
23029
+ * deliberately no longer prints this to stderr; the runner threads
23030
+ * `lensAutoDetected` through so the CLI announces it here, in order,
23031
+ * right before the summary. The text ends in a newline, so the
23032
+ * summary lands cleanly on its own line. No-op when the lens came
23033
+ * from config or no marker matched (`null` / `undefined`).
23034
+ */
23035
+ #announceAutoDetectedLens(lensAutoDetected) {
23036
+ if (!lensAutoDetected) return;
23037
+ this.printer.data(
23038
+ tx(SCAN_RUNNER_TEXTS.activeProviderAutodetected, { id: lensAutoDetected })
23039
+ );
23040
+ }
22586
23041
  /**
22587
23042
  * `--json` output path. Under `--strict` (H4) self-validates the
22588
23043
  * ScanResult against `scan-result.schema.json` before emitting it,
@@ -22900,7 +23355,7 @@ function renderDeltaIssues(issues) {
22900
23355
 
22901
23356
  // cli/commands/serve.ts
22902
23357
  import { spawn as spawn2 } from "child_process";
22903
- import { existsSync as existsSync30 } from "fs";
23358
+ import { existsSync as existsSync31 } from "fs";
22904
23359
  import { Command as Command33, Option as Option31 } from "clipanion";
22905
23360
 
22906
23361
  // kernel/util/dev-mode.ts
@@ -23055,6 +23510,12 @@ var SERVER_TEXTS = {
23055
23510
  // watcher loop continues, a transient FS error must not kill the
23056
23511
  // broadcaster.
23057
23512
  watcherBatchFailed: "skill-map server: watcher batch failed ({{message}}).\n",
23513
+ // Logged on the server pane when a scan batch (initial or follow-up)
23514
+ // skipped one or more files for exceeding `scan.maxFileSizeBytes`.
23515
+ // `{{files}}` is the comma-separated `path (size)` list. The SPA
23516
+ // raises its own banner from the persisted `oversizedFiles`, so this
23517
+ // is log-only (no WS advisory).
23518
+ watcherFilesOversized: "skill-map server: skipped {{count}} file(s) over the size limit (scan.maxFileSizeBytes): {{files}}. Raise the limit or add them to .skillmapignore.\n",
23058
23519
  // Logged once when the pre-1.0 schema-drift check rebuilt the DB on
23059
23520
  // watcher boot (the on-disk cache was written by a different
23060
23521
  // major.minor). The scan that follows repopulates it; .sm sidecars
@@ -23140,19 +23601,19 @@ var SERVER_TEXTS = {
23140
23601
  // 400, cascade route rejects qualified ids: the bare-id PATCH is the
23141
23602
  // bundle macro endpoint. Anything containing `/` needs the dedicated
23142
23603
  // per-extension route below.
23143
- pluginsCascadeRouteQualifiedRejected: 'Plugin id "{{id}}" contains "/"; toggle individual extensions via PATCH /api/plugins/<bundle>/extensions/<extensionId>.',
23604
+ pluginsCascadeRouteQualifiedRejected: 'Plugin id "{{id}}" contains "/"; toggle individual extensions via PATCH /api/plugins/<plugin>/extensions/<extensionId>.',
23144
23605
  // 404, unknown plugin / extension.
23145
23606
  pluginsUnknown: 'No plugin with id "{{id}}".',
23146
- pluginsExtensionUnknown: 'Plugin "{{bundleId}}" has no extension named "{{extensionId}}".',
23607
+ pluginsExtensionUnknown: 'Plugin "{{pluginId}}" has no extension named "{{extensionId}}".',
23147
23608
  // 500, DB missing on a write path. Read paths degrade to empty
23148
23609
  // shapes, but mutations cannot persist without a DB so they fail fast.
23149
23610
  pluginsDbMissing: "Cannot persist plugin override: project DB not found at {{path}}. Run `sm scan` first or pass --db <path>.",
23150
23611
  // 403, host-enforced lock from `src/server/locked-plugins.ts`. The
23151
- // bundle (or qualified extension) is in the hardcoded lock-list and
23612
+ // plugin (or qualified extension) is in the hardcoded lock-list and
23152
23613
  // its enabled state is fixed; the UI mirrors the same rule by
23153
23614
  // disabling the toggle.
23154
23615
  pluginsLocked: 'Plugin "{{id}}" is locked by the host and cannot be toggled.',
23155
- pluginsExtensionLocked: 'Extension "{{bundleId}}/{{extensionId}}" is locked by the host and cannot be toggled.',
23616
+ pluginsExtensionLocked: 'Extension "{{pluginId}}/{{extensionId}}" is locked by the host and cannot be toggled.',
23156
23617
  // 400 envelopes specific to the bulk `PATCH /api/plugins` endpoint.
23157
23618
  // The single-id variants above still apply for per-entry validation
23158
23619
  // (unknown id, granularity mismatch, lock); these cover the
@@ -23691,7 +24152,7 @@ function contentTypeFor(format) {
23691
24152
  }
23692
24153
 
23693
24154
  // server/health.ts
23694
- import { existsSync as existsSync25 } from "fs";
24155
+ import { existsSync as existsSync26 } from "fs";
23695
24156
  var FALLBACK_SCHEMA_VERSION = "1";
23696
24157
  function buildHealth(deps) {
23697
24158
  const dev = isDevBuild();
@@ -23700,7 +24161,7 @@ function buildHealth(deps) {
23700
24161
  schemaVersion: FALLBACK_SCHEMA_VERSION,
23701
24162
  specVersion: deps.specVersion,
23702
24163
  implVersion: VERSION,
23703
- db: existsSync25(deps.dbPath) ? "present" : "missing",
24164
+ db: existsSync26(deps.dbPath) ? "present" : "missing",
23704
24165
  cwd: deps.cwd,
23705
24166
  dbPath: deps.dbPath,
23706
24167
  // Only emit when truthy so a published install keeps the wire
@@ -24219,28 +24680,28 @@ function registerPluginsRoute(app, deps) {
24219
24680
  });
24220
24681
  }
24221
24682
  const body = await parsePatchBody(c.req.raw);
24222
- const childIds = bundleExtensionIds(handle).map((extId) => qualifiedExtensionId(id, extId));
24683
+ const childIds = pluginExtensionIds(handle).map((extId) => qualifiedExtensionId(id, extId));
24223
24684
  const writable = childIds.filter((q) => !isPluginLocked(q));
24224
24685
  return await persistManyAndProject(c, deps, writable, body.enabled);
24225
24686
  });
24226
- app.patch("/api/plugins/:bundleId/extensions/:extensionId", async (c) => {
24227
- const bundleId = c.req.param("bundleId");
24687
+ app.patch("/api/plugins/:pluginId/extensions/:extensionId", async (c) => {
24688
+ const pluginId = c.req.param("pluginId");
24228
24689
  const extensionId = c.req.param("extensionId");
24229
- const handle = findHandle(bundleId, deps);
24690
+ const handle = findHandle(pluginId, deps);
24230
24691
  if (!handle) {
24231
24692
  throw new HTTPException9(404, {
24232
- message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
24693
+ message: tx(SERVER_TEXTS.pluginsUnknown, { id: pluginId })
24233
24694
  });
24234
24695
  }
24235
24696
  if (!hasExtension(handle, extensionId)) {
24236
24697
  throw new HTTPException9(404, {
24237
- message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { bundleId, extensionId })
24698
+ message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { pluginId, extensionId })
24238
24699
  });
24239
24700
  }
24240
- const qualified = qualifiedExtensionId(bundleId, extensionId);
24241
- if (isPluginLocked(qualified) || isPluginLocked(bundleId)) {
24701
+ const qualified = qualifiedExtensionId(pluginId, extensionId);
24702
+ if (isPluginLocked(qualified) || isPluginLocked(pluginId)) {
24242
24703
  throw new HTTPException9(403, {
24243
- message: tx(SERVER_TEXTS.pluginsExtensionLocked, { bundleId, extensionId })
24704
+ message: tx(SERVER_TEXTS.pluginsExtensionLocked, { pluginId, extensionId })
24244
24705
  });
24245
24706
  }
24246
24707
  const body = await parsePatchBody(c.req.raw);
@@ -24269,11 +24730,11 @@ function listItems(deps, resolveEnabled) {
24269
24730
  ];
24270
24731
  }
24271
24732
  function buildBuiltInItems(resolveEnabled) {
24272
- return sortBundlesForPresentation(builtInBundles).map((bundle) => {
24273
- const bundleLocked = isPluginLocked(bundle.id);
24274
- const extensions = bundle.extensions.map((ext) => {
24275
- const qualified = qualifiedExtensionId(bundle.id, ext.id);
24276
- const extLocked = bundleLocked || isPluginLocked(qualified);
24733
+ return sortPluginsForPresentation(builtInPlugins).map((plugin) => {
24734
+ const pluginLocked = isPluginLocked(plugin.id);
24735
+ const extensions = plugin.extensions.map((ext) => {
24736
+ const qualified = qualifiedExtensionId(plugin.id, ext.id);
24737
+ const extLocked = pluginLocked || isPluginLocked(qualified);
24277
24738
  return {
24278
24739
  id: ext.id,
24279
24740
  kind: ext.kind,
@@ -24283,17 +24744,17 @@ function buildBuiltInItems(resolveEnabled) {
24283
24744
  ...extLocked ? { locked: true } : {}
24284
24745
  };
24285
24746
  });
24286
- const bundleEnabled = extensions.some((e) => e.enabled);
24747
+ const pluginEnabled = extensions.some((e) => e.enabled);
24287
24748
  return {
24288
- id: bundle.id,
24289
- version: firstVersion(bundle.extensions),
24290
- kinds: uniqueKinds(bundle.extensions.map((e) => e.kind)),
24291
- status: bundleEnabled ? "enabled" : "disabled",
24749
+ id: plugin.id,
24750
+ version: firstVersion(plugin.extensions),
24751
+ kinds: uniqueKinds(plugin.extensions.map((e) => e.kind)),
24752
+ status: pluginEnabled ? "enabled" : "disabled",
24292
24753
  reason: null,
24293
24754
  source: "built-in",
24294
- description: bundle.description,
24755
+ description: plugin.description,
24295
24756
  ...extensions.length > 0 ? { extensions } : {},
24296
- ...bundleLocked ? { locked: true } : {}
24757
+ ...pluginLocked ? { locked: true } : {}
24297
24758
  };
24298
24759
  });
24299
24760
  }
@@ -24301,8 +24762,8 @@ function buildDiscoveredItems(discovered, deps, resolveEnabled) {
24301
24762
  return discovered.map((plugin) => buildDiscoveredItem(plugin, deps, resolveEnabled));
24302
24763
  }
24303
24764
  function buildDiscoveredItem(plugin, deps, resolveEnabled) {
24304
- const bundleLocked = isPluginLocked(plugin.id);
24305
- const extensions = projectExtensionRows(plugin, resolveEnabled, bundleLocked);
24765
+ const pluginLocked = isPluginLocked(plugin.id);
24766
+ const extensions = projectExtensionRows(plugin, resolveEnabled, pluginLocked);
24306
24767
  const optional = optionalDiscoveredFields(plugin, extensions);
24307
24768
  return {
24308
24769
  id: plugin.id,
@@ -24312,7 +24773,7 @@ function buildDiscoveredItem(plugin, deps, resolveEnabled) {
24312
24773
  reason: plugin.reason ?? null,
24313
24774
  source: classifyPluginSource(plugin.path, deps),
24314
24775
  ...optional,
24315
- ...bundleLocked ? { locked: true } : {},
24776
+ ...pluginLocked ? { locked: true } : {},
24316
24777
  ...plugin.status === "disabled" ? { startsAsDisabled: true } : {}
24317
24778
  };
24318
24779
  }
@@ -24323,12 +24784,12 @@ function optionalDiscoveredFields(plugin, extensions) {
24323
24784
  if (extensions) out.extensions = extensions;
24324
24785
  return out;
24325
24786
  }
24326
- function projectExtensionRows(plugin, resolveEnabled, bundleLocked) {
24787
+ function projectExtensionRows(plugin, resolveEnabled, pluginLocked) {
24327
24788
  if (!plugin.extensions || plugin.extensions.length === 0) return void 0;
24328
24789
  return plugin.extensions.map((ext) => {
24329
24790
  const description = readInstanceDescription(ext.instance);
24330
24791
  const qualified = qualifiedExtensionId(plugin.id, ext.id);
24331
- const extLocked = bundleLocked || isPluginLocked(qualified);
24792
+ const extLocked = pluginLocked || isPluginLocked(qualified);
24332
24793
  return {
24333
24794
  id: ext.id,
24334
24795
  kind: ext.kind,
@@ -24437,28 +24898,28 @@ function validateBulkChange(change, deps) {
24437
24898
  }
24438
24899
  return null;
24439
24900
  }
24440
- const bundleId = change.id.slice(0, slash);
24901
+ const pluginId = change.id.slice(0, slash);
24441
24902
  const extensionId = change.id.slice(slash + 1);
24442
- const handle = findHandle(bundleId, deps);
24903
+ const handle = findHandle(pluginId, deps);
24443
24904
  if (!handle) {
24444
24905
  return {
24445
24906
  status: 404,
24446
24907
  code: "not-found",
24447
- message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
24908
+ message: tx(SERVER_TEXTS.pluginsUnknown, { id: pluginId })
24448
24909
  };
24449
24910
  }
24450
24911
  if (!hasExtension(handle, extensionId)) {
24451
24912
  return {
24452
24913
  status: 404,
24453
24914
  code: "not-found",
24454
- message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { bundleId, extensionId })
24915
+ message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { pluginId, extensionId })
24455
24916
  };
24456
24917
  }
24457
- if (isPluginLocked(change.id) || isPluginLocked(bundleId)) {
24918
+ if (isPluginLocked(change.id) || isPluginLocked(pluginId)) {
24458
24919
  return {
24459
24920
  status: 403,
24460
24921
  code: "locked",
24461
- message: tx(SERVER_TEXTS.pluginsExtensionLocked, { bundleId, extensionId })
24922
+ message: tx(SERVER_TEXTS.pluginsExtensionLocked, { pluginId, extensionId })
24462
24923
  };
24463
24924
  }
24464
24925
  return null;
@@ -24482,7 +24943,7 @@ function expandBulkChangeKeys(change, deps) {
24482
24943
  if (change.id.includes("/")) return [change.id];
24483
24944
  const handle = findHandle(change.id, deps);
24484
24945
  if (!handle) return [];
24485
- return bundleExtensionIds(handle).map((extId) => qualifiedExtensionId(change.id, extId)).filter((q) => !isPluginLocked(q));
24946
+ return pluginExtensionIds(handle).map((extId) => qualifiedExtensionId(change.id, extId)).filter((q) => !isPluginLocked(q));
24486
24947
  }
24487
24948
  async function buildFreshResolver2(deps) {
24488
24949
  return buildFreshResolver({
@@ -24495,21 +24956,21 @@ function composeResolver2(deps, overrides) {
24495
24956
  return composeResolver(deps.configService.effective(), overrides);
24496
24957
  }
24497
24958
  function findHandle(id, deps) {
24498
- const builtIn = builtInBundles.find((b) => b.id === id);
24499
- if (builtIn) return { kind: "built-in", bundle: builtIn };
24959
+ const builtIn = builtInPlugins.find((b) => b.id === id);
24960
+ if (builtIn) return { kind: "built-in", plugin: builtIn };
24500
24961
  const discovered = deps.pluginRuntime.discovered.find((p) => p.id === id);
24501
24962
  if (discovered) return { kind: "discovered", plugin: discovered };
24502
24963
  return null;
24503
24964
  }
24504
- function bundleExtensionIds(handle) {
24965
+ function pluginExtensionIds(handle) {
24505
24966
  if (handle.kind === "built-in") {
24506
- return handle.bundle.extensions.map((e) => e.id);
24967
+ return handle.plugin.extensions.map((e) => e.id);
24507
24968
  }
24508
24969
  return (handle.plugin.extensions ?? []).map((e) => e.id);
24509
24970
  }
24510
24971
  function hasExtension(handle, extensionId) {
24511
24972
  if (handle.kind === "built-in") {
24512
- return handle.bundle.extensions.some((e) => e.id === extensionId);
24973
+ return handle.plugin.extensions.some((e) => e.id === extensionId);
24513
24974
  }
24514
24975
  return (handle.plugin.extensions ?? []).some((e) => e.id === extensionId);
24515
24976
  }
@@ -24609,15 +25070,15 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
24609
25070
  import { HTTPException as HTTPException11 } from "hono/http-exception";
24610
25071
 
24611
25072
  // server/util/skillmapignore-io.ts
24612
- 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";
24613
25074
  import { resolve as resolve35 } from "path";
24614
25075
  var IGNORE_FILENAME2 = ".skillmapignore";
24615
25076
  function readPatterns(cwd) {
24616
25077
  const path = resolve35(cwd, IGNORE_FILENAME2);
24617
- if (!existsSync26(path)) return [];
25078
+ if (!existsSync27(path)) return [];
24618
25079
  let raw;
24619
25080
  try {
24620
- raw = readFileSync17(path, "utf8");
25081
+ raw = readFileSync18(path, "utf8");
24621
25082
  } catch {
24622
25083
  return [];
24623
25084
  }
@@ -24625,13 +25086,13 @@ function readPatterns(cwd) {
24625
25086
  }
24626
25087
  function writePatterns(cwd, nextPatterns) {
24627
25088
  const path = resolve35(cwd, IGNORE_FILENAME2);
24628
- const prior = existsSync26(path) ? safeRead(path) : "";
25089
+ const prior = existsSync27(path) ? safeRead(path) : "";
24629
25090
  const content = buildContent(prior, nextPatterns);
24630
25091
  writeFileSync2(path, content, "utf8");
24631
25092
  }
24632
25093
  function safeRead(path) {
24633
25094
  try {
24634
- return readFileSync17(path, "utf8");
25095
+ return readFileSync18(path, "utf8");
24635
25096
  } catch {
24636
25097
  return "";
24637
25098
  }
@@ -24975,7 +25436,7 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
24975
25436
  });
24976
25437
 
24977
25438
  // server/routes/active-provider.ts
24978
- import { existsSync as existsSync27 } from "fs";
25439
+ import { existsSync as existsSync28 } from "fs";
24979
25440
  import { HTTPException as HTTPException13 } from "hono/http-exception";
24980
25441
  function registerActiveProviderRoute(app, deps) {
24981
25442
  app.get("/api/active-provider", (c) => {
@@ -25008,7 +25469,7 @@ function applyLensSwitch(deps, newValue) {
25008
25469
  });
25009
25470
  }
25010
25471
  const dbPath = resolveDbPath({ db: void 0, cwd });
25011
- if (!existsSync27(dbPath)) return { dropped: null };
25472
+ if (!existsSync28(dbPath)) return { dropped: null };
25012
25473
  const dropResult = dropScanZone(dbPath);
25013
25474
  return {
25014
25475
  dropped: {
@@ -25116,7 +25577,9 @@ function createWatcherService(opts) {
25116
25577
  message: sanitizeForTerminal(outcome.message)
25117
25578
  })
25118
25579
  );
25580
+ return;
25119
25581
  }
25582
+ warnOversizedFiles(outcome.result);
25120
25583
  },
25121
25584
  onWatcherError: (message) => {
25122
25585
  log.warn(
@@ -25179,6 +25642,17 @@ function createWatcherService(opts) {
25179
25642
  }
25180
25643
  };
25181
25644
  }
25645
+ function warnOversizedFiles(result) {
25646
+ const oversized = result.oversizedFiles ?? [];
25647
+ if ((result.stats.filesOversized ?? oversized.length) <= 0) return;
25648
+ const files = oversized.map((f) => formatOversizedFilePair({ path: sanitizeForTerminal(f.path), bytes: f.bytes })).join(", ");
25649
+ log.warn(
25650
+ tx(SERVER_TEXTS.watcherFilesOversized, {
25651
+ count: String(oversized.length),
25652
+ files
25653
+ })
25654
+ );
25655
+ }
25182
25656
  function buildBroadcasterEmitter(broadcaster) {
25183
25657
  return {
25184
25658
  emit(event) {
@@ -25262,7 +25736,16 @@ async function buildBffResolverOverride(deps) {
25262
25736
  }
25263
25737
  async function loadPersistedScan(deps) {
25264
25738
  const opened = await tryWithSqlite(
25265
- { 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
+ },
25266
25749
  async (adapter) => {
25267
25750
  const [loaded, favSet] = await Promise.all([
25268
25751
  adapter.scans.load(),
@@ -25357,6 +25840,13 @@ var bffScanRunnerPrinter = {
25357
25840
  warn: (text) => log.warn(sanitizeForTerminal(text.trimEnd())),
25358
25841
  error: (text) => log.warn(sanitizeForTerminal(text.trimEnd()))
25359
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
+ };
25360
25850
  function emptyScanResult() {
25361
25851
  return {
25362
25852
  schemaVersion: 1,
@@ -25543,7 +26033,7 @@ function registerUpdateStatusRoute(app, deps) {
25543
26033
  }
25544
26034
 
25545
26035
  // server/static.ts
25546
- import { existsSync as existsSync28 } from "fs";
26036
+ import { existsSync as existsSync29 } from "fs";
25547
26037
  import { readFile as readFile6 } from "fs/promises";
25548
26038
  import { extname, join as join19 } from "path";
25549
26039
  import { serveStatic } from "@hono/node-server/serve-static";
@@ -25598,7 +26088,7 @@ function createSpaFallback(opts) {
25598
26088
  if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
25599
26089
  if (opts.uiDist === null) return htmlResponse(c, placeholder);
25600
26090
  const indexPath = join19(opts.uiDist, INDEX_HTML);
25601
- if (!existsSync28(indexPath)) return htmlResponse(c, placeholder);
26091
+ if (!existsSync29(indexPath)) return htmlResponse(c, placeholder);
25602
26092
  return fileResponse(c, indexPath);
25603
26093
  };
25604
26094
  }
@@ -26208,7 +26698,7 @@ function validateNoUi(noUi, uiDist) {
26208
26698
  }
26209
26699
 
26210
26700
  // server/paths.ts
26211
- import { existsSync as existsSync29, statSync as statSync10 } from "fs";
26701
+ import { existsSync as existsSync30, statSync as statSync10 } from "fs";
26212
26702
  import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
26213
26703
  import { fileURLToPath as fileURLToPath6 } from "url";
26214
26704
  var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
@@ -26223,10 +26713,10 @@ function resolveExplicitUiDist(ctx, raw) {
26223
26713
  return isAbsolute11(raw) ? raw : resolve37(ctx.cwd, raw);
26224
26714
  }
26225
26715
  function isUiBundleDir(path) {
26226
- if (!existsSync29(path)) return false;
26716
+ if (!existsSync30(path)) return false;
26227
26717
  try {
26228
26718
  if (!statSync10(path).isDirectory()) return false;
26229
- return existsSync29(join20(path, INDEX_HTML2));
26719
+ return existsSync30(join20(path, INDEX_HTML2));
26230
26720
  } catch {
26231
26721
  return false;
26232
26722
  }
@@ -26354,8 +26844,8 @@ function assembleKernel(pluginRuntime, noBuiltIns) {
26354
26844
  (c) => `${c.pluginId}/${c.extensionId}/${c.contributionId}`
26355
26845
  )
26356
26846
  );
26357
- for (const bundle of builtInBundles) {
26358
- for (const ext of bundle.extensions) {
26847
+ for (const plugin of builtInPlugins) {
26848
+ for (const ext of plugin.extensions) {
26359
26849
  collectViewContributions(ext.pluginId, ext.id, ext, mergedViewContributions, {
26360
26850
  excludeQualifiedIds: userKey
26361
26851
  });
@@ -26368,8 +26858,8 @@ function assembleKernel(pluginRuntime, noBuiltIns) {
26368
26858
  }
26369
26859
  function collectBuiltInProviders() {
26370
26860
  const out = [];
26371
- for (const bundle of builtInBundles) {
26372
- for (const ext of bundle.extensions) {
26861
+ for (const plugin of builtInPlugins) {
26862
+ for (const ext of plugin.extensions) {
26373
26863
  if (ext.kind === "provider") {
26374
26864
  out.push(ext);
26375
26865
  }
@@ -26520,7 +27010,14 @@ var SERVE_TEXTS = {
26520
27010
  // Shutdown trace, printed once the listener has closed. Informational
26521
27011
  // (`ℹ` cyan) per §3.1: no failure, no action; just a marker that the
26522
27012
  // long-running daemon has wound down cleanly.
26523
- 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"
26524
27021
  };
26525
27022
 
26526
27023
  // cli/util/serve-banner.ts
@@ -26747,6 +27244,9 @@ var ServeCommand = class extends SmCommand {
26747
27244
  noWatcher = Option31.Boolean("--no-watcher", false, {
26748
27245
  description: "Disable the chokidar-fed scan-and-broadcast loop. Use only for CI / read-only deployments."
26749
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
+ });
26750
27250
  // `--watcher-debounce-ms` is undocumented sugar for advanced users
26751
27251
  // who want to tighten / relax the watcher's batching window without
26752
27252
  // editing settings.json. Hidden flag, the Usage block omits it.
@@ -26781,7 +27281,7 @@ var ServeCommand = class extends SmCommand {
26781
27281
  return ExitCode.Error;
26782
27282
  }
26783
27283
  const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
26784
- if (this.db !== void 0 && !existsSync30(dbPath)) {
27284
+ if (this.db !== void 0 && !existsSync31(dbPath)) {
26785
27285
  this.printer.info(
26786
27286
  tx(SERVE_TEXTS.dbNotFound, {
26787
27287
  glyph: errGlyph,
@@ -26863,6 +27363,8 @@ var ServeCommand = class extends SmCommand {
26863
27363
  this.printer.info(formatValidationError(validation.error, stderrAnsi));
26864
27364
  return ExitCode.Error;
26865
27365
  }
27366
+ const driftAbort = await this.#rebuildOnDrift(dbPath, stderrAnsi, warnGlyph);
27367
+ if (driftAbort !== null) return driftAbort;
26866
27368
  await initSentryBff(VERSION);
26867
27369
  let handle;
26868
27370
  try {
@@ -26915,6 +27417,35 @@ var ServeCommand = class extends SmCommand {
26915
27417
  this.printer.info(tx(SERVE_TEXTS.shutdown, { glyph: infoGlyph }));
26916
27418
  return ExitCode.Ok;
26917
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
+ }
26918
27449
  };
26919
27450
  function parsePort(raw) {
26920
27451
  if (raw === void 0) return { ok: true, port: void 0 };
@@ -27130,26 +27661,34 @@ var ShowCommand = class extends SmCommand {
27130
27661
  const dbPath = resolveDbPath({ db: this.db, ...defaultRuntimeContext() });
27131
27662
  const exit = requireDbOrExit(dbPath, this.context.stderr);
27132
27663
  if (exit !== null) return exit;
27133
- return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
27134
- const bundle = await adapter.scans.findNode(this.nodePath);
27135
- if (!bundle) {
27136
- this.printer.error(tx(SHOW_TEXTS.nodeNotFound, { nodePath: this.nodePath }));
27137
- return ExitCode.NotFound;
27138
- }
27139
- const doc = {
27140
- node: bundle.node,
27141
- linksOut: bundle.linksOut,
27142
- linksIn: bundle.linksIn,
27143
- issues: bundle.issues
27144
- };
27145
- if (this.json) {
27146
- 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));
27147
27689
  return ExitCode.Ok;
27148
27690
  }
27149
- const ansi = this.ansiFor("stdout");
27150
- this.printer.data(renderHuman2(doc, ansi));
27151
- return ExitCode.Ok;
27152
- });
27691
+ );
27153
27692
  }
27154
27693
  };
27155
27694
  function renderHuman2(doc, ansi) {
@@ -28047,7 +28586,7 @@ var STUB_COMMANDS = [
28047
28586
  ];
28048
28587
 
28049
28588
  // cli/commands/tutorial.ts
28050
- 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";
28051
28590
  import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
28052
28591
  import { createInterface as createInterface5 } from "readline";
28053
28592
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -28391,7 +28930,7 @@ function resolveSkillSourceDir(variant) {
28391
28930
  resolve39(here, "../cli/tutorial", spec.slug)
28392
28931
  ];
28393
28932
  for (const candidate of candidates) {
28394
- if (existsSync31(candidate) && statSync11(candidate).isDirectory()) {
28933
+ if (existsSync32(candidate) && statSync11(candidate).isDirectory()) {
28395
28934
  cachedSourceDirs.set(variant, candidate);
28396
28935
  return candidate;
28397
28936
  }
@@ -28592,7 +29131,7 @@ function resolveBareInvocation(rawArgs) {
28592
29131
  if (first !== void 0 && first.startsWith("-") && !passthrough.has(first)) {
28593
29132
  const isSingleDashLong = !first.startsWith("--") && first.length > 2;
28594
29133
  if (isSingleDashLong) return null;
28595
- if (existsSync32(defaultProjectDbPath(defaultRuntimeContext()))) {
29134
+ if (existsSync33(defaultProjectDbPath(defaultRuntimeContext()))) {
28596
29135
  return ["serve", ...rawArgs];
28597
29136
  }
28598
29137
  return resolveBareDefault();
@@ -28601,7 +29140,7 @@ function resolveBareInvocation(rawArgs) {
28601
29140
  }
28602
29141
  function resolveBareDefault() {
28603
29142
  const ctx = defaultRuntimeContext();
28604
- if (existsSync32(defaultProjectDbPath(ctx))) {
29143
+ if (existsSync33(defaultProjectDbPath(ctx))) {
28605
29144
  return ["serve"];
28606
29145
  }
28607
29146
  const stderr = process.stderr;
@@ -28615,4 +29154,5 @@ function resolveBareDefault() {
28615
29154
  );
28616
29155
  process.exit(ExitCode.Error);
28617
29156
  }
28618
- //# sourceMappingURL=cli.js.map
29157
+ //# sourceMappingURL=cli.js.map
29158
+ //# debugId=926243f9-3707-54bc-9aa6-c68955118161