@skill-map/cli 0.15.0 → 0.16.1

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() {
@@ -7362,7 +7362,7 @@ import { Command as Command8, Option as Option8 } from "clipanion";
7362
7362
  // package.json
7363
7363
  var package_default = {
7364
7364
  name: "@skill-map/cli",
7365
- version: "0.15.0",
7365
+ version: "0.16.1",
7366
7366
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
7367
7367
  license: "MIT",
7368
7368
  type: "module",
@@ -7421,7 +7421,7 @@ var package_default = {
7421
7421
  },
7422
7422
  dependencies: {
7423
7423
  "@hono/node-server": "2.0.1",
7424
- "@skill-map/spec": "0.15.0",
7424
+ "@skill-map/spec": "0.16.0",
7425
7425
  ajv: "8.18.0",
7426
7426
  "ajv-formats": "3.0.1",
7427
7427
  chokidar: "5.0.0",
@@ -8850,11 +8850,14 @@ import { resolve as resolve14, relative as relative4, sep as sep2 } from "path";
8850
8850
  import chokidar from "chokidar";
8851
8851
  function createChokidarWatcher(opts) {
8852
8852
  const absRoots = opts.roots.map((r) => resolve14(opts.cwd, r));
8853
- const ignoreFilter = opts.ignoreFilter;
8854
- const ignored = ignoreFilter ? (path) => {
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;
8855
8858
  const rel = relativePathFromRoots(path, absRoots);
8856
8859
  if (rel === null) return false;
8857
- return ignoreFilter.ignores(rel);
8860
+ return filter.ignores(rel);
8858
8861
  } : void 0;
8859
8862
  const watcher = chokidar.watch(absRoots, {
8860
8863
  ignoreInitial: true,
@@ -9101,7 +9104,7 @@ var InitCommand = class extends SmCommand {
9101
9104
  details: `
9102
9105
  Project scope (default): creates ./.skill-map/ with settings.json,
9103
9106
  settings.local.json, and skill-map.db. Drops a starter
9104
- .skill-mapignore at the scope root and appends the DB + local
9107
+ .skillmapignore at the scope root and appends the DB + local
9105
9108
  settings to .gitignore.
9106
9109
 
9107
9110
  Global scope (-g): same scaffolding under ~/.skill-map/. No
@@ -9123,7 +9126,7 @@ var InitCommand = class extends SmCommand {
9123
9126
  description: "Skip the first scan after scaffolding."
9124
9127
  });
9125
9128
  force = Option9.Boolean("--force", false, {
9126
- description: "Overwrite an existing settings.json / settings.local.json / .skill-mapignore."
9129
+ description: "Overwrite an existing settings.json / settings.local.json / .skillmapignore."
9127
9130
  });
9128
9131
  strict = Option9.Boolean("--strict", false, {
9129
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."
@@ -11563,6 +11566,7 @@ async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
11563
11566
  }
11564
11567
 
11565
11568
  // cli/commands/watch.ts
11569
+ import { resolve as resolve19 } from "path";
11566
11570
  import { Command as Command16, Option as Option16 } from "clipanion";
11567
11571
 
11568
11572
  // cli/i18n/watch.texts.ts
@@ -11586,20 +11590,24 @@ async function runWatchLoop(opts) {
11586
11590
  const { context } = opts;
11587
11591
  const runtimeCtx = defaultRuntimeContext();
11588
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
+ };
11589
11601
  let cfg;
11590
11602
  try {
11591
- cfg = loadConfig({ scope: "project", strict: opts.strict, ...runtimeCtx }).effective;
11603
+ cfg = loadEffectiveConfig();
11592
11604
  } catch (err) {
11593
11605
  const message = formatErrorMessage(err);
11594
11606
  context.stderr.write(tx(WATCH_TEXTS.configLoadFailure, { message }));
11595
11607
  return ExitCode.Error;
11596
11608
  }
11597
- const ignoreFileText = readIgnoreFileText(cwd);
11598
- const ignoreFilterOpts = {};
11599
- if (cfg.ignore.length > 0) ignoreFilterOpts.configIgnore = cfg.ignore;
11600
- if (ignoreFileText !== void 0) ignoreFilterOpts.ignoreFileText = ignoreFileText;
11601
- const ignoreFilter = buildIgnoreFilter(ignoreFilterOpts);
11602
- const strict = opts.strict || cfg.scan.strict === true;
11609
+ let ignoreFilter = buildCurrentIgnoreFilter(cfg);
11610
+ let strict = opts.strict || cfg.scan.strict === true;
11603
11611
  const debounceMs = cfg.scan.watch.debounceMs;
11604
11612
  const dbPath = defaultProjectDbPath(runtimeCtx);
11605
11613
  const pluginRuntime = opts.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: "project" });
@@ -11708,7 +11716,11 @@ async function runWatchLoop(opts) {
11708
11716
  roots: opts.roots,
11709
11717
  cwd,
11710
11718
  debounceMs,
11711
- 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,
11712
11724
  onBatch: async () => {
11713
11725
  const next = await handleBatch();
11714
11726
  if (next === "stop") {
@@ -11720,6 +11732,29 @@ async function runWatchLoop(opts) {
11720
11732
  context.stderr.write(tx(WATCH_TEXTS.watcherError, { message: err.message }));
11721
11733
  }
11722
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
+ });
11723
11758
  const onSignal = () => {
11724
11759
  if (stopRequested) return;
11725
11760
  stopRequested = true;
@@ -11728,12 +11763,14 @@ async function runWatchLoop(opts) {
11728
11763
  process.once("SIGINT", onSignal);
11729
11764
  process.once("SIGTERM", onSignal);
11730
11765
  await watcher.ready;
11766
+ await metaWatcher.ready;
11731
11767
  if (!opts.json) {
11732
11768
  context.stderr.write(WATCH_TEXTS.ready);
11733
11769
  }
11734
11770
  await stopped;
11735
11771
  process.removeListener("SIGINT", onSignal);
11736
11772
  process.removeListener("SIGTERM", onSignal);
11773
+ await metaWatcher.close();
11737
11774
  await watcher.close();
11738
11775
  if (!opts.json) {
11739
11776
  context.stderr.write(tx(WATCH_TEXTS.stopped, { batchCount }));
@@ -11748,7 +11785,7 @@ var WatchCommand = class extends SmCommand {
11748
11785
  details: `
11749
11786
  Long-running version of 'sm scan --changed'. Subscribes to the
11750
11787
  given roots via chokidar, applies the same ignore chain
11751
- (.skill-mapignore + config.ignore + bundled defaults), and
11788
+ (.skillmapignore + config.ignore + bundled defaults), and
11752
11789
  triggers an incremental scan after each debounced batch.
11753
11790
 
11754
11791
  Default debounce is 300ms; configure via 'scan.watch.debounceMs'
@@ -13166,6 +13203,9 @@ function buildKindRegistry(providers) {
13166
13203
  return registry;
13167
13204
  }
13168
13205
 
13206
+ // server/watcher.ts
13207
+ import { resolve as resolve20 } from "path";
13208
+
13169
13209
  // server/events.ts
13170
13210
  function buildWatcherStartedEvent(data) {
13171
13211
  return {
@@ -13188,18 +13228,24 @@ function buildWatcherErrorEvent(data) {
13188
13228
  var WATCH_ROOT = ".";
13189
13229
  function createWatcherService(opts) {
13190
13230
  let chokidarHandle = null;
13231
+ let metaHandle = null;
13191
13232
  let stopped = false;
13192
13233
  const start = async () => {
13193
- const cfg = loadConfig({
13234
+ const cwd = opts.runtimeContext.cwd;
13235
+ const loadEffectiveConfig = () => loadConfig({
13194
13236
  scope: opts.options.scope,
13195
- cwd: opts.runtimeContext.cwd,
13237
+ cwd,
13196
13238
  homedir: opts.runtimeContext.homedir
13197
13239
  }).effective;
13198
- const ignoreFileText = readIgnoreFileText(opts.runtimeContext.cwd);
13199
- const ignoreFilterOpts = {};
13200
- if (cfg.ignore.length > 0) ignoreFilterOpts.configIgnore = cfg.ignore;
13201
- if (ignoreFileText !== void 0) ignoreFilterOpts.ignoreFileText = ignoreFileText;
13202
- 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);
13203
13249
  const debounceMs = opts.debounceMsOverride ?? cfg.scan.watch.debounceMs;
13204
13250
  const pluginRuntime = opts.options.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: opts.options.scope });
13205
13251
  for (const warn of pluginRuntime.warnings) {
@@ -13230,7 +13276,12 @@ function createWatcherService(opts) {
13230
13276
  roots: [WATCH_ROOT],
13231
13277
  cwd: opts.runtimeContext.cwd,
13232
13278
  debounceMs,
13233
- 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,
13234
13285
  onBatch: async () => {
13235
13286
  if (stopped) return;
13236
13287
  try {
@@ -13257,6 +13308,42 @@ function createWatcherService(opts) {
13257
13308
  if ("ready" in chokidarHandle && chokidarHandle.ready instanceof Promise) {
13258
13309
  await chokidarHandle.ready;
13259
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
+ }
13260
13347
  await runInitialBatch({ isStopped: () => stopped, runOneBatch });
13261
13348
  opts.broadcaster.broadcast(
13262
13349
  buildWatcherStartedEvent({ roots: [WATCH_ROOT], debounceMs })
@@ -13271,19 +13358,23 @@ function createWatcherService(opts) {
13271
13358
  const stop = async () => {
13272
13359
  if (stopped) return;
13273
13360
  stopped = true;
13274
- if (chokidarHandle) {
13361
+ const closeQuietly = async (handle, label) => {
13362
+ if (!handle) return;
13275
13363
  try {
13276
- await chokidarHandle.close();
13364
+ await handle.close();
13277
13365
  } catch (err) {
13278
13366
  const message = err instanceof Error ? err.message : String(err);
13279
13367
  log.warn(
13280
13368
  tx(SERVER_TEXTS.watcherCloseFailed, {
13281
- message: sanitizeForTerminal(message)
13369
+ message: sanitizeForTerminal(`${label}: ${message}`)
13282
13370
  })
13283
13371
  );
13284
13372
  }
13285
- chokidarHandle = null;
13286
- }
13373
+ };
13374
+ await closeQuietly(metaHandle, "meta-watcher");
13375
+ metaHandle = null;
13376
+ await closeQuietly(chokidarHandle, "primary");
13377
+ chokidarHandle = null;
13287
13378
  };
13288
13379
  return { start, stop };
13289
13380
  }
@@ -13458,7 +13549,7 @@ function validateWatcherDebounce(value) {
13458
13549
 
13459
13550
  // server/paths.ts
13460
13551
  import { existsSync as existsSync17, statSync as statSync5 } from "fs";
13461
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join14, resolve as resolve19 } from "path";
13552
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join14, resolve as resolve21 } from "path";
13462
13553
  import { fileURLToPath as fileURLToPath5 } from "url";
13463
13554
  var DEFAULT_UI_REL = join14("ui", "dist", "ui", "browser");
13464
13555
  var PACKAGE_UI_REL = "ui";
@@ -13469,7 +13560,7 @@ function resolveDefaultUiDist(ctx) {
13469
13560
  return walkUpForUi(ctx.cwd);
13470
13561
  }
13471
13562
  function resolveExplicitUiDist(ctx, raw) {
13472
- return isAbsolute5(raw) ? raw : resolve19(ctx.cwd, raw);
13563
+ return isAbsolute5(raw) ? raw : resolve21(ctx.cwd, raw);
13473
13564
  }
13474
13565
  function isUiBundleDir(path) {
13475
13566
  if (!existsSync17(path)) return false;
@@ -13503,7 +13594,7 @@ function resolvePackageBundledUiFrom(here) {
13503
13594
  return null;
13504
13595
  }
13505
13596
  function walkUpForUi(startDir) {
13506
- let current = resolve19(startDir);
13597
+ let current = resolve21(startDir);
13507
13598
  for (let i = 0; i < 64; i++) {
13508
13599
  const candidate = join14(current, DEFAULT_UI_REL);
13509
13600
  if (isUiBundleDir(candidate)) return candidate;
@@ -14467,7 +14558,7 @@ var STUB_COMMANDS = [
14467
14558
  // cli/commands/tutorial.ts
14468
14559
  import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
14469
14560
  import { writeFile as writeFile2 } from "fs/promises";
14470
- import { dirname as dirname10, join as join15, resolve as resolve20 } from "path";
14561
+ import { dirname as dirname10, join as join15, resolve as resolve22 } from "path";
14471
14562
  import { fileURLToPath as fileURLToPath6 } from "url";
14472
14563
  import { Command as Command22, Option as Option22 } from "clipanion";
14473
14564
 
@@ -14543,11 +14634,11 @@ function readTutorialFromDisk() {
14543
14634
  const here = dirname10(fileURLToPath6(import.meta.url));
14544
14635
  const candidates = [
14545
14636
  // dev: src/cli/commands/ → repo-root .claude/skills/sm-tutorial/SKILL.md
14546
- resolve20(here, "../../../.claude/skills/sm-tutorial/SKILL.md"),
14637
+ resolve22(here, "../../../.claude/skills/sm-tutorial/SKILL.md"),
14547
14638
  // bundled: dist/cli.js → dist/cli/tutorial/sm-tutorial.md (sibling)
14548
- resolve20(here, "cli/tutorial/sm-tutorial.md"),
14639
+ resolve22(here, "cli/tutorial/sm-tutorial.md"),
14549
14640
  // bundled fallback: any-depth → cli/tutorial/sm-tutorial.md
14550
- resolve20(here, "../cli/tutorial/sm-tutorial.md")
14641
+ resolve22(here, "../cli/tutorial/sm-tutorial.md")
14551
14642
  ];
14552
14643
  for (const candidate of candidates) {
14553
14644
  if (existsSync19(candidate)) {