@skill-map/cli 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -225,7 +225,7 @@ var JOBS_DIRNAME = "jobs";
225
225
  var PLUGINS_DIRNAME = "plugins";
226
226
  var SETTINGS_FILENAME = "settings.json";
227
227
  var LOCAL_SETTINGS_FILENAME = "settings.local.json";
228
- var IGNORE_FILENAME = ".skill-mapignore";
228
+ var IGNORE_FILENAME = ".skillmapignore";
229
229
  var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
230
230
  var GITIGNORE_ENTRIES = [
231
231
  `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
@@ -609,7 +609,7 @@ function loadBundledIgnoreText() {
609
609
  return loadDefaultsText();
610
610
  }
611
611
  function readIgnoreFileText(scopeRoot) {
612
- const path = resolve2(scopeRoot, ".skill-mapignore");
612
+ const path = resolve2(scopeRoot, ".skillmapignore");
613
613
  if (!existsSync2(path)) return void 0;
614
614
  try {
615
615
  return readFileSync(path, "utf8");
@@ -626,11 +626,11 @@ function loadDefaultsText() {
626
626
  function readDefaultsFromDisk() {
627
627
  const here = dirname(fileURLToPath(import.meta.url));
628
628
  const candidates = [
629
- resolve2(here, "../../config/defaults/skill-mapignore"),
629
+ resolve2(here, "../../config/defaults/skillmapignore"),
630
630
  // src/kernel/scan/ → src/config/defaults/
631
- resolve2(here, "../config/defaults/skill-mapignore"),
631
+ resolve2(here, "../config/defaults/skillmapignore"),
632
632
  // dist/cli.js → dist/config/defaults/ (siblings)
633
- resolve2(here, "config/defaults/skill-mapignore")
633
+ resolve2(here, "config/defaults/skillmapignore")
634
634
  ];
635
635
  for (const candidate of candidates) {
636
636
  if (existsSync2(candidate)) {
@@ -2958,7 +2958,7 @@ var AsyncMutex = class {
2958
2958
  this.#locked = true;
2959
2959
  return;
2960
2960
  }
2961
- await new Promise((resolve21) => this.#waiters.push(resolve21));
2961
+ await new Promise((resolve23) => this.#waiters.push(resolve23));
2962
2962
  this.#locked = true;
2963
2963
  }
2964
2964
  unlock() {
@@ -7353,109 +7353,16 @@ var GraphCommand = class extends SmCommand {
7353
7353
  }
7354
7354
  };
7355
7355
 
7356
- // cli/commands/guide.ts
7357
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
7358
- import { writeFile } from "fs/promises";
7359
- import { dirname as dirname9, join as join10, resolve as resolve13 } from "path";
7360
- import { fileURLToPath as fileURLToPath5 } from "url";
7361
- import { Command as Command8, Option as Option8 } from "clipanion";
7362
-
7363
- // cli/i18n/guide.texts.ts
7364
- var GUIDE_TEXTS = {
7365
- // Success — written to stdout after `<cwd>/sm-guide.md` is created.
7366
- written: 'Listo. sm-guide.md creado en {{cwd}}. Abr\xED Claude Code ac\xE1 y decile "ejecut\xE1 @sm-guide.md" para arrancar la gu\xEDa interactiva.\n',
7367
- // Refusal — `sm-guide.md` already exists and `--force` was not set.
7368
- // Goes to stderr, exit code 2 (operational error per spec § Exit codes).
7369
- alreadyExists: "sm guide: sm-guide.md ya existe en {{cwd}}. Us\xE1 `--force` para sobrescribir.\n",
7370
- // I/O failure on write or on reading the bundled SKILL source.
7371
- writeFailed: "sm guide: no se pudo escribir sm-guide.md: {{message}}\n",
7372
- sourceMissing: "sm guide: no se pudo leer la gu\xEDa empaquetada (SKILL.md) desde la instalaci\xF3n. Reinstal\xE1 @skill-map/cli o report\xE1 el bug.\n"
7373
- };
7374
-
7375
- // cli/commands/guide.ts
7376
- var SM_GUIDE_FILENAME = "sm-guide.md";
7377
- var GuideCommand = class extends SmCommand {
7378
- static paths = [["guide"]];
7379
- static usage = Command8.Usage({
7380
- category: "Setup",
7381
- description: "Materialize the interactive tester guide (sm-guide.md) in the current directory.",
7382
- details: `
7383
- Drops the canonical SKILL.md content as ./sm-guide.md so a tester
7384
- can open Claude Code in the cwd and load the file as a skill by
7385
- typing "ejecut\xE1 @sm-guide.md". Top-level only \u2014 no subdirectory
7386
- is created.
7387
-
7388
- Does NOT require an initialized .skill-map/ project. Refuses to
7389
- overwrite an existing sm-guide.md unless --force is passed.
7390
- `,
7391
- examples: [
7392
- ["Materialize the guide in the cwd", "$0 guide"],
7393
- ["Overwrite an existing sm-guide.md", "$0 guide --force"]
7394
- ]
7395
- });
7396
- force = Option8.Boolean("--force", false, {
7397
- description: "Overwrite an existing sm-guide.md without prompting."
7398
- });
7399
- async run() {
7400
- const ctx = defaultRuntimeContext();
7401
- const target = join10(ctx.cwd, SM_GUIDE_FILENAME);
7402
- if (await pathExists(target) && !this.force) {
7403
- this.context.stderr.write(tx(GUIDE_TEXTS.alreadyExists, { cwd: ctx.cwd }));
7404
- return ExitCode.Error;
7405
- }
7406
- let body;
7407
- try {
7408
- body = loadBundledGuideText();
7409
- } catch {
7410
- this.context.stderr.write(GUIDE_TEXTS.sourceMissing);
7411
- return ExitCode.Error;
7412
- }
7413
- try {
7414
- await writeFile(target, body);
7415
- } catch (err) {
7416
- this.context.stderr.write(
7417
- tx(GUIDE_TEXTS.writeFailed, { message: formatErrorMessage(err) })
7418
- );
7419
- return ExitCode.Error;
7420
- }
7421
- this.context.stdout.write(tx(GUIDE_TEXTS.written, { cwd: ctx.cwd }));
7422
- return ExitCode.Ok;
7423
- }
7424
- };
7425
- var cachedGuide = null;
7426
- function loadBundledGuideText() {
7427
- if (cachedGuide !== null) return cachedGuide;
7428
- cachedGuide = readGuideFromDisk();
7429
- return cachedGuide;
7430
- }
7431
- function readGuideFromDisk() {
7432
- const here = dirname9(fileURLToPath5(import.meta.url));
7433
- const candidates = [
7434
- // dev: src/cli/commands/ → repo-root .claude/skills/sm-guide/SKILL.md
7435
- resolve13(here, "../../../.claude/skills/sm-guide/SKILL.md"),
7436
- // bundled: dist/cli.js → dist/cli/guide/sm-guide.md (sibling)
7437
- resolve13(here, "cli/guide/sm-guide.md"),
7438
- // bundled fallback: any-depth → cli/guide/sm-guide.md
7439
- resolve13(here, "../cli/guide/sm-guide.md")
7440
- ];
7441
- for (const candidate of candidates) {
7442
- if (existsSync12(candidate)) {
7443
- return readFileSync10(candidate, "utf8");
7444
- }
7445
- }
7446
- throw new Error(`SKILL.md not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`);
7447
- }
7448
-
7449
7356
  // cli/commands/help.ts
7450
- import { readFileSync as readFileSync11 } from "fs";
7357
+ import { readFileSync as readFileSync10 } from "fs";
7451
7358
  import { createRequire as createRequire4 } from "module";
7452
- import { resolve as resolve14 } from "path";
7453
- import { Command as Command9, Option as Option9 } from "clipanion";
7359
+ import { resolve as resolve13 } from "path";
7360
+ import { Command as Command8, Option as Option8 } from "clipanion";
7454
7361
 
7455
7362
  // package.json
7456
7363
  var package_default = {
7457
7364
  name: "@skill-map/cli",
7458
- version: "0.14.1",
7365
+ version: "0.16.0",
7459
7366
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
7460
7367
  license: "MIT",
7461
7368
  type: "module",
@@ -7514,7 +7421,7 @@ var package_default = {
7514
7421
  },
7515
7422
  dependencies: {
7516
7423
  "@hono/node-server": "2.0.1",
7517
- "@skill-map/spec": "0.14.1",
7424
+ "@skill-map/spec": "0.16.0",
7518
7425
  ajv: "8.18.0",
7519
7426
  "ajv-formats": "3.0.1",
7520
7427
  chokidar: "5.0.0",
@@ -7631,9 +7538,9 @@ var HELP_TEXTS = {
7631
7538
  };
7632
7539
 
7633
7540
  // cli/commands/help.ts
7634
- var HelpCommand = class extends Command9 {
7541
+ var HelpCommand = class extends Command8 {
7635
7542
  static paths = [["help"]];
7636
- static usage = Command9.Usage({
7543
+ static usage = Command8.Usage({
7637
7544
  category: "Introspection",
7638
7545
  description: "Self-describing introspection. --format human|md|json.",
7639
7546
  details: `
@@ -7647,8 +7554,8 @@ var HelpCommand = class extends Command9 {
7647
7554
  json \u2014 structured surface dump per spec/cli-contract.md.
7648
7555
  `
7649
7556
  });
7650
- verbParts = Option9.Rest({ required: 0 });
7651
- format = Option9.String("--format", "human");
7557
+ verbParts = Option8.Rest({ required: 0 });
7558
+ format = Option8.String("--format", "human");
7652
7559
  async execute() {
7653
7560
  const format = normalizeFormat(this.format);
7654
7561
  if (!format) {
@@ -7770,8 +7677,8 @@ function resolveSpecVersion() {
7770
7677
  try {
7771
7678
  const req = createRequire4(import.meta.url);
7772
7679
  const indexPath = req.resolve("@skill-map/spec/index.json");
7773
- const pkgPath = resolve14(indexPath, "..", "package.json");
7774
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
7680
+ const pkgPath = resolve13(indexPath, "..", "package.json");
7681
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
7775
7682
  return pkg.version;
7776
7683
  } catch {
7777
7684
  return "unknown";
@@ -7966,7 +7873,7 @@ function renderCompactOverview(verbs) {
7966
7873
  lines.push(HELP_TEXTS.compactFooter);
7967
7874
  return lines.join("\n") + "\n";
7968
7875
  }
7969
- var RootHelpCommand = class extends Command9 {
7876
+ var RootHelpCommand = class extends Command8 {
7970
7877
  static paths = [["-h"], ["--help"]];
7971
7878
  async execute() {
7972
7879
  const rawDefs = this.cli.definitions();
@@ -8022,13 +7929,13 @@ function registeredVerbPaths(cli2) {
8022
7929
  }
8023
7930
 
8024
7931
  // cli/commands/init.ts
8025
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
8026
- import { join as join11 } from "path";
8027
- import { Command as Command10, Option as Option10 } from "clipanion";
7932
+ import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
7933
+ import { join as join10 } from "path";
7934
+ import { Command as Command9, Option as Option9 } from "clipanion";
8028
7935
 
8029
7936
  // kernel/orchestrator.ts
8030
7937
  import { createHash } from "crypto";
8031
- import { existsSync as existsSync13, statSync as statSync3 } from "fs";
7938
+ import { existsSync as existsSync12, statSync as statSync3 } from "fs";
8032
7939
  import { Tiktoken } from "js-tiktoken/lite";
8033
7940
  import cl100k_base from "js-tiktoken/ranks/cl100k_base";
8034
7941
  import yaml2 from "js-yaml";
@@ -8163,7 +8070,7 @@ function validateRoots(roots) {
8163
8070
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
8164
8071
  }
8165
8072
  for (const root of roots) {
8166
- if (!existsSync13(root) || !statSync3(root).isDirectory()) {
8073
+ if (!existsSync12(root) || !statSync3(root).isDirectory()) {
8167
8074
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
8168
8075
  }
8169
8076
  }
@@ -8939,15 +8846,18 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
8939
8846
  }
8940
8847
 
8941
8848
  // kernel/scan/watcher.ts
8942
- import { resolve as resolve15, relative as relative4, sep as sep2 } from "path";
8849
+ import { resolve as resolve14, relative as relative4, sep as sep2 } from "path";
8943
8850
  import chokidar from "chokidar";
8944
8851
  function createChokidarWatcher(opts) {
8945
- const absRoots = opts.roots.map((r) => resolve15(opts.cwd, r));
8946
- const ignoreFilter = opts.ignoreFilter;
8947
- const ignored = ignoreFilter ? (path) => {
8852
+ const absRoots = opts.roots.map((r) => resolve14(opts.cwd, r));
8853
+ const ignoreFilterOpt = opts.ignoreFilter;
8854
+ const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
8855
+ const ignored = getFilter ? (path) => {
8856
+ const filter = getFilter();
8857
+ if (!filter) return false;
8948
8858
  const rel = relativePathFromRoots(path, absRoots);
8949
8859
  if (rel === null) return false;
8950
- return ignoreFilter.ignores(rel);
8860
+ return filter.ignores(rel);
8951
8861
  } : void 0;
8952
8862
  const watcher = chokidar.watch(absRoots, {
8953
8863
  ignoreInitial: true,
@@ -9188,13 +9098,13 @@ function createCliProgressEmitter(stderr) {
9188
9098
  // cli/commands/init.ts
9189
9099
  var InitCommand = class extends SmCommand {
9190
9100
  static paths = [["init"]];
9191
- static usage = Command10.Usage({
9101
+ static usage = Command9.Usage({
9192
9102
  category: "Setup",
9193
9103
  description: "Bootstrap the current scope: scaffold .skill-map/, provision DB, run first scan.",
9194
9104
  details: `
9195
9105
  Project scope (default): creates ./.skill-map/ with settings.json,
9196
9106
  settings.local.json, and skill-map.db. Drops a starter
9197
- .skill-mapignore at the scope root and appends the DB + local
9107
+ .skillmapignore at the scope root and appends the DB + local
9198
9108
  settings to .gitignore.
9199
9109
 
9200
9110
  Global scope (-g): same scaffolding under ~/.skill-map/. No
@@ -9212,16 +9122,16 @@ var InitCommand = class extends SmCommand {
9212
9122
  ["Preview what would be created", "$0 init --dry-run"]
9213
9123
  ]
9214
9124
  });
9215
- noScan = Option10.Boolean("--no-scan", false, {
9125
+ noScan = Option9.Boolean("--no-scan", false, {
9216
9126
  description: "Skip the first scan after scaffolding."
9217
9127
  });
9218
- force = Option10.Boolean("--force", false, {
9219
- description: "Overwrite an existing settings.json / settings.local.json / .skill-mapignore."
9128
+ force = Option9.Boolean("--force", false, {
9129
+ description: "Overwrite an existing settings.json / settings.local.json / .skillmapignore."
9220
9130
  });
9221
- strict = Option10.Boolean("--strict", false, {
9131
+ strict = Option9.Boolean("--strict", false, {
9222
9132
  description: "Strict mode: fail on any layered-loader warning AND promote frontmatter warnings to errors during the first scan. Same flag as sm scan / sm config."
9223
9133
  });
9224
- dryRun = Option10.Boolean("-n,--dry-run", false, {
9134
+ dryRun = Option9.Boolean("-n,--dry-run", false, {
9225
9135
  description: "Preview the scope provisioning without touching the filesystem or the DB. Honours --force for the would-overwrite preview. Skips the first scan unconditionally \u2014 dry-run never persists."
9226
9136
  });
9227
9137
  // CLI orchestrator: paths setup + dry-run branch (delegated to
@@ -9232,7 +9142,7 @@ var InitCommand = class extends SmCommand {
9232
9142
  async run() {
9233
9143
  const ctx = defaultRuntimeContext();
9234
9144
  const scopeRoot = this.global ? ctx.homedir : ctx.cwd;
9235
- const skillMapDir = join11(scopeRoot, SKILL_MAP_DIR);
9145
+ const skillMapDir = join10(scopeRoot, SKILL_MAP_DIR);
9236
9146
  const settingsPath = defaultSettingsPath(scopeRoot);
9237
9147
  const localPath = defaultLocalSettingsPath(scopeRoot);
9238
9148
  const ignorePath = defaultIgnoreFilePath(scopeRoot);
@@ -9256,17 +9166,17 @@ var InitCommand = class extends SmCommand {
9256
9166
  return ExitCode.Ok;
9257
9167
  }
9258
9168
  await mkdir2(skillMapDir, { recursive: true });
9259
- await writeFile2(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
9169
+ await writeFile(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
9260
9170
  if (!await pathExists(localPath) || this.force) {
9261
- await writeFile2(localPath, "{}\n");
9171
+ await writeFile(localPath, "{}\n");
9262
9172
  }
9263
9173
  if (!await pathExists(ignorePath) || this.force) {
9264
- await writeFile2(ignorePath, loadBundledIgnoreText());
9174
+ await writeFile(ignorePath, loadBundledIgnoreText());
9265
9175
  }
9266
9176
  if (!this.global) {
9267
9177
  const updated = await ensureGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
9268
9178
  if (updated) {
9269
- const gitignorePath = join11(scopeRoot, ".gitignore");
9179
+ const gitignorePath = join10(scopeRoot, ".gitignore");
9270
9180
  this.context.stdout.write(
9271
9181
  GITIGNORE_ENTRIES.length === 1 ? tx(INIT_TEXTS.gitignoreUpdatedSingular, { path: gitignorePath }) : tx(INIT_TEXTS.gitignoreUpdatedPlural, {
9272
9182
  path: gitignorePath,
@@ -9305,7 +9215,7 @@ async function dryRunFileMessage(path) {
9305
9215
  }
9306
9216
  async function writeDryRunGitignorePlan(stdout, scopeRoot) {
9307
9217
  const wouldAdd = await previewGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
9308
- const gitignorePath = join11(scopeRoot, ".gitignore");
9218
+ const gitignorePath = join10(scopeRoot, ".gitignore");
9309
9219
  if (wouldAdd.length === 0) {
9310
9220
  stdout.write(tx(INIT_TEXTS.dryRunWouldLeaveGitignoreUnchanged, { path: gitignorePath }));
9311
9221
  } else if (wouldAdd.length === 1) {
@@ -9380,7 +9290,7 @@ async function runFirstScan(scopeRoot, homedir3, dbPath, strict, stdout, stderr)
9380
9290
  return hasErrors ? ExitCode.Issues : ExitCode.Ok;
9381
9291
  }
9382
9292
  async function previewGitignoreEntries(scopeRoot, entries) {
9383
- const path = join11(scopeRoot, ".gitignore");
9293
+ const path = join10(scopeRoot, ".gitignore");
9384
9294
  const body = await pathExists(path) ? await readFile2(path, "utf8") : "";
9385
9295
  const present = new Set(
9386
9296
  body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
@@ -9388,7 +9298,7 @@ async function previewGitignoreEntries(scopeRoot, entries) {
9388
9298
  return entries.filter((entry) => !present.has(entry));
9389
9299
  }
9390
9300
  async function ensureGitignoreEntries(scopeRoot, entries) {
9391
- const path = join11(scopeRoot, ".gitignore");
9301
+ const path = join10(scopeRoot, ".gitignore");
9392
9302
  let body = "";
9393
9303
  if (await pathExists(path)) {
9394
9304
  body = await readFile2(path, "utf8");
@@ -9405,12 +9315,12 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
9405
9315
  present.add(entry);
9406
9316
  changed = true;
9407
9317
  }
9408
- if (changed) await writeFile2(path, body);
9318
+ if (changed) await writeFile(path, body);
9409
9319
  return changed;
9410
9320
  }
9411
9321
 
9412
9322
  // cli/commands/history.ts
9413
- import { Command as Command11, Option as Option11 } from "clipanion";
9323
+ import { Command as Command10, Option as Option10 } from "clipanion";
9414
9324
 
9415
9325
  // cli/i18n/option-validators.texts.ts
9416
9326
  var OPTION_VALIDATORS_TEXTS = {
@@ -9499,7 +9409,7 @@ function parseStatuses(input, stderr) {
9499
9409
  }
9500
9410
  var HistoryCommand = class extends SmCommand {
9501
9411
  static paths = [["history"]];
9502
- static usage = Command11.Usage({
9412
+ static usage = Command10.Usage({
9503
9413
  category: "History",
9504
9414
  description: "Filter execution records. --json emits an array conforming to execution-record.schema.json.",
9505
9415
  details: `
@@ -9519,12 +9429,12 @@ var HistoryCommand = class extends SmCommand {
9519
9429
  ["Machine-readable, scoped to one node", "$0 history -n skills/foo.md --json"]
9520
9430
  ]
9521
9431
  });
9522
- node = Option11.String("-n", { required: false });
9523
- action = Option11.String("--action", { required: false });
9524
- status = Option11.String("--status", { required: false });
9525
- since = Option11.String("--since", { required: false });
9526
- until = Option11.String("--until", { required: false });
9527
- limit = Option11.String("--limit", { required: false });
9432
+ node = Option10.String("-n", { required: false });
9433
+ action = Option10.String("--action", { required: false });
9434
+ status = Option10.String("--status", { required: false });
9435
+ since = Option10.String("--since", { required: false });
9436
+ until = Option10.String("--until", { required: false });
9437
+ limit = Option10.String("--limit", { required: false });
9528
9438
  // CLI list verb: many optional filter flags (`--node`, `--action`,
9529
9439
  // `--status`, `--since`, `--until`, `--limit`, `--json`, `--quiet`)
9530
9440
  // each adding a guarded mutation to the filter or render path. Each
@@ -9572,7 +9482,7 @@ var HistoryCommand = class extends SmCommand {
9572
9482
  };
9573
9483
  var HistoryStatsCommand = class extends SmCommand {
9574
9484
  static paths = [["history", "stats"]];
9575
- static usage = Command11.Usage({
9485
+ static usage = Command10.Usage({
9576
9486
  category: "History",
9577
9487
  description: "Aggregate counts, tokens, periods, top nodes, and error rates over state_executions. --json conforms to history-stats.schema.json.",
9578
9488
  details: `
@@ -9590,10 +9500,10 @@ var HistoryStatsCommand = class extends SmCommand {
9590
9500
  ["Top 5 nodes, JSON", "$0 history stats --top 5 --json"]
9591
9501
  ]
9592
9502
  });
9593
- since = Option11.String("--since", { required: false });
9594
- until = Option11.String("--until", { required: false });
9595
- period = Option11.String("--period", { required: false });
9596
- top = Option11.String("--top", { required: false });
9503
+ since = Option10.String("--since", { required: false });
9504
+ until = Option10.String("--until", { required: false });
9505
+ period = Option10.String("--period", { required: false });
9506
+ top = Option10.String("--top", { required: false });
9597
9507
  // CLI stats verb: range parsing + window flags + period flag + JSON
9598
9508
  // branch + per-period iteration. Each branch is a single-purpose
9599
9509
  // gate; the data work lives in `aggregateHistoryStats`.
@@ -9774,11 +9684,11 @@ function formatRow(...cols) {
9774
9684
 
9775
9685
  // cli/commands/jobs.ts
9776
9686
  import { unlink } from "fs/promises";
9777
- import { Command as Command12, Option as Option12 } from "clipanion";
9687
+ import { Command as Command11, Option as Option11 } from "clipanion";
9778
9688
 
9779
9689
  // kernel/jobs/orphan-files.ts
9780
9690
  import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
9781
- import { join as join12, resolve as resolve16 } from "path";
9691
+ import { join as join11, resolve as resolve15 } from "path";
9782
9692
  function findOrphanJobFiles(jobsDir, referencedPaths) {
9783
9693
  let entries;
9784
9694
  try {
@@ -9793,7 +9703,7 @@ function findOrphanJobFiles(jobsDir, referencedPaths) {
9793
9703
  const orphans = [];
9794
9704
  for (const name of entries) {
9795
9705
  if (!name.endsWith(".md")) continue;
9796
- const abs = resolve16(join12(jobsDir, name));
9706
+ const abs = resolve15(join11(jobsDir, name));
9797
9707
  if (!referencedPaths.has(abs)) orphans.push(abs);
9798
9708
  }
9799
9709
  orphans.sort();
@@ -9822,7 +9732,7 @@ var JOBS_TEXTS = {
9822
9732
  // cli/commands/jobs.ts
9823
9733
  var JobPruneCommand = class extends SmCommand {
9824
9734
  static paths = [["job", "prune"]];
9825
- static usage = Command12.Usage({
9735
+ static usage = Command11.Usage({
9826
9736
  category: "Jobs",
9827
9737
  description: "Retention GC for completed / failed jobs (per config policy). --orphan-files removes MD files with no DB row.",
9828
9738
  details: `
@@ -9849,10 +9759,10 @@ var JobPruneCommand = class extends SmCommand {
9849
9759
  ["Preview without touching the DB", "$0 job prune --dry-run --json"]
9850
9760
  ]
9851
9761
  });
9852
- orphanFiles = Option12.Boolean("--orphan-files", false, {
9762
+ orphanFiles = Option11.Boolean("--orphan-files", false, {
9853
9763
  description: "Also remove MD files in .skill-map/jobs/ that have no matching state_jobs row."
9854
9764
  });
9855
- dryRun = Option12.Boolean("-n,--dry-run", false, {
9765
+ dryRun = Option11.Boolean("-n,--dry-run", false, {
9856
9766
  description: "Report what would be pruned without touching the DB or filesystem."
9857
9767
  });
9858
9768
  async run() {
@@ -9969,7 +9879,7 @@ function formatPolicy(seconds) {
9969
9879
  }
9970
9880
 
9971
9881
  // cli/commands/list.ts
9972
- import { Command as Command13, Option as Option13 } from "clipanion";
9882
+ import { Command as Command12, Option as Option12 } from "clipanion";
9973
9883
 
9974
9884
  // cli/i18n/list.texts.ts
9975
9885
  var LIST_TEXTS = {
@@ -9997,7 +9907,7 @@ var SORT_BY = {
9997
9907
  var PATH_COL_WIDTH = 50;
9998
9908
  var ListCommand = class extends SmCommand {
9999
9909
  static paths = [["list"]];
10000
- static usage = Command13.Usage({
9910
+ static usage = Command12.Usage({
10001
9911
  category: "Browse",
10002
9912
  description: "Tabular listing of nodes. --json emits an array conforming to node.schema.json.",
10003
9913
  details: `
@@ -10018,10 +9928,10 @@ var ListCommand = class extends SmCommand {
10018
9928
  ["Only nodes with issues, machine-readable", "$0 list --issue --json"]
10019
9929
  ]
10020
9930
  });
10021
- kind = Option13.String("--kind", { required: false });
10022
- issue = Option13.Boolean("--issue", false);
10023
- sortBy = Option13.String("--sort-by", { required: false });
10024
- limit = Option13.String("--limit", { required: false });
9931
+ kind = Option12.String("--kind", { required: false });
9932
+ issue = Option12.Boolean("--issue", false);
9933
+ sortBy = Option12.String("--sort-by", { required: false });
9934
+ limit = Option12.String("--limit", { required: false });
10025
9935
  async run() {
10026
9936
  let sortColumn = "path";
10027
9937
  let sortDirection = "asc";
@@ -10123,7 +10033,7 @@ function formatRow2(path, kind, out, inCount, ext, issues, bytes) {
10123
10033
  }
10124
10034
 
10125
10035
  // cli/commands/orphans.ts
10126
- import { Command as Command14, Option as Option14 } from "clipanion";
10036
+ import { Command as Command13, Option as Option13 } from "clipanion";
10127
10037
 
10128
10038
  // cli/i18n/orphans.texts.ts
10129
10039
  var ORPHANS_TEXTS = {
@@ -10178,7 +10088,7 @@ function isStringArray(v) {
10178
10088
  }
10179
10089
  var OrphansCommand = class extends SmCommand {
10180
10090
  static paths = [["orphans"]];
10181
- static usage = Command14.Usage({
10091
+ static usage = Command13.Usage({
10182
10092
  category: "Browse",
10183
10093
  description: "List orphan / auto-rename issues from the last scan. --json emits an array conforming to issue.schema.json.",
10184
10094
  details: `
@@ -10193,7 +10103,7 @@ var OrphansCommand = class extends SmCommand {
10193
10103
  ["Just the ambiguous ones, JSON", "$0 orphans --kind ambiguous --json"]
10194
10104
  ]
10195
10105
  });
10196
- kind = Option14.String("--kind", { required: false });
10106
+ kind = Option13.String("--kind", { required: false });
10197
10107
  async run() {
10198
10108
  let ruleFilter = null;
10199
10109
  if (this.kind !== void 0) {
@@ -10231,7 +10141,7 @@ var OrphansCommand = class extends SmCommand {
10231
10141
  };
10232
10142
  var OrphansReconcileCommand = class extends SmCommand {
10233
10143
  static paths = [["orphans", "reconcile"]];
10234
- static usage = Command14.Usage({
10144
+ static usage = Command13.Usage({
10235
10145
  category: "Browse",
10236
10146
  description: "Migrate state_* FKs from an orphan path to a live node, resolving the orphan issue.",
10237
10147
  details: `
@@ -10247,9 +10157,9 @@ var OrphansReconcileCommand = class extends SmCommand {
10247
10157
  ["Reattach orphan history", "$0 orphans reconcile skills/old.md --to skills/new.md"]
10248
10158
  ]
10249
10159
  });
10250
- orphanPath = Option14.String({ required: true });
10251
- to = Option14.String("--to", { required: true });
10252
- dryRun = Option14.Boolean("-n,--dry-run", false);
10160
+ orphanPath = Option13.String({ required: true });
10161
+ to = Option13.String("--to", { required: true });
10162
+ dryRun = Option13.Boolean("-n,--dry-run", false);
10253
10163
  async run() {
10254
10164
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
10255
10165
  if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
@@ -10319,7 +10229,7 @@ var OrphansReconcileCommand = class extends SmCommand {
10319
10229
  };
10320
10230
  var OrphansUndoRenameCommand = class extends SmCommand {
10321
10231
  static paths = [["orphans", "undo-rename"]];
10322
- static usage = Command14.Usage({
10232
+ static usage = Command13.Usage({
10323
10233
  category: "Browse",
10324
10234
  description: "Reverse a medium- or ambiguous-confidence auto-rename. Migrates state_* FKs back, emits a new orphan on the prior path.",
10325
10235
  details: `
@@ -10339,10 +10249,10 @@ var OrphansUndoRenameCommand = class extends SmCommand {
10339
10249
  ["Undo an ambiguous, picking a candidate", "$0 orphans undo-rename skills/new.md --from skills/old-a.md"]
10340
10250
  ]
10341
10251
  });
10342
- newPath = Option14.String({ required: true });
10343
- from = Option14.String("--from", { required: false });
10344
- force = Option14.Boolean("--force", false);
10345
- dryRun = Option14.Boolean("-n,--dry-run", false);
10252
+ newPath = Option13.String({ required: true });
10253
+ from = Option13.String("--from", { required: false });
10254
+ force = Option13.Boolean("--force", false);
10255
+ dryRun = Option13.Boolean("-n,--dry-run", false);
10346
10256
  async run() {
10347
10257
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
10348
10258
  if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
@@ -10506,9 +10416,9 @@ var ORPHANS_COMMANDS = [
10506
10416
  ];
10507
10417
 
10508
10418
  // cli/commands/plugins.ts
10509
- import { existsSync as existsSync14 } from "fs";
10510
- import { join as join13, resolve as resolve17 } from "path";
10511
- import { Command as Command15, Option as Option15 } from "clipanion";
10419
+ import { existsSync as existsSync13 } from "fs";
10420
+ import { join as join12, resolve as resolve16 } from "path";
10421
+ import { Command as Command14, Option as Option14 } from "clipanion";
10512
10422
 
10513
10423
  // cli/i18n/plugins.texts.ts
10514
10424
  var PLUGINS_TEXTS = {
@@ -10555,8 +10465,8 @@ var PLUGINS_TEXTS = {
10555
10465
  rowStatusOkPad: "ok ",
10556
10466
  rowStatusOffPad: "off ",
10557
10467
  builtInBundleHeader: "{{status}} {{id}}@built-in (granularity={{granularity}})",
10558
- builtInBundleKindsLine: " {{kinds}}",
10559
- builtInExtensionRow: " {{stat}} {{kind}}:{{qualifiedId}}@{{version}}",
10468
+ builtInBundleKindsLine: " {{kinds}}",
10469
+ builtInExtensionRow: "{{stat}} {{kind}}:{{qualifiedId}}@{{version}}",
10560
10470
  pluginRow: "{{statusIcon}} {{id}}@{{version}}{{granularitySuffix}}{{tail}}",
10561
10471
  pluginRowGranularitySuffix: " (granularity={{granularity}})",
10562
10472
  pluginRowTailEnabled: " \xB7 {{kinds}}",
@@ -10584,7 +10494,7 @@ var PLUGINS_TEXTS = {
10584
10494
 
10585
10495
  // cli/commands/plugins.ts
10586
10496
  function resolveSearchPaths2(opts, cwd, homedir3) {
10587
- if (opts.pluginDir) return [resolve17(opts.pluginDir)];
10497
+ if (opts.pluginDir) return [resolve16(opts.pluginDir)];
10588
10498
  const ctx = { cwd, homedir: homedir3 };
10589
10499
  const project = defaultProjectPluginsDir(ctx);
10590
10500
  const user = defaultUserPluginsDir(ctx);
@@ -10653,12 +10563,12 @@ function builtInRows(resolveEnabled) {
10653
10563
  }
10654
10564
  var PluginsListCommand = class extends SmCommand {
10655
10565
  static paths = [["plugins", "list"]];
10656
- static usage = Command15.Usage({
10566
+ static usage = Command14.Usage({
10657
10567
  category: "Plugins",
10658
10568
  description: "List discovered plugins and their load status.",
10659
10569
  details: "Scans <scope>/.skill-map/plugins and ~/.skill-map/plugins (or --plugin-dir <path>). Built-in bundles (claude, core) are listed alongside user plugins."
10660
10570
  });
10661
- pluginDir = Option15.String("--plugin-dir", { required: false });
10571
+ pluginDir = Option14.String("--plugin-dir", { required: false });
10662
10572
  async run() {
10663
10573
  const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
10664
10574
  const resolveEnabled = await buildResolver(this.global);
@@ -10688,13 +10598,15 @@ function renderBuiltInBundleRow(bundle) {
10688
10598
  })
10689
10599
  );
10690
10600
  if (bundle.granularity === "bundle") {
10691
- const kinds = bundle.extensions.map((e) => `${e.kind}:${qualifiedExtensionId(bundle.id, e.id)}`).join(", ");
10692
- lines.push(tx(PLUGINS_TEXTS.builtInBundleKindsLine, { kinds }));
10601
+ for (const ext of bundle.extensions) {
10602
+ const line = `${ext.kind}:${qualifiedExtensionId(bundle.id, ext.id)}@${ext.version}`;
10603
+ lines.push(tx(PLUGINS_TEXTS.builtInBundleKindsLine, { kinds: line }));
10604
+ }
10693
10605
  } else {
10694
10606
  for (const ext of bundle.extensions) {
10695
10607
  lines.push(
10696
10608
  tx(PLUGINS_TEXTS.builtInExtensionRow, {
10697
- stat: ext.enabled ? PLUGINS_TEXTS.rowStatusOkPad : PLUGINS_TEXTS.rowStatusOffPad,
10609
+ stat: ext.enabled ? PLUGINS_TEXTS.rowStatusOk : PLUGINS_TEXTS.rowStatusOff,
10698
10610
  kind: ext.kind,
10699
10611
  qualifiedId: qualifiedExtensionId(bundle.id, ext.id),
10700
10612
  version: ext.version
@@ -10720,12 +10632,12 @@ function renderPluginRow(p) {
10720
10632
  }
10721
10633
  var PluginsShowCommand = class extends SmCommand {
10722
10634
  static paths = [["plugins", "show"]];
10723
- static usage = Command15.Usage({
10635
+ static usage = Command14.Usage({
10724
10636
  category: "Plugins",
10725
10637
  description: "Show a single plugin's manifest + loaded extensions."
10726
10638
  });
10727
- id = Option15.String({ required: true });
10728
- pluginDir = Option15.String("--plugin-dir", { required: false });
10639
+ id = Option14.String({ required: true });
10640
+ pluginDir = Option14.String("--plugin-dir", { required: false });
10729
10641
  async run() {
10730
10642
  const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
10731
10643
  const resolveEnabled = await buildResolver(this.global);
@@ -10902,7 +10814,7 @@ function appendUnknownKindWarnings(out, extractorQualifiedId, applicableKinds, k
10902
10814
  }
10903
10815
  function expandHome(p, homedir3) {
10904
10816
  if (p === "~") return homedir3;
10905
- if (p.startsWith("~/")) return join13(homedir3, p.slice(2));
10817
+ if (p.startsWith("~/")) return join12(homedir3, p.slice(2));
10906
10818
  return p;
10907
10819
  }
10908
10820
  function collectExplorationDirWarnings(plugins, homedir3) {
@@ -10911,7 +10823,7 @@ function collectExplorationDirWarnings(plugins, homedir3) {
10911
10823
  const dir = instance["explorationDir"];
10912
10824
  if (typeof dir !== "string" || dir.length === 0) return;
10913
10825
  const resolved = expandHome(dir, homedir3);
10914
- if (!existsSync14(resolved)) {
10826
+ if (!existsSync13(resolved)) {
10915
10827
  out.push({
10916
10828
  providerQualifiedId: qualifiedExtensionId(pluginId, id),
10917
10829
  explorationDir: dir,
@@ -10923,12 +10835,12 @@ function collectExplorationDirWarnings(plugins, homedir3) {
10923
10835
  }
10924
10836
  var PluginsDoctorCommand = class extends SmCommand {
10925
10837
  static paths = [["plugins", "doctor"]];
10926
- static usage = Command15.Usage({
10838
+ static usage = Command14.Usage({
10927
10839
  category: "Plugins",
10928
10840
  description: "Run the full load pass and summarise by failure mode.",
10929
10841
  details: "Exit code 0 when every plugin loads or is intentionally disabled; 1 when any plugin is in an error / incompat state."
10930
10842
  });
10931
- pluginDir = Option15.String("--plugin-dir", { required: false });
10843
+ pluginDir = Option14.String("--plugin-dir", { required: false });
10932
10844
  // Doctor verb: counts by status + applicableKinds warnings +
10933
10845
  // explorationDir warnings + bad-plugins issues, each with its own
10934
10846
  // gated render. Branching is intrinsic to the multi-section diagnostic
@@ -11094,8 +11006,8 @@ function resolveToggleTarget(id, catalogue, verb) {
11094
11006
  return { key: bundle.id };
11095
11007
  }
11096
11008
  var TogglePluginsBase = class extends SmCommand {
11097
- all = Option15.Boolean("--all", false);
11098
- id = Option15.String({ required: false });
11009
+ all = Option14.Boolean("--all", false);
11010
+ id = Option14.String({ required: false });
11099
11011
  // eslint-disable-next-line complexity
11100
11012
  async toggle(enabled) {
11101
11013
  const verb = enabled ? "enable" : "disable";
@@ -11146,7 +11058,7 @@ var TogglePluginsBase = class extends SmCommand {
11146
11058
  };
11147
11059
  var PluginsEnableCommand = class extends TogglePluginsBase {
11148
11060
  static paths = [["plugins", "enable"]];
11149
- static usage = Command15.Usage({
11061
+ static usage = Command14.Usage({
11150
11062
  category: "Plugins",
11151
11063
  description: "Enable a plugin (or --all). Persists in config_plugins.",
11152
11064
  details: `
@@ -11168,7 +11080,7 @@ var PluginsEnableCommand = class extends TogglePluginsBase {
11168
11080
  };
11169
11081
  var PluginsDisableCommand = class extends TogglePluginsBase {
11170
11082
  static paths = [["plugins", "disable"]];
11171
- static usage = Command15.Usage({
11083
+ static usage = Command14.Usage({
11172
11084
  category: "Plugins",
11173
11085
  description: "Disable a plugin (or --all). Persists in config_plugins; does not delete files.",
11174
11086
  details: `
@@ -11204,8 +11116,8 @@ var PLUGIN_COMMANDS = [
11204
11116
 
11205
11117
  // cli/commands/refresh.ts
11206
11118
  import { readFile as readFile3 } from "fs/promises";
11207
- import { resolve as resolve19 } from "path";
11208
- import { Command as Command16, Option as Option16 } from "clipanion";
11119
+ import { resolve as resolve18 } from "path";
11120
+ import { Command as Command15, Option as Option15 } from "clipanion";
11209
11121
 
11210
11122
  // cli/i18n/refresh.texts.ts
11211
11123
  var REFRESH_TEXTS = {
@@ -11234,12 +11146,12 @@ var REFRESH_TEXTS = {
11234
11146
  };
11235
11147
 
11236
11148
  // cli/util/path-guard.ts
11237
- import { isAbsolute as isAbsolute3, resolve as resolve18, sep as sep3 } from "path";
11149
+ import { isAbsolute as isAbsolute3, resolve as resolve17, sep as sep3 } from "path";
11238
11150
  function assertContained2(cwd, rel) {
11239
11151
  if (isAbsolute3(rel)) {
11240
11152
  throw new Error(`node path is absolute, refusing to read: ${rel}`);
11241
11153
  }
11242
- const abs = resolve18(cwd, rel);
11154
+ const abs = resolve17(cwd, rel);
11243
11155
  if (abs !== cwd && !abs.startsWith(cwd + sep3)) {
11244
11156
  throw new Error(`node path escapes repo root: ${rel}`);
11245
11157
  }
@@ -11248,7 +11160,7 @@ function assertContained2(cwd, rel) {
11248
11160
  // cli/commands/refresh.ts
11249
11161
  var RefreshCommand = class extends SmCommand {
11250
11162
  static paths = [["refresh"]];
11251
- static usage = Command16.Usage({
11163
+ static usage = Command15.Usage({
11252
11164
  category: "Scan",
11253
11165
  description: "Refresh enrichment rows: granular (single node) or batch (every stale row).",
11254
11166
  details: `
@@ -11273,11 +11185,11 @@ var RefreshCommand = class extends SmCommand {
11273
11185
  ["Refresh every node with stale enrichments", "$0 refresh --stale"]
11274
11186
  ]
11275
11187
  });
11276
- nodePath = Option16.String({ name: "node", required: false });
11277
- stale = Option16.Boolean("--stale", false, {
11188
+ nodePath = Option15.String({ name: "node", required: false });
11189
+ stale = Option15.Boolean("--stale", false, {
11278
11190
  description: "Refresh every node whose probabilistic enrichment row is flagged stale=1."
11279
11191
  });
11280
- noPlugins = Option16.Boolean("--no-plugins", false, {
11192
+ noPlugins = Option15.Boolean("--no-plugins", false, {
11281
11193
  description: "Skip drop-in plugin discovery; use only the built-in extractor set."
11282
11194
  });
11283
11195
  // The remaining cyclomatic count comes from CLI ergonomics that don't
@@ -11413,7 +11325,7 @@ var RefreshCommand = class extends SmCommand {
11413
11325
  let body;
11414
11326
  try {
11415
11327
  assertContained2(cwd, node.path);
11416
- const raw = await readFile3(resolve19(cwd, node.path), "utf8");
11328
+ const raw = await readFile3(resolve18(cwd, node.path), "utf8");
11417
11329
  body = stripFrontmatterFence(raw);
11418
11330
  } catch (err) {
11419
11331
  this.context.stderr.write(
@@ -11462,7 +11374,7 @@ function stripFrontmatterFence(text) {
11462
11374
  var REFRESH_COMMANDS = [RefreshCommand];
11463
11375
 
11464
11376
  // cli/commands/scan.ts
11465
- import { Command as Command18, Option as Option18 } from "clipanion";
11377
+ import { Command as Command17, Option as Option17 } from "clipanion";
11466
11378
 
11467
11379
  // cli/i18n/scan.texts.ts
11468
11380
  var SCAN_TEXTS = {
@@ -11654,7 +11566,8 @@ async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
11654
11566
  }
11655
11567
 
11656
11568
  // cli/commands/watch.ts
11657
- import { Command as Command17, Option as Option17 } from "clipanion";
11569
+ import { resolve as resolve19 } from "path";
11570
+ import { Command as Command16, Option as Option16 } from "clipanion";
11658
11571
 
11659
11572
  // cli/i18n/watch.texts.ts
11660
11573
  var WATCH_TEXTS = {
@@ -11677,20 +11590,24 @@ async function runWatchLoop(opts) {
11677
11590
  const { context } = opts;
11678
11591
  const runtimeCtx = defaultRuntimeContext();
11679
11592
  const { cwd } = runtimeCtx;
11593
+ const loadEffectiveConfig = () => loadConfig({ scope: "project", strict: opts.strict, ...runtimeCtx }).effective;
11594
+ const buildCurrentIgnoreFilter = (cfgIn) => {
11595
+ const text = readIgnoreFileText(cwd);
11596
+ const filterOpts = {};
11597
+ if (cfgIn.ignore.length > 0) filterOpts.configIgnore = cfgIn.ignore;
11598
+ if (text !== void 0) filterOpts.ignoreFileText = text;
11599
+ return buildIgnoreFilter(filterOpts);
11600
+ };
11680
11601
  let cfg;
11681
11602
  try {
11682
- cfg = loadConfig({ scope: "project", strict: opts.strict, ...runtimeCtx }).effective;
11603
+ cfg = loadEffectiveConfig();
11683
11604
  } catch (err) {
11684
11605
  const message = formatErrorMessage(err);
11685
11606
  context.stderr.write(tx(WATCH_TEXTS.configLoadFailure, { message }));
11686
11607
  return ExitCode.Error;
11687
11608
  }
11688
- const ignoreFileText = readIgnoreFileText(cwd);
11689
- const ignoreFilterOpts = {};
11690
- if (cfg.ignore.length > 0) ignoreFilterOpts.configIgnore = cfg.ignore;
11691
- if (ignoreFileText !== void 0) ignoreFilterOpts.ignoreFileText = ignoreFileText;
11692
- const ignoreFilter = buildIgnoreFilter(ignoreFilterOpts);
11693
- const strict = opts.strict || cfg.scan.strict === true;
11609
+ let ignoreFilter = buildCurrentIgnoreFilter(cfg);
11610
+ let strict = opts.strict || cfg.scan.strict === true;
11694
11611
  const debounceMs = cfg.scan.watch.debounceMs;
11695
11612
  const dbPath = defaultProjectDbPath(runtimeCtx);
11696
11613
  const pluginRuntime = opts.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: "project" });
@@ -11799,7 +11716,11 @@ async function runWatchLoop(opts) {
11799
11716
  roots: opts.roots,
11800
11717
  cwd,
11801
11718
  debounceMs,
11802
- ignoreFilter,
11719
+ // Pass a getter, NOT the filter directly: the meta-file watcher
11720
+ // below mutates `ignoreFilter` after a `.skillmapignore` /
11721
+ // `.skill-map/settings.json` edit, and chokidar's `ignored`
11722
+ // predicate must read the current value on every event.
11723
+ ignoreFilter: () => ignoreFilter,
11803
11724
  onBatch: async () => {
11804
11725
  const next = await handleBatch();
11805
11726
  if (next === "stop") {
@@ -11811,6 +11732,29 @@ async function runWatchLoop(opts) {
11811
11732
  context.stderr.write(tx(WATCH_TEXTS.watcherError, { message: err.message }));
11812
11733
  }
11813
11734
  });
11735
+ const metaWatcher = createChokidarWatcher({
11736
+ roots: [
11737
+ resolve19(cwd, ".skillmapignore"),
11738
+ resolve19(cwd, ".skill-map", "settings.json")
11739
+ ],
11740
+ cwd,
11741
+ debounceMs,
11742
+ onBatch: async () => {
11743
+ if (stopRequested) return;
11744
+ try {
11745
+ cfg = loadEffectiveConfig();
11746
+ ignoreFilter = buildCurrentIgnoreFilter(cfg);
11747
+ strict = opts.strict || cfg.scan.strict === true;
11748
+ await handleBatch();
11749
+ } catch (err) {
11750
+ const message = formatErrorMessage(err);
11751
+ context.stderr.write(tx(WATCH_TEXTS.batchFailed, { message }));
11752
+ }
11753
+ },
11754
+ onError: (err) => {
11755
+ context.stderr.write(tx(WATCH_TEXTS.watcherError, { message: err.message }));
11756
+ }
11757
+ });
11814
11758
  const onSignal = () => {
11815
11759
  if (stopRequested) return;
11816
11760
  stopRequested = true;
@@ -11819,12 +11763,14 @@ async function runWatchLoop(opts) {
11819
11763
  process.once("SIGINT", onSignal);
11820
11764
  process.once("SIGTERM", onSignal);
11821
11765
  await watcher.ready;
11766
+ await metaWatcher.ready;
11822
11767
  if (!opts.json) {
11823
11768
  context.stderr.write(WATCH_TEXTS.ready);
11824
11769
  }
11825
11770
  await stopped;
11826
11771
  process.removeListener("SIGINT", onSignal);
11827
11772
  process.removeListener("SIGTERM", onSignal);
11773
+ await metaWatcher.close();
11828
11774
  await watcher.close();
11829
11775
  if (!opts.json) {
11830
11776
  context.stderr.write(tx(WATCH_TEXTS.stopped, { batchCount }));
@@ -11833,13 +11779,13 @@ async function runWatchLoop(opts) {
11833
11779
  }
11834
11780
  var WatchCommand = class extends SmCommand {
11835
11781
  static paths = [["watch"]];
11836
- static usage = Command17.Usage({
11782
+ static usage = Command16.Usage({
11837
11783
  category: "Scan",
11838
11784
  description: "Watch roots and run an incremental scan after each debounced batch of filesystem events.",
11839
11785
  details: `
11840
11786
  Long-running version of 'sm scan --changed'. Subscribes to the
11841
11787
  given roots via chokidar, applies the same ignore chain
11842
- (.skill-mapignore + config.ignore + bundled defaults), and
11788
+ (.skillmapignore + config.ignore + bundled defaults), and
11843
11789
  triggers an incremental scan after each debounced batch.
11844
11790
 
11845
11791
  Default debounce is 300ms; configure via 'scan.watch.debounceMs'
@@ -11857,17 +11803,17 @@ var WatchCommand = class extends SmCommand {
11857
11803
  ["Stream ScanResult per batch as ndjson", "$0 watch --json"]
11858
11804
  ]
11859
11805
  });
11860
- roots = Option17.Rest({ name: "roots" });
11861
- noTokens = Option17.Boolean("--no-tokens", false, {
11806
+ roots = Option16.Rest({ name: "roots" });
11807
+ noTokens = Option16.Boolean("--no-tokens", false, {
11862
11808
  description: "Skip per-node token counts (cl100k_base BPE)."
11863
11809
  });
11864
- strict = Option17.Boolean("--strict", false, {
11810
+ strict = Option16.Boolean("--strict", false, {
11865
11811
  description: "Promote frontmatter-validation findings from warn to error inside each batch. Does not change the watcher exit code."
11866
11812
  });
11867
- noPlugins = Option17.Boolean("--no-plugins", false, {
11813
+ noPlugins = Option16.Boolean("--no-plugins", false, {
11868
11814
  description: "Skip drop-in plugin discovery for the watcher session."
11869
11815
  });
11870
- maxConsecutiveFailures = Option17.String("--max-consecutive-failures", {
11816
+ maxConsecutiveFailures = Option16.String("--max-consecutive-failures", {
11871
11817
  required: false,
11872
11818
  description: "Shut down with exit 2 after N consecutive batch failures (default 5; 0 disables the breaker)."
11873
11819
  });
@@ -11905,7 +11851,7 @@ function parseBreakerLimit(raw, stderr) {
11905
11851
  // cli/commands/scan.ts
11906
11852
  var ScanCommand = class extends SmCommand {
11907
11853
  static paths = [["scan"]];
11908
- static usage = Command18.Usage({
11854
+ static usage = Command17.Usage({
11909
11855
  category: "Scan",
11910
11856
  description: "Scan roots for markdown nodes, run extractors and rules.",
11911
11857
  details: `
@@ -11934,29 +11880,29 @@ var ScanCommand = class extends SmCommand {
11934
11880
  ["What would the next incremental scan persist?", "$0 scan --changed -n --json"]
11935
11881
  ]
11936
11882
  });
11937
- roots = Option18.Rest({ name: "roots" });
11938
- noBuiltIns = Option18.Boolean("--no-built-ins", false, {
11883
+ roots = Option17.Rest({ name: "roots" });
11884
+ noBuiltIns = Option17.Boolean("--no-built-ins", false, {
11939
11885
  description: "Skip the built-in extension set. Yields a zero-filled ScanResult (kernel-empty-boot parity); skips DB persistence."
11940
11886
  });
11941
- noPlugins = Option18.Boolean("--no-plugins", false, {
11887
+ noPlugins = Option17.Boolean("--no-plugins", false, {
11942
11888
  description: "Skip drop-in plugin discovery. Only the built-in set runs. Combine with --no-built-ins for a fully empty pipeline."
11943
11889
  });
11944
- noTokens = Option18.Boolean("--no-tokens", false, {
11890
+ noTokens = Option17.Boolean("--no-tokens", false, {
11945
11891
  description: "Skip per-node token counts (cl100k_base BPE). Leaves node.tokens undefined; spec-valid since the field is optional."
11946
11892
  });
11947
- dryRun = Option18.Boolean("-n,--dry-run", false, {
11893
+ dryRun = Option17.Boolean("-n,--dry-run", false, {
11948
11894
  description: "Run the scan in memory and skip every DB write. Combined with --changed, still opens the DB read-side to load the prior snapshot."
11949
11895
  });
11950
- changed = Option18.Boolean("--changed", false, {
11896
+ changed = Option17.Boolean("--changed", false, {
11951
11897
  description: "Incremental scan: reuse unchanged nodes from the persisted prior snapshot. Degrades to a full scan if no prior snapshot exists."
11952
11898
  });
11953
- allowEmpty = Option18.Boolean("--allow-empty", false, {
11899
+ allowEmpty = Option17.Boolean("--allow-empty", false, {
11954
11900
  description: "Allow a zero-result scan to wipe an already-populated DB (replace-all replace by zero rows). Off by default to avoid the typo-trap where an invalid root silently clears your data."
11955
11901
  });
11956
- strict = Option18.Boolean("--strict", false, {
11902
+ strict = Option17.Boolean("--strict", false, {
11957
11903
  description: "Promote frontmatter-validation findings from warn to error (exit code 1 on any violation). Overrides scan.strict from config when both are set."
11958
11904
  });
11959
- watch = Option18.Boolean("--watch", false, {
11905
+ watch = Option17.Boolean("--watch", false, {
11960
11906
  description: "Long-running mode: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`."
11961
11907
  });
11962
11908
  async run() {
@@ -12054,11 +12000,11 @@ var ScanCommand = class extends SmCommand {
12054
12000
  };
12055
12001
 
12056
12002
  // cli/commands/scan-compare.ts
12057
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
12058
- import { Command as Command19, Option as Option19 } from "clipanion";
12003
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
12004
+ import { Command as Command18, Option as Option18 } from "clipanion";
12059
12005
  var ScanCompareCommand = class extends SmCommand {
12060
12006
  static paths = [["scan", "compare-with"]];
12061
- static usage = Command19.Usage({
12007
+ static usage = Command18.Usage({
12062
12008
  category: "Scan",
12063
12009
  description: "Run a fresh scan in memory and emit a delta against the saved ScanResult dump at <dump>. Read-only.",
12064
12010
  details: `
@@ -12086,15 +12032,15 @@ var ScanCompareCommand = class extends SmCommand {
12086
12032
  ["JSON output for tooling", "$0 scan compare-with baseline.json --json"]
12087
12033
  ]
12088
12034
  });
12089
- dump = Option19.String({ required: true });
12090
- roots = Option19.Rest({ name: "roots" });
12091
- noTokens = Option19.Boolean("--no-tokens", false, {
12035
+ dump = Option18.String({ required: true });
12036
+ roots = Option18.Rest({ name: "roots" });
12037
+ noTokens = Option18.Boolean("--no-tokens", false, {
12092
12038
  description: "Skip per-node token counts during the fresh scan."
12093
12039
  });
12094
- strict = Option19.Boolean("--strict", false, {
12040
+ strict = Option18.Boolean("--strict", false, {
12095
12041
  description: "Promote layered-config warnings and frontmatter-validation findings from warn to error."
12096
12042
  });
12097
- noPlugins = Option19.Boolean("--no-plugins", false, {
12043
+ noPlugins = Option18.Boolean("--no-plugins", false, {
12098
12044
  description: "Skip drop-in plugin discovery."
12099
12045
  });
12100
12046
  // Cyclomatic count comes from CLI ergonomics: 3 distinct try/catch
@@ -12165,12 +12111,12 @@ var ScanCompareCommand = class extends SmCommand {
12165
12111
  }
12166
12112
  };
12167
12113
  function loadAndValidateDump(path) {
12168
- if (!existsSync15(path)) {
12114
+ if (!existsSync14(path)) {
12169
12115
  throw new Error(tx(SCAN_TEXTS.compareDumpNotFound, { path }));
12170
12116
  }
12171
12117
  let raw;
12172
12118
  try {
12173
- raw = readFileSync12(path, "utf8");
12119
+ raw = readFileSync11(path, "utf8");
12174
12120
  } catch (err) {
12175
12121
  const message = formatErrorMessage(err);
12176
12122
  throw new Error(tx(SCAN_TEXTS.compareDumpReadFailed, { path, message }), { cause: err });
@@ -12279,8 +12225,8 @@ function renderDeltaIssues(issues) {
12279
12225
 
12280
12226
  // cli/commands/serve.ts
12281
12227
  import { spawn } from "child_process";
12282
- import { existsSync as existsSync19 } from "fs";
12283
- import { Command as Command20, Option as Option20 } from "clipanion";
12228
+ import { existsSync as existsSync18 } from "fs";
12229
+ import { Command as Command19, Option as Option19 } from "clipanion";
12284
12230
 
12285
12231
  // server/index.ts
12286
12232
  import { serve } from "@hono/node-server";
@@ -12461,7 +12407,7 @@ function contentTypeFor(format) {
12461
12407
  }
12462
12408
 
12463
12409
  // server/health.ts
12464
- import { existsSync as existsSync16 } from "fs";
12410
+ import { existsSync as existsSync15 } from "fs";
12465
12411
  var FALLBACK_SCHEMA_VERSION = "1";
12466
12412
  function buildHealth(deps) {
12467
12413
  return {
@@ -12470,7 +12416,7 @@ function buildHealth(deps) {
12470
12416
  specVersion: deps.specVersion,
12471
12417
  implVersion: VERSION,
12472
12418
  scope: deps.scope,
12473
- db: existsSync16(deps.dbPath) ? "present" : "missing"
12419
+ db: existsSync15(deps.dbPath) ? "present" : "missing"
12474
12420
  };
12475
12421
  }
12476
12422
  async function resolveSpecVersion2() {
@@ -12910,9 +12856,9 @@ function emptyScanResult() {
12910
12856
  }
12911
12857
 
12912
12858
  // server/static.ts
12913
- import { existsSync as existsSync17 } from "fs";
12859
+ import { existsSync as existsSync16 } from "fs";
12914
12860
  import { readFile as readFile5 } from "fs/promises";
12915
- import { extname, join as join14 } from "path";
12861
+ import { extname, join as join13 } from "path";
12916
12862
  import { serveStatic } from "@hono/node-server/serve-static";
12917
12863
  var INDEX_HTML = "index.html";
12918
12864
  var PLACEHOLDER_HTML = `<!doctype html>
@@ -12943,8 +12889,8 @@ function createSpaFallback(uiDist) {
12943
12889
  return async (c, _next) => {
12944
12890
  if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
12945
12891
  if (uiDist === null) return htmlResponse(c, PLACEHOLDER_HTML);
12946
- const indexPath = join14(uiDist, INDEX_HTML);
12947
- if (!existsSync17(indexPath)) return htmlResponse(c, PLACEHOLDER_HTML);
12892
+ const indexPath = join13(uiDist, INDEX_HTML);
12893
+ if (!existsSync16(indexPath)) return htmlResponse(c, PLACEHOLDER_HTML);
12948
12894
  return fileResponse(c, indexPath);
12949
12895
  };
12950
12896
  }
@@ -13257,6 +13203,9 @@ function buildKindRegistry(providers) {
13257
13203
  return registry;
13258
13204
  }
13259
13205
 
13206
+ // server/watcher.ts
13207
+ import { resolve as resolve20 } from "path";
13208
+
13260
13209
  // server/events.ts
13261
13210
  function buildWatcherStartedEvent(data) {
13262
13211
  return {
@@ -13279,18 +13228,24 @@ function buildWatcherErrorEvent(data) {
13279
13228
  var WATCH_ROOT = ".";
13280
13229
  function createWatcherService(opts) {
13281
13230
  let chokidarHandle = null;
13231
+ let metaHandle = null;
13282
13232
  let stopped = false;
13283
13233
  const start = async () => {
13284
- const cfg = loadConfig({
13234
+ const cwd = opts.runtimeContext.cwd;
13235
+ const loadEffectiveConfig = () => loadConfig({
13285
13236
  scope: opts.options.scope,
13286
- cwd: opts.runtimeContext.cwd,
13237
+ cwd,
13287
13238
  homedir: opts.runtimeContext.homedir
13288
13239
  }).effective;
13289
- const ignoreFileText = readIgnoreFileText(opts.runtimeContext.cwd);
13290
- const ignoreFilterOpts = {};
13291
- if (cfg.ignore.length > 0) ignoreFilterOpts.configIgnore = cfg.ignore;
13292
- if (ignoreFileText !== void 0) ignoreFilterOpts.ignoreFileText = ignoreFileText;
13293
- const ignoreFilter = buildIgnoreFilter(ignoreFilterOpts);
13240
+ const buildCurrentIgnoreFilter = (cfgIn) => {
13241
+ const ignoreFileText = readIgnoreFileText(cwd);
13242
+ const filterOpts = {};
13243
+ if (cfgIn.ignore.length > 0) filterOpts.configIgnore = cfgIn.ignore;
13244
+ if (ignoreFileText !== void 0) filterOpts.ignoreFileText = ignoreFileText;
13245
+ return buildIgnoreFilter(filterOpts);
13246
+ };
13247
+ let cfg = loadEffectiveConfig();
13248
+ let ignoreFilter = buildCurrentIgnoreFilter(cfg);
13294
13249
  const debounceMs = opts.debounceMsOverride ?? cfg.scan.watch.debounceMs;
13295
13250
  const pluginRuntime = opts.options.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: opts.options.scope });
13296
13251
  for (const warn of pluginRuntime.warnings) {
@@ -13321,7 +13276,12 @@ function createWatcherService(opts) {
13321
13276
  roots: [WATCH_ROOT],
13322
13277
  cwd: opts.runtimeContext.cwd,
13323
13278
  debounceMs,
13324
- ignoreFilter,
13279
+ // Pass a getter, NOT the filter directly: the meta-file watcher
13280
+ // below mutates `ignoreFilter` after a `.skillmapignore` /
13281
+ // `.skill-map/settings.json` edit, and chokidar's `ignored`
13282
+ // predicate must read the current value on every event. See
13283
+ // `kernel/scan/watcher.ts` for the supported shapes.
13284
+ ignoreFilter: () => ignoreFilter,
13325
13285
  onBatch: async () => {
13326
13286
  if (stopped) return;
13327
13287
  try {
@@ -13348,6 +13308,43 @@ function createWatcherService(opts) {
13348
13308
  if ("ready" in chokidarHandle && chokidarHandle.ready instanceof Promise) {
13349
13309
  await chokidarHandle.ready;
13350
13310
  }
13311
+ metaHandle = createChokidarWatcher({
13312
+ roots: [
13313
+ resolve20(cwd, ".skillmapignore"),
13314
+ resolve20(cwd, ".skill-map", "settings.json")
13315
+ ],
13316
+ cwd,
13317
+ debounceMs,
13318
+ // No `ignoreFilter` — these specific paths must always be observed,
13319
+ // regardless of any user pattern.
13320
+ onBatch: async () => {
13321
+ if (stopped) return;
13322
+ try {
13323
+ cfg = loadEffectiveConfig();
13324
+ ignoreFilter = buildCurrentIgnoreFilter(cfg);
13325
+ await runOneBatch();
13326
+ } catch (err) {
13327
+ const message = formatErrorMessage(err);
13328
+ log.warn(
13329
+ tx(SERVER_TEXTS.watcherBatchFailed, {
13330
+ message: sanitizeForTerminal(message)
13331
+ })
13332
+ );
13333
+ }
13334
+ },
13335
+ onError: (err) => {
13336
+ const message = err.message;
13337
+ log.warn(
13338
+ tx(SERVER_TEXTS.watcherError, {
13339
+ message: sanitizeForTerminal(message)
13340
+ })
13341
+ );
13342
+ }
13343
+ });
13344
+ if ("ready" in metaHandle && metaHandle.ready instanceof Promise) {
13345
+ await metaHandle.ready;
13346
+ }
13347
+ await runInitialBatch({ isStopped: () => stopped, runOneBatch });
13351
13348
  opts.broadcaster.broadcast(
13352
13349
  buildWatcherStartedEvent({ roots: [WATCH_ROOT], debounceMs })
13353
13350
  );
@@ -13361,19 +13358,23 @@ function createWatcherService(opts) {
13361
13358
  const stop = async () => {
13362
13359
  if (stopped) return;
13363
13360
  stopped = true;
13364
- if (chokidarHandle) {
13361
+ const closeQuietly = async (handle, label) => {
13362
+ if (!handle) return;
13365
13363
  try {
13366
- await chokidarHandle.close();
13364
+ await handle.close();
13367
13365
  } catch (err) {
13368
13366
  const message = err instanceof Error ? err.message : String(err);
13369
13367
  log.warn(
13370
13368
  tx(SERVER_TEXTS.watcherCloseFailed, {
13371
- message: sanitizeForTerminal(message)
13369
+ message: sanitizeForTerminal(`${label}: ${message}`)
13372
13370
  })
13373
13371
  );
13374
13372
  }
13375
- chokidarHandle = null;
13376
- }
13373
+ };
13374
+ await closeQuietly(metaHandle, "meta-watcher");
13375
+ metaHandle = null;
13376
+ await closeQuietly(chokidarHandle, "primary");
13377
+ chokidarHandle = null;
13377
13378
  };
13378
13379
  return { start, stop };
13379
13380
  }
@@ -13430,6 +13431,19 @@ async function persistOutcome(dbPath, ran) {
13430
13431
  (writer) => writer.scans.persist(result, { renameOps, extractorRuns, enrichments })
13431
13432
  );
13432
13433
  }
13434
+ async function runInitialBatch(deps) {
13435
+ if (deps.isStopped()) return;
13436
+ try {
13437
+ await deps.runOneBatch();
13438
+ } catch (err) {
13439
+ const message = formatErrorMessage(err);
13440
+ log.warn(
13441
+ tx(SERVER_TEXTS.watcherBatchFailed, {
13442
+ message: sanitizeForTerminal(message)
13443
+ })
13444
+ );
13445
+ }
13446
+ }
13433
13447
 
13434
13448
  // server/options.ts
13435
13449
  var DEFAULT_PORT = 4242;
@@ -13534,10 +13548,10 @@ function validateWatcherDebounce(value) {
13534
13548
  }
13535
13549
 
13536
13550
  // server/paths.ts
13537
- import { existsSync as existsSync18, statSync as statSync5 } from "fs";
13538
- import { dirname as dirname10, isAbsolute as isAbsolute5, join as join15, resolve as resolve20 } from "path";
13539
- import { fileURLToPath as fileURLToPath6 } from "url";
13540
- var DEFAULT_UI_REL = join15("ui", "dist", "ui", "browser");
13551
+ import { existsSync as existsSync17, statSync as statSync5 } from "fs";
13552
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join14, resolve as resolve21 } from "path";
13553
+ import { fileURLToPath as fileURLToPath5 } from "url";
13554
+ var DEFAULT_UI_REL = join14("ui", "dist", "ui", "browser");
13541
13555
  var PACKAGE_UI_REL = "ui";
13542
13556
  var INDEX_HTML2 = "index.html";
13543
13557
  function resolveDefaultUiDist(ctx) {
@@ -13546,13 +13560,13 @@ function resolveDefaultUiDist(ctx) {
13546
13560
  return walkUpForUi(ctx.cwd);
13547
13561
  }
13548
13562
  function resolveExplicitUiDist(ctx, raw) {
13549
- return isAbsolute5(raw) ? raw : resolve20(ctx.cwd, raw);
13563
+ return isAbsolute5(raw) ? raw : resolve21(ctx.cwd, raw);
13550
13564
  }
13551
13565
  function isUiBundleDir(path) {
13552
- if (!existsSync18(path)) return false;
13566
+ if (!existsSync17(path)) return false;
13553
13567
  try {
13554
13568
  if (!statSync5(path).isDirectory()) return false;
13555
- return existsSync18(join15(path, INDEX_HTML2));
13569
+ return existsSync17(join14(path, INDEX_HTML2));
13556
13570
  } catch {
13557
13571
  return false;
13558
13572
  }
@@ -13560,7 +13574,7 @@ function isUiBundleDir(path) {
13560
13574
  function resolvePackageBundledUi() {
13561
13575
  let here;
13562
13576
  try {
13563
- here = dirname10(fileURLToPath6(import.meta.url));
13577
+ here = dirname9(fileURLToPath5(import.meta.url));
13564
13578
  } catch {
13565
13579
  return null;
13566
13580
  }
@@ -13569,22 +13583,22 @@ function resolvePackageBundledUi() {
13569
13583
  function resolvePackageBundledUiFrom(here) {
13570
13584
  let current = here;
13571
13585
  for (let i = 0; i < 8; i++) {
13572
- const candidate = join15(current, PACKAGE_UI_REL);
13586
+ const candidate = join14(current, PACKAGE_UI_REL);
13573
13587
  if (isUiBundleDir(candidate)) return candidate;
13574
- const distHere = join15(current, "dist", PACKAGE_UI_REL);
13588
+ const distHere = join14(current, "dist", PACKAGE_UI_REL);
13575
13589
  if (isUiBundleDir(distHere)) return distHere;
13576
- const parent = dirname10(current);
13590
+ const parent = dirname9(current);
13577
13591
  if (parent === current) return null;
13578
13592
  current = parent;
13579
13593
  }
13580
13594
  return null;
13581
13595
  }
13582
13596
  function walkUpForUi(startDir) {
13583
- let current = resolve20(startDir);
13597
+ let current = resolve21(startDir);
13584
13598
  for (let i = 0; i < 64; i++) {
13585
- const candidate = join15(current, DEFAULT_UI_REL);
13599
+ const candidate = join14(current, DEFAULT_UI_REL);
13586
13600
  if (isUiBundleDir(candidate)) return candidate;
13587
- const parent = dirname10(current);
13601
+ const parent = dirname9(current);
13588
13602
  if (parent === current) return null;
13589
13603
  current = parent;
13590
13604
  }
@@ -13810,15 +13824,9 @@ function renderFiglet(input) {
13810
13824
  greenUnderline,
13811
13825
  greenUnderlineClose,
13812
13826
  violetOpen,
13813
- violetClose,
13814
- greenOpen,
13815
- greenClose
13827
+ violetClose
13816
13828
  } = resolveAnsi(input.colorEnabled);
13817
- const logoLines = LOGO_LINES.map((line, i) => {
13818
- const open = i < 3 ? violetOpen : greenOpen;
13819
- const close = i < 3 ? violetClose : greenClose;
13820
- return `${open}${line}${close}`;
13821
- });
13829
+ const logoLines = LOGO_LINES.map((line) => `${violetOpen}${line}${violetClose}`);
13822
13830
  const versionText = `v${input.version}`;
13823
13831
  const versionPad = Math.max(0, LOGO_WIDTH - versionText.length);
13824
13832
  const versionLine = `${" ".repeat(versionPad)}${dimOpen}${versionText}${dimClose}`;
@@ -13842,9 +13850,7 @@ var EMPTY_ANSI = {
13842
13850
  greenUnderline: "",
13843
13851
  greenUnderlineClose: "",
13844
13852
  violetOpen: "",
13845
- violetClose: "",
13846
- greenOpen: "",
13847
- greenClose: ""
13853
+ violetClose: ""
13848
13854
  };
13849
13855
  var ENABLED_ANSI = {
13850
13856
  dimOpen: ESC.dim,
@@ -13852,9 +13858,7 @@ var ENABLED_ANSI = {
13852
13858
  greenUnderline: `${ESC.green}${ESC.underline}`,
13853
13859
  greenUnderlineClose: ESC.reset,
13854
13860
  violetOpen: ESC.violet,
13855
- violetClose: ESC.reset,
13856
- greenOpen: ESC.green,
13857
- greenClose: ESC.reset
13861
+ violetClose: ESC.reset
13858
13862
  };
13859
13863
  function resolveAnsi(colorEnabled) {
13860
13864
  return colorEnabled ? ENABLED_ANSI : EMPTY_ANSI;
@@ -13880,7 +13884,7 @@ function formatCwdPath(cwd) {
13880
13884
  // cli/commands/serve.ts
13881
13885
  var ServeCommand = class extends SmCommand {
13882
13886
  static paths = [["serve"]];
13883
- static usage = Command20.Usage({
13887
+ static usage = Command19.Usage({
13884
13888
  category: "Setup",
13885
13889
  description: "Start the Hono BFF (single-port: REST + WebSocket + SPA bundle).",
13886
13890
  details: `
@@ -13905,22 +13909,22 @@ var ServeCommand = class extends SmCommand {
13905
13909
  ["Point at a pre-built UI bundle", "$0 serve --ui-dist ./ui/dist/browser"]
13906
13910
  ]
13907
13911
  });
13908
- port = Option20.String("--port", {
13912
+ port = Option19.String("--port", {
13909
13913
  required: false,
13910
13914
  description: "Listening port (default 4242). 0 = OS-assigned."
13911
13915
  });
13912
- host = Option20.String("--host", {
13916
+ host = Option19.String("--host", {
13913
13917
  required: false,
13914
13918
  description: "Listening host (default 127.0.0.1). Loopback-only enforced when --dev-cors is set."
13915
13919
  });
13916
- scope = Option20.String("--scope", {
13920
+ scope = Option19.String("--scope", {
13917
13921
  required: false,
13918
13922
  description: "project | global. Alias for -g/--global. Default: project."
13919
13923
  });
13920
- noBuiltIns = Option20.Boolean("--no-built-ins", false, {
13924
+ noBuiltIns = Option19.Boolean("--no-built-ins", false, {
13921
13925
  description: "Skip built-in plugin registration (parity with sm scan --no-built-ins)."
13922
13926
  });
13923
- noPlugins = Option20.Boolean("--no-plugins", false, {
13927
+ noPlugins = Option19.Boolean("--no-plugins", false, {
13924
13928
  description: "Skip drop-in plugin discovery."
13925
13929
  });
13926
13930
  // `Option.Boolean('--open', true)` — Clipanion's parser auto-derives
@@ -13930,24 +13934,24 @@ var ServeCommand = class extends SmCommand {
13930
13934
  // two registrations for the same flag and rejects the invocation
13931
13935
  // with "Ambiguous Syntax Error". Same convention shipped by every
13932
13936
  // other `--no-...` flag in the CLI tree.
13933
- open = Option20.Boolean("--open", true, {
13937
+ open = Option19.Boolean("--open", true, {
13934
13938
  description: "Auto-open the SPA in the user's default browser after listen. --no-open opts out."
13935
13939
  });
13936
- devCors = Option20.Boolean("--dev-cors", false, {
13940
+ devCors = Option19.Boolean("--dev-cors", false, {
13937
13941
  description: "Enable permissive CORS for the Angular dev-server proxy workflow."
13938
13942
  });
13939
13943
  // `--ui-dist` is intentionally undocumented in the Usage block above
13940
13944
  // (the demo build pipeline + tests rely on it; everyday users never
13941
13945
  // need it). Clipanion still exposes it on the parser; the Usage
13942
13946
  // omission is the "hidden" contract per the 14.1 brief.
13943
- uiDist = Option20.String("--ui-dist", { required: false, hidden: true });
13944
- noWatcher = Option20.Boolean("--no-watcher", false, {
13947
+ uiDist = Option19.String("--ui-dist", { required: false, hidden: true });
13948
+ noWatcher = Option19.Boolean("--no-watcher", false, {
13945
13949
  description: "Disable the chokidar-fed scan-and-broadcast loop. Use only for CI / read-only deployments."
13946
13950
  });
13947
13951
  // `--watcher-debounce-ms` is undocumented sugar for advanced users
13948
13952
  // who want to tighten / relax the watcher's batching window without
13949
13953
  // editing settings.json. Hidden flag — the Usage block omits it.
13950
- watcherDebounceMs = Option20.String("--watcher-debounce-ms", { required: false, hidden: true });
13954
+ watcherDebounceMs = Option19.String("--watcher-debounce-ms", { required: false, hidden: true });
13951
13955
  // Long-running daemon — `done in <…>` after a graceful shutdown is
13952
13956
  // noise. Mirrors `sm watch`'s opt-out.
13953
13957
  emitElapsed = false;
@@ -13975,7 +13979,7 @@ var ServeCommand = class extends SmCommand {
13975
13979
  return ExitCode.Error;
13976
13980
  }
13977
13981
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...runtimeCtx });
13978
- if (this.db !== void 0 && !existsSync19(dbPath)) {
13982
+ if (this.db !== void 0 && !existsSync18(dbPath)) {
13979
13983
  this.context.stderr.write(
13980
13984
  tx(SERVE_TEXTS.dbNotFound, { path: sanitizeForTerminal(dbPath) })
13981
13985
  );
@@ -14162,7 +14166,7 @@ function tryOpenBrowser(url, stderr) {
14162
14166
  }
14163
14167
 
14164
14168
  // cli/commands/show.ts
14165
- import { Command as Command21, Option as Option21 } from "clipanion";
14169
+ import { Command as Command20, Option as Option20 } from "clipanion";
14166
14170
 
14167
14171
  // cli/i18n/show.texts.ts
14168
14172
  var SHOW_TEXTS = {
@@ -14202,7 +14206,7 @@ var SHOW_TEXTS = {
14202
14206
  // cli/commands/show.ts
14203
14207
  var ShowCommand = class extends SmCommand {
14204
14208
  static paths = [["show"]];
14205
- static usage = Command21.Usage({
14209
+ static usage = Command20.Usage({
14206
14210
  category: "Browse",
14207
14211
  description: "Node detail: weight, frontmatter, links, issues.",
14208
14212
  details: `
@@ -14218,7 +14222,7 @@ var ShowCommand = class extends SmCommand {
14218
14222
  ["Machine-readable detail", "$0 show .claude/agents/architect.md --json"]
14219
14223
  ]
14220
14224
  });
14221
- nodePath = Option21.String({ required: true });
14225
+ nodePath = Option20.String({ required: true });
14222
14226
  async run() {
14223
14227
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
14224
14228
  if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
@@ -14367,7 +14371,7 @@ function rankConfidenceForGrouping(c) {
14367
14371
  }
14368
14372
 
14369
14373
  // cli/commands/stubs.ts
14370
- import { Command as Command22, Option as Option22 } from "clipanion";
14374
+ import { Command as Command21, Option as Option21 } from "clipanion";
14371
14375
 
14372
14376
  // cli/i18n/stubs.texts.ts
14373
14377
  var STUBS_TEXTS = {
@@ -14382,9 +14386,9 @@ function notImplemented(cmd, verb) {
14382
14386
  cmd.context.stderr.write(tx(STUBS_TEXTS.notImplemented, { verb }));
14383
14387
  return ExitCode.Error;
14384
14388
  }
14385
- var DoctorCommand = class extends Command22 {
14389
+ var DoctorCommand = class extends Command21 {
14386
14390
  static paths = [["doctor"]];
14387
- static usage = Command22.Usage({
14391
+ static usage = Command21.Usage({
14388
14392
  category: "Setup",
14389
14393
  description: planned("Diagnostic report: DB integrity, pending migrations, orphan rows, plugin status, runner availability.")
14390
14394
  });
@@ -14392,23 +14396,23 @@ var DoctorCommand = class extends Command22 {
14392
14396
  return notImplemented(this, "doctor");
14393
14397
  }
14394
14398
  };
14395
- var FindingsCommand = class extends Command22 {
14399
+ var FindingsCommand = class extends Command21 {
14396
14400
  static paths = [["findings"]];
14397
- static usage = Command22.Usage({
14401
+ static usage = Command21.Usage({
14398
14402
  category: "Browse",
14399
14403
  description: planned("Probabilistic findings: injection, stale summaries, low confidence.")
14400
14404
  });
14401
- kind = Option22.String("--kind", { required: false });
14402
- since = Option22.String("--since", { required: false });
14403
- threshold = Option22.String("--threshold", { required: false });
14404
- json = Option22.Boolean("--json", false);
14405
+ kind = Option21.String("--kind", { required: false });
14406
+ since = Option21.String("--since", { required: false });
14407
+ threshold = Option21.String("--threshold", { required: false });
14408
+ json = Option21.Boolean("--json", false);
14405
14409
  async execute() {
14406
14410
  return notImplemented(this, "findings");
14407
14411
  }
14408
14412
  };
14409
- var ActionsListCommand = class extends Command22 {
14413
+ var ActionsListCommand = class extends Command21 {
14410
14414
  static paths = [["actions", "list"]];
14411
- static usage = Command22.Usage({
14415
+ static usage = Command21.Usage({
14412
14416
  category: "Jobs",
14413
14417
  description: planned("Registered action types (manifest view).")
14414
14418
  });
@@ -14416,121 +14420,121 @@ var ActionsListCommand = class extends Command22 {
14416
14420
  return notImplemented(this, "actions list");
14417
14421
  }
14418
14422
  };
14419
- var ActionsShowCommand = class extends Command22 {
14423
+ var ActionsShowCommand = class extends Command21 {
14420
14424
  static paths = [["actions", "show"]];
14421
- static usage = Command22.Usage({
14425
+ static usage = Command21.Usage({
14422
14426
  category: "Jobs",
14423
14427
  description: planned("Full action manifest, including preconditions and expected duration.")
14424
14428
  });
14425
- id = Option22.String({ required: true });
14429
+ id = Option21.String({ required: true });
14426
14430
  async execute() {
14427
14431
  return notImplemented(this, "actions show");
14428
14432
  }
14429
14433
  };
14430
- var JobSubmitCommand = class extends Command22 {
14434
+ var JobSubmitCommand = class extends Command21 {
14431
14435
  static paths = [["job", "submit"]];
14432
- static usage = Command22.Usage({
14436
+ static usage = Command21.Usage({
14433
14437
  category: "Jobs",
14434
14438
  description: planned("Enqueue a single job or fan out to every matching node (--all).")
14435
14439
  });
14436
- action = Option22.String({ required: true });
14437
- node = Option22.String("-n", { required: false });
14438
- all = Option22.Boolean("--all", false);
14439
- run = Option22.Boolean("--run", false);
14440
- force = Option22.Boolean("--force", false);
14441
- ttl = Option22.String("--ttl", { required: false });
14442
- priority = Option22.String("--priority", { required: false });
14440
+ action = Option21.String({ required: true });
14441
+ node = Option21.String("-n", { required: false });
14442
+ all = Option21.Boolean("--all", false);
14443
+ run = Option21.Boolean("--run", false);
14444
+ force = Option21.Boolean("--force", false);
14445
+ ttl = Option21.String("--ttl", { required: false });
14446
+ priority = Option21.String("--priority", { required: false });
14443
14447
  async execute() {
14444
14448
  return notImplemented(this, "job submit");
14445
14449
  }
14446
14450
  };
14447
- var JobListCommand = class extends Command22 {
14451
+ var JobListCommand = class extends Command21 {
14448
14452
  static paths = [["job", "list"]];
14449
- static usage = Command22.Usage({ category: "Jobs", description: planned("List jobs.") });
14450
- status = Option22.String("--status", { required: false });
14451
- action = Option22.String("--action", { required: false });
14452
- node = Option22.String("--node", { required: false });
14453
+ static usage = Command21.Usage({ category: "Jobs", description: planned("List jobs.") });
14454
+ status = Option21.String("--status", { required: false });
14455
+ action = Option21.String("--action", { required: false });
14456
+ node = Option21.String("--node", { required: false });
14453
14457
  async execute() {
14454
14458
  return notImplemented(this, "job list");
14455
14459
  }
14456
14460
  };
14457
- var JobShowCommand = class extends Command22 {
14461
+ var JobShowCommand = class extends Command21 {
14458
14462
  static paths = [["job", "show"]];
14459
- static usage = Command22.Usage({ category: "Jobs", description: planned("Job detail: state, claim time, TTL, runner, content hash.") });
14460
- id = Option22.String({ required: true });
14463
+ static usage = Command21.Usage({ category: "Jobs", description: planned("Job detail: state, claim time, TTL, runner, content hash.") });
14464
+ id = Option21.String({ required: true });
14461
14465
  async execute() {
14462
14466
  return notImplemented(this, "job show");
14463
14467
  }
14464
14468
  };
14465
- var JobPreviewCommand = class extends Command22 {
14469
+ var JobPreviewCommand = class extends Command21 {
14466
14470
  static paths = [["job", "preview"]];
14467
- static usage = Command22.Usage({ category: "Jobs", description: planned("Render the job MD file without executing.") });
14468
- id = Option22.String({ required: true });
14471
+ static usage = Command21.Usage({ category: "Jobs", description: planned("Render the job MD file without executing.") });
14472
+ id = Option21.String({ required: true });
14469
14473
  async execute() {
14470
14474
  return notImplemented(this, "job preview");
14471
14475
  }
14472
14476
  };
14473
- var JobClaimCommand = class extends Command22 {
14477
+ var JobClaimCommand = class extends Command21 {
14474
14478
  static paths = [["job", "claim"]];
14475
- static usage = Command22.Usage({
14479
+ static usage = Command21.Usage({
14476
14480
  category: "Jobs",
14477
14481
  description: planned("Atomic primitive: return next queued job id, mark it running.")
14478
14482
  });
14479
- filter = Option22.String("--filter", { required: false });
14483
+ filter = Option21.String("--filter", { required: false });
14480
14484
  async execute() {
14481
14485
  return notImplemented(this, "job claim");
14482
14486
  }
14483
14487
  };
14484
- var JobRunCommand = class extends Command22 {
14488
+ var JobRunCommand = class extends Command21 {
14485
14489
  static paths = [["job", "run"]];
14486
- static usage = Command22.Usage({
14490
+ static usage = Command21.Usage({
14487
14491
  category: "Jobs",
14488
14492
  description: planned("Full CLI-runner loop: claim + spawn + record.")
14489
14493
  });
14490
- all = Option22.Boolean("--all", false);
14491
- max = Option22.String("--max", { required: false });
14494
+ all = Option21.Boolean("--all", false);
14495
+ max = Option21.String("--max", { required: false });
14492
14496
  async execute() {
14493
14497
  return notImplemented(this, "job run");
14494
14498
  }
14495
14499
  };
14496
- var JobStatusCommand = class extends Command22 {
14500
+ var JobStatusCommand = class extends Command21 {
14497
14501
  static paths = [["job", "status"]];
14498
- static usage = Command22.Usage({
14502
+ static usage = Command21.Usage({
14499
14503
  category: "Jobs",
14500
14504
  description: planned("Counts (per status) or single-job status.")
14501
14505
  });
14502
- id = Option22.String({ required: false });
14506
+ id = Option21.String({ required: false });
14503
14507
  async execute() {
14504
14508
  return notImplemented(this, "job status");
14505
14509
  }
14506
14510
  };
14507
- var JobCancelCommand = class extends Command22 {
14511
+ var JobCancelCommand = class extends Command21 {
14508
14512
  static paths = [["job", "cancel"]];
14509
- static usage = Command22.Usage({
14513
+ static usage = Command21.Usage({
14510
14514
  category: "Jobs",
14511
14515
  description: planned("Force a running job to failed with reason user-cancelled.")
14512
14516
  });
14513
- id = Option22.String({ required: false });
14514
- all = Option22.Boolean("--all", false);
14517
+ id = Option21.String({ required: false });
14518
+ all = Option21.Boolean("--all", false);
14515
14519
  async execute() {
14516
14520
  return notImplemented(this, "job cancel");
14517
14521
  }
14518
14522
  };
14519
- var RecordCommand = class extends Command22 {
14523
+ var RecordCommand = class extends Command21 {
14520
14524
  static paths = [["record"]];
14521
- static usage = Command22.Usage({
14525
+ static usage = Command21.Usage({
14522
14526
  category: "Jobs",
14523
14527
  description: planned("Close a running job with success or failure. Nonce is the sole credential.")
14524
14528
  });
14525
- id = Option22.String("--id", { required: true });
14526
- nonce = Option22.String("--nonce", { required: true });
14527
- status = Option22.String("--status", { required: true });
14528
- report = Option22.String("--report", { required: false });
14529
- tokensIn = Option22.String("--tokens-in", { required: false });
14530
- tokensOut = Option22.String("--tokens-out", { required: false });
14531
- durationMs = Option22.String("--duration-ms", { required: false });
14532
- model = Option22.String("--model", { required: false });
14533
- error = Option22.String("--error", { required: false });
14529
+ id = Option21.String("--id", { required: true });
14530
+ nonce = Option21.String("--nonce", { required: true });
14531
+ status = Option21.String("--status", { required: true });
14532
+ report = Option21.String("--report", { required: false });
14533
+ tokensIn = Option21.String("--tokens-in", { required: false });
14534
+ tokensOut = Option21.String("--tokens-out", { required: false });
14535
+ durationMs = Option21.String("--duration-ms", { required: false });
14536
+ model = Option21.String("--model", { required: false });
14537
+ error = Option21.String("--error", { required: false });
14534
14538
  async execute() {
14535
14539
  return notImplemented(this, "record");
14536
14540
  }
@@ -14551,6 +14555,99 @@ var STUB_COMMANDS = [
14551
14555
  RecordCommand
14552
14556
  ];
14553
14557
 
14558
+ // cli/commands/tutorial.ts
14559
+ import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
14560
+ import { writeFile as writeFile2 } from "fs/promises";
14561
+ import { dirname as dirname10, join as join15, resolve as resolve22 } from "path";
14562
+ import { fileURLToPath as fileURLToPath6 } from "url";
14563
+ import { Command as Command22, Option as Option22 } from "clipanion";
14564
+
14565
+ // cli/i18n/tutorial.texts.ts
14566
+ var TUTORIAL_TEXTS = {
14567
+ // Success — written to stdout after `<cwd>/sm-tutorial.md` is created.
14568
+ written: 'Done. sm-tutorial.md created at {{cwd}}. Open Claude Code here and tell it "run @sm-tutorial.md" to start the interactive tutorial.\n',
14569
+ // Refusal — `sm-tutorial.md` already exists and `--force` was not set.
14570
+ // Goes to stderr, exit code 2 (operational error per spec § Exit codes).
14571
+ alreadyExists: "sm tutorial: sm-tutorial.md already exists at {{cwd}}. Pass `--force` to overwrite.\n",
14572
+ // I/O failure on write or on reading the bundled SKILL source.
14573
+ writeFailed: "sm tutorial: failed to write sm-tutorial.md: {{message}}\n",
14574
+ sourceMissing: "sm tutorial: could not read the bundled tutorial (SKILL.md) from the install. Reinstall @skill-map/cli or report the bug.\n"
14575
+ };
14576
+
14577
+ // cli/commands/tutorial.ts
14578
+ var SM_TUTORIAL_FILENAME = "sm-tutorial.md";
14579
+ var TutorialCommand = class extends SmCommand {
14580
+ static paths = [["tutorial"]];
14581
+ static usage = Command22.Usage({
14582
+ category: "Setup",
14583
+ description: "Materialize the interactive tester tutorial (sm-tutorial.md) in the current directory.",
14584
+ details: `
14585
+ Drops the canonical SKILL.md content as ./sm-tutorial.md so a tester
14586
+ can open Claude Code in the cwd and load the file as a skill by
14587
+ typing "ejecut\xE1 @sm-tutorial.md". Top-level only \u2014 no subdirectory
14588
+ is created.
14589
+
14590
+ Does NOT require an initialized .skill-map/ project. Refuses to
14591
+ overwrite an existing sm-tutorial.md unless --force is passed.
14592
+ `,
14593
+ examples: [
14594
+ ["Materialize the tutorial in the cwd", "$0 tutorial"],
14595
+ ["Overwrite an existing sm-tutorial.md", "$0 tutorial --force"]
14596
+ ]
14597
+ });
14598
+ force = Option22.Boolean("--force", false, {
14599
+ description: "Overwrite an existing sm-tutorial.md without prompting."
14600
+ });
14601
+ async run() {
14602
+ const ctx = defaultRuntimeContext();
14603
+ const target = join15(ctx.cwd, SM_TUTORIAL_FILENAME);
14604
+ if (await pathExists(target) && !this.force) {
14605
+ this.context.stderr.write(tx(TUTORIAL_TEXTS.alreadyExists, { cwd: ctx.cwd }));
14606
+ return ExitCode.Error;
14607
+ }
14608
+ let body;
14609
+ try {
14610
+ body = loadBundledTutorialText();
14611
+ } catch {
14612
+ this.context.stderr.write(TUTORIAL_TEXTS.sourceMissing);
14613
+ return ExitCode.Error;
14614
+ }
14615
+ try {
14616
+ await writeFile2(target, body);
14617
+ } catch (err) {
14618
+ this.context.stderr.write(
14619
+ tx(TUTORIAL_TEXTS.writeFailed, { message: formatErrorMessage(err) })
14620
+ );
14621
+ return ExitCode.Error;
14622
+ }
14623
+ this.context.stdout.write(tx(TUTORIAL_TEXTS.written, { cwd: ctx.cwd }));
14624
+ return ExitCode.Ok;
14625
+ }
14626
+ };
14627
+ var cachedTutorial = null;
14628
+ function loadBundledTutorialText() {
14629
+ if (cachedTutorial !== null) return cachedTutorial;
14630
+ cachedTutorial = readTutorialFromDisk();
14631
+ return cachedTutorial;
14632
+ }
14633
+ function readTutorialFromDisk() {
14634
+ const here = dirname10(fileURLToPath6(import.meta.url));
14635
+ const candidates = [
14636
+ // dev: src/cli/commands/ → repo-root .claude/skills/sm-tutorial/SKILL.md
14637
+ resolve22(here, "../../../.claude/skills/sm-tutorial/SKILL.md"),
14638
+ // bundled: dist/cli.js → dist/cli/tutorial/sm-tutorial.md (sibling)
14639
+ resolve22(here, "cli/tutorial/sm-tutorial.md"),
14640
+ // bundled fallback: any-depth → cli/tutorial/sm-tutorial.md
14641
+ resolve22(here, "../cli/tutorial/sm-tutorial.md")
14642
+ ];
14643
+ for (const candidate of candidates) {
14644
+ if (existsSync19(candidate)) {
14645
+ return readFileSync12(candidate, "utf8");
14646
+ }
14647
+ }
14648
+ throw new Error(`SKILL.md not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`);
14649
+ }
14650
+
14554
14651
  // cli/commands/version.ts
14555
14652
  import { Command as Command23 } from "clipanion";
14556
14653
 
@@ -14634,7 +14731,7 @@ cli.register(Builtins.VersionCommand);
14634
14731
  cli.register(RootHelpCommand);
14635
14732
  cli.register(HelpCommand);
14636
14733
  cli.register(InitCommand);
14637
- cli.register(GuideCommand);
14734
+ cli.register(TutorialCommand);
14638
14735
  cli.register(ScanCommand);
14639
14736
  cli.register(ScanCompareCommand);
14640
14737
  cli.register(ServeCommand);