@skill-map/cli 0.14.0 → 0.15.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
@@ -7353,108 +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 "gu\xEDame" 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 trigger the sm-guide skill
7385
- ("gu\xEDame"). Top-level only \u2014 no subdirectory is created.
7386
-
7387
- Does NOT require an initialized .skill-map/ project. Refuses to
7388
- overwrite an existing sm-guide.md unless --force is passed.
7389
- `,
7390
- examples: [
7391
- ["Materialize the guide in the cwd", "$0 guide"],
7392
- ["Overwrite an existing sm-guide.md", "$0 guide --force"]
7393
- ]
7394
- });
7395
- force = Option8.Boolean("--force", false, {
7396
- description: "Overwrite an existing sm-guide.md without prompting."
7397
- });
7398
- async run() {
7399
- const ctx = defaultRuntimeContext();
7400
- const target = join10(ctx.cwd, SM_GUIDE_FILENAME);
7401
- if (await pathExists(target) && !this.force) {
7402
- this.context.stderr.write(tx(GUIDE_TEXTS.alreadyExists, { cwd: ctx.cwd }));
7403
- return ExitCode.Error;
7404
- }
7405
- let body;
7406
- try {
7407
- body = loadBundledGuideText();
7408
- } catch {
7409
- this.context.stderr.write(GUIDE_TEXTS.sourceMissing);
7410
- return ExitCode.Error;
7411
- }
7412
- try {
7413
- await writeFile(target, body);
7414
- } catch (err) {
7415
- this.context.stderr.write(
7416
- tx(GUIDE_TEXTS.writeFailed, { message: formatErrorMessage(err) })
7417
- );
7418
- return ExitCode.Error;
7419
- }
7420
- this.context.stdout.write(tx(GUIDE_TEXTS.written, { cwd: ctx.cwd }));
7421
- return ExitCode.Ok;
7422
- }
7423
- };
7424
- var cachedGuide = null;
7425
- function loadBundledGuideText() {
7426
- if (cachedGuide !== null) return cachedGuide;
7427
- cachedGuide = readGuideFromDisk();
7428
- return cachedGuide;
7429
- }
7430
- function readGuideFromDisk() {
7431
- const here = dirname9(fileURLToPath5(import.meta.url));
7432
- const candidates = [
7433
- // dev: src/cli/commands/ → repo-root .claude/skills/sm-guide/SKILL.md
7434
- resolve13(here, "../../../.claude/skills/sm-guide/SKILL.md"),
7435
- // bundled: dist/cli.js → dist/cli/guide/sm-guide.md (sibling)
7436
- resolve13(here, "cli/guide/sm-guide.md"),
7437
- // bundled fallback: any-depth → cli/guide/sm-guide.md
7438
- resolve13(here, "../cli/guide/sm-guide.md")
7439
- ];
7440
- for (const candidate of candidates) {
7441
- if (existsSync12(candidate)) {
7442
- return readFileSync10(candidate, "utf8");
7443
- }
7444
- }
7445
- throw new Error(`SKILL.md not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`);
7446
- }
7447
-
7448
7356
  // cli/commands/help.ts
7449
- import { readFileSync as readFileSync11 } from "fs";
7357
+ import { readFileSync as readFileSync10 } from "fs";
7450
7358
  import { createRequire as createRequire4 } from "module";
7451
- import { resolve as resolve14 } from "path";
7452
- 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";
7453
7361
 
7454
7362
  // package.json
7455
7363
  var package_default = {
7456
7364
  name: "@skill-map/cli",
7457
- version: "0.14.0",
7365
+ version: "0.15.0",
7458
7366
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
7459
7367
  license: "MIT",
7460
7368
  type: "module",
@@ -7513,7 +7421,7 @@ var package_default = {
7513
7421
  },
7514
7422
  dependencies: {
7515
7423
  "@hono/node-server": "2.0.1",
7516
- "@skill-map/spec": "0.14.1",
7424
+ "@skill-map/spec": "0.15.0",
7517
7425
  ajv: "8.18.0",
7518
7426
  "ajv-formats": "3.0.1",
7519
7427
  chokidar: "5.0.0",
@@ -7630,9 +7538,9 @@ var HELP_TEXTS = {
7630
7538
  };
7631
7539
 
7632
7540
  // cli/commands/help.ts
7633
- var HelpCommand = class extends Command9 {
7541
+ var HelpCommand = class extends Command8 {
7634
7542
  static paths = [["help"]];
7635
- static usage = Command9.Usage({
7543
+ static usage = Command8.Usage({
7636
7544
  category: "Introspection",
7637
7545
  description: "Self-describing introspection. --format human|md|json.",
7638
7546
  details: `
@@ -7646,8 +7554,8 @@ var HelpCommand = class extends Command9 {
7646
7554
  json \u2014 structured surface dump per spec/cli-contract.md.
7647
7555
  `
7648
7556
  });
7649
- verbParts = Option9.Rest({ required: 0 });
7650
- format = Option9.String("--format", "human");
7557
+ verbParts = Option8.Rest({ required: 0 });
7558
+ format = Option8.String("--format", "human");
7651
7559
  async execute() {
7652
7560
  const format = normalizeFormat(this.format);
7653
7561
  if (!format) {
@@ -7769,8 +7677,8 @@ function resolveSpecVersion() {
7769
7677
  try {
7770
7678
  const req = createRequire4(import.meta.url);
7771
7679
  const indexPath = req.resolve("@skill-map/spec/index.json");
7772
- const pkgPath = resolve14(indexPath, "..", "package.json");
7773
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
7680
+ const pkgPath = resolve13(indexPath, "..", "package.json");
7681
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
7774
7682
  return pkg.version;
7775
7683
  } catch {
7776
7684
  return "unknown";
@@ -7965,7 +7873,7 @@ function renderCompactOverview(verbs) {
7965
7873
  lines.push(HELP_TEXTS.compactFooter);
7966
7874
  return lines.join("\n") + "\n";
7967
7875
  }
7968
- var RootHelpCommand = class extends Command9 {
7876
+ var RootHelpCommand = class extends Command8 {
7969
7877
  static paths = [["-h"], ["--help"]];
7970
7878
  async execute() {
7971
7879
  const rawDefs = this.cli.definitions();
@@ -8021,13 +7929,13 @@ function registeredVerbPaths(cli2) {
8021
7929
  }
8022
7930
 
8023
7931
  // cli/commands/init.ts
8024
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
8025
- import { join as join11 } from "path";
8026
- 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";
8027
7935
 
8028
7936
  // kernel/orchestrator.ts
8029
7937
  import { createHash } from "crypto";
8030
- import { existsSync as existsSync13, statSync as statSync3 } from "fs";
7938
+ import { existsSync as existsSync12, statSync as statSync3 } from "fs";
8031
7939
  import { Tiktoken } from "js-tiktoken/lite";
8032
7940
  import cl100k_base from "js-tiktoken/ranks/cl100k_base";
8033
7941
  import yaml2 from "js-yaml";
@@ -8162,7 +8070,7 @@ function validateRoots(roots) {
8162
8070
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
8163
8071
  }
8164
8072
  for (const root of roots) {
8165
- if (!existsSync13(root) || !statSync3(root).isDirectory()) {
8073
+ if (!existsSync12(root) || !statSync3(root).isDirectory()) {
8166
8074
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
8167
8075
  }
8168
8076
  }
@@ -8938,10 +8846,10 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
8938
8846
  }
8939
8847
 
8940
8848
  // kernel/scan/watcher.ts
8941
- 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";
8942
8850
  import chokidar from "chokidar";
8943
8851
  function createChokidarWatcher(opts) {
8944
- const absRoots = opts.roots.map((r) => resolve15(opts.cwd, r));
8852
+ const absRoots = opts.roots.map((r) => resolve14(opts.cwd, r));
8945
8853
  const ignoreFilter = opts.ignoreFilter;
8946
8854
  const ignored = ignoreFilter ? (path) => {
8947
8855
  const rel = relativePathFromRoots(path, absRoots);
@@ -9187,7 +9095,7 @@ function createCliProgressEmitter(stderr) {
9187
9095
  // cli/commands/init.ts
9188
9096
  var InitCommand = class extends SmCommand {
9189
9097
  static paths = [["init"]];
9190
- static usage = Command10.Usage({
9098
+ static usage = Command9.Usage({
9191
9099
  category: "Setup",
9192
9100
  description: "Bootstrap the current scope: scaffold .skill-map/, provision DB, run first scan.",
9193
9101
  details: `
@@ -9211,16 +9119,16 @@ var InitCommand = class extends SmCommand {
9211
9119
  ["Preview what would be created", "$0 init --dry-run"]
9212
9120
  ]
9213
9121
  });
9214
- noScan = Option10.Boolean("--no-scan", false, {
9122
+ noScan = Option9.Boolean("--no-scan", false, {
9215
9123
  description: "Skip the first scan after scaffolding."
9216
9124
  });
9217
- force = Option10.Boolean("--force", false, {
9125
+ force = Option9.Boolean("--force", false, {
9218
9126
  description: "Overwrite an existing settings.json / settings.local.json / .skill-mapignore."
9219
9127
  });
9220
- strict = Option10.Boolean("--strict", false, {
9128
+ strict = Option9.Boolean("--strict", false, {
9221
9129
  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."
9222
9130
  });
9223
- dryRun = Option10.Boolean("-n,--dry-run", false, {
9131
+ dryRun = Option9.Boolean("-n,--dry-run", false, {
9224
9132
  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."
9225
9133
  });
9226
9134
  // CLI orchestrator: paths setup + dry-run branch (delegated to
@@ -9231,7 +9139,7 @@ var InitCommand = class extends SmCommand {
9231
9139
  async run() {
9232
9140
  const ctx = defaultRuntimeContext();
9233
9141
  const scopeRoot = this.global ? ctx.homedir : ctx.cwd;
9234
- const skillMapDir = join11(scopeRoot, SKILL_MAP_DIR);
9142
+ const skillMapDir = join10(scopeRoot, SKILL_MAP_DIR);
9235
9143
  const settingsPath = defaultSettingsPath(scopeRoot);
9236
9144
  const localPath = defaultLocalSettingsPath(scopeRoot);
9237
9145
  const ignorePath = defaultIgnoreFilePath(scopeRoot);
@@ -9255,17 +9163,17 @@ var InitCommand = class extends SmCommand {
9255
9163
  return ExitCode.Ok;
9256
9164
  }
9257
9165
  await mkdir2(skillMapDir, { recursive: true });
9258
- await writeFile2(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
9166
+ await writeFile(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
9259
9167
  if (!await pathExists(localPath) || this.force) {
9260
- await writeFile2(localPath, "{}\n");
9168
+ await writeFile(localPath, "{}\n");
9261
9169
  }
9262
9170
  if (!await pathExists(ignorePath) || this.force) {
9263
- await writeFile2(ignorePath, loadBundledIgnoreText());
9171
+ await writeFile(ignorePath, loadBundledIgnoreText());
9264
9172
  }
9265
9173
  if (!this.global) {
9266
9174
  const updated = await ensureGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
9267
9175
  if (updated) {
9268
- const gitignorePath = join11(scopeRoot, ".gitignore");
9176
+ const gitignorePath = join10(scopeRoot, ".gitignore");
9269
9177
  this.context.stdout.write(
9270
9178
  GITIGNORE_ENTRIES.length === 1 ? tx(INIT_TEXTS.gitignoreUpdatedSingular, { path: gitignorePath }) : tx(INIT_TEXTS.gitignoreUpdatedPlural, {
9271
9179
  path: gitignorePath,
@@ -9304,7 +9212,7 @@ async function dryRunFileMessage(path) {
9304
9212
  }
9305
9213
  async function writeDryRunGitignorePlan(stdout, scopeRoot) {
9306
9214
  const wouldAdd = await previewGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
9307
- const gitignorePath = join11(scopeRoot, ".gitignore");
9215
+ const gitignorePath = join10(scopeRoot, ".gitignore");
9308
9216
  if (wouldAdd.length === 0) {
9309
9217
  stdout.write(tx(INIT_TEXTS.dryRunWouldLeaveGitignoreUnchanged, { path: gitignorePath }));
9310
9218
  } else if (wouldAdd.length === 1) {
@@ -9324,13 +9232,13 @@ async function writeDryRunGitignorePlan(stdout, scopeRoot) {
9324
9232
  );
9325
9233
  }
9326
9234
  }
9327
- async function runFirstScan(scopeRoot, homedir2, dbPath, strict, stdout, stderr) {
9235
+ async function runFirstScan(scopeRoot, homedir3, dbPath, strict, stdout, stderr) {
9328
9236
  stdout.write(INIT_TEXTS.runningFirstScan);
9329
9237
  const kernel = createKernel();
9330
9238
  for (const manifest of listBuiltIns()) kernel.registry.register(manifest);
9331
9239
  let cfg;
9332
9240
  try {
9333
- cfg = loadConfig({ scope: "project", cwd: scopeRoot, homedir: homedir2, strict }).effective;
9241
+ cfg = loadConfig({ scope: "project", cwd: scopeRoot, homedir: homedir3, strict }).effective;
9334
9242
  } catch (err) {
9335
9243
  const message = formatErrorMessage(err);
9336
9244
  stderr.write(tx(INIT_TEXTS.configLoadFailure, { message }));
@@ -9379,7 +9287,7 @@ async function runFirstScan(scopeRoot, homedir2, dbPath, strict, stdout, stderr)
9379
9287
  return hasErrors ? ExitCode.Issues : ExitCode.Ok;
9380
9288
  }
9381
9289
  async function previewGitignoreEntries(scopeRoot, entries) {
9382
- const path = join11(scopeRoot, ".gitignore");
9290
+ const path = join10(scopeRoot, ".gitignore");
9383
9291
  const body = await pathExists(path) ? await readFile2(path, "utf8") : "";
9384
9292
  const present = new Set(
9385
9293
  body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
@@ -9387,7 +9295,7 @@ async function previewGitignoreEntries(scopeRoot, entries) {
9387
9295
  return entries.filter((entry) => !present.has(entry));
9388
9296
  }
9389
9297
  async function ensureGitignoreEntries(scopeRoot, entries) {
9390
- const path = join11(scopeRoot, ".gitignore");
9298
+ const path = join10(scopeRoot, ".gitignore");
9391
9299
  let body = "";
9392
9300
  if (await pathExists(path)) {
9393
9301
  body = await readFile2(path, "utf8");
@@ -9404,12 +9312,12 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
9404
9312
  present.add(entry);
9405
9313
  changed = true;
9406
9314
  }
9407
- if (changed) await writeFile2(path, body);
9315
+ if (changed) await writeFile(path, body);
9408
9316
  return changed;
9409
9317
  }
9410
9318
 
9411
9319
  // cli/commands/history.ts
9412
- import { Command as Command11, Option as Option11 } from "clipanion";
9320
+ import { Command as Command10, Option as Option10 } from "clipanion";
9413
9321
 
9414
9322
  // cli/i18n/option-validators.texts.ts
9415
9323
  var OPTION_VALIDATORS_TEXTS = {
@@ -9498,7 +9406,7 @@ function parseStatuses(input, stderr) {
9498
9406
  }
9499
9407
  var HistoryCommand = class extends SmCommand {
9500
9408
  static paths = [["history"]];
9501
- static usage = Command11.Usage({
9409
+ static usage = Command10.Usage({
9502
9410
  category: "History",
9503
9411
  description: "Filter execution records. --json emits an array conforming to execution-record.schema.json.",
9504
9412
  details: `
@@ -9518,12 +9426,12 @@ var HistoryCommand = class extends SmCommand {
9518
9426
  ["Machine-readable, scoped to one node", "$0 history -n skills/foo.md --json"]
9519
9427
  ]
9520
9428
  });
9521
- node = Option11.String("-n", { required: false });
9522
- action = Option11.String("--action", { required: false });
9523
- status = Option11.String("--status", { required: false });
9524
- since = Option11.String("--since", { required: false });
9525
- until = Option11.String("--until", { required: false });
9526
- limit = Option11.String("--limit", { required: false });
9429
+ node = Option10.String("-n", { required: false });
9430
+ action = Option10.String("--action", { required: false });
9431
+ status = Option10.String("--status", { required: false });
9432
+ since = Option10.String("--since", { required: false });
9433
+ until = Option10.String("--until", { required: false });
9434
+ limit = Option10.String("--limit", { required: false });
9527
9435
  // CLI list verb: many optional filter flags (`--node`, `--action`,
9528
9436
  // `--status`, `--since`, `--until`, `--limit`, `--json`, `--quiet`)
9529
9437
  // each adding a guarded mutation to the filter or render path. Each
@@ -9571,7 +9479,7 @@ var HistoryCommand = class extends SmCommand {
9571
9479
  };
9572
9480
  var HistoryStatsCommand = class extends SmCommand {
9573
9481
  static paths = [["history", "stats"]];
9574
- static usage = Command11.Usage({
9482
+ static usage = Command10.Usage({
9575
9483
  category: "History",
9576
9484
  description: "Aggregate counts, tokens, periods, top nodes, and error rates over state_executions. --json conforms to history-stats.schema.json.",
9577
9485
  details: `
@@ -9589,10 +9497,10 @@ var HistoryStatsCommand = class extends SmCommand {
9589
9497
  ["Top 5 nodes, JSON", "$0 history stats --top 5 --json"]
9590
9498
  ]
9591
9499
  });
9592
- since = Option11.String("--since", { required: false });
9593
- until = Option11.String("--until", { required: false });
9594
- period = Option11.String("--period", { required: false });
9595
- top = Option11.String("--top", { required: false });
9500
+ since = Option10.String("--since", { required: false });
9501
+ until = Option10.String("--until", { required: false });
9502
+ period = Option10.String("--period", { required: false });
9503
+ top = Option10.String("--top", { required: false });
9596
9504
  // CLI stats verb: range parsing + window flags + period flag + JSON
9597
9505
  // branch + per-period iteration. Each branch is a single-purpose
9598
9506
  // gate; the data work lives in `aggregateHistoryStats`.
@@ -9773,11 +9681,11 @@ function formatRow(...cols) {
9773
9681
 
9774
9682
  // cli/commands/jobs.ts
9775
9683
  import { unlink } from "fs/promises";
9776
- import { Command as Command12, Option as Option12 } from "clipanion";
9684
+ import { Command as Command11, Option as Option11 } from "clipanion";
9777
9685
 
9778
9686
  // kernel/jobs/orphan-files.ts
9779
9687
  import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
9780
- import { join as join12, resolve as resolve16 } from "path";
9688
+ import { join as join11, resolve as resolve15 } from "path";
9781
9689
  function findOrphanJobFiles(jobsDir, referencedPaths) {
9782
9690
  let entries;
9783
9691
  try {
@@ -9792,7 +9700,7 @@ function findOrphanJobFiles(jobsDir, referencedPaths) {
9792
9700
  const orphans = [];
9793
9701
  for (const name of entries) {
9794
9702
  if (!name.endsWith(".md")) continue;
9795
- const abs = resolve16(join12(jobsDir, name));
9703
+ const abs = resolve15(join11(jobsDir, name));
9796
9704
  if (!referencedPaths.has(abs)) orphans.push(abs);
9797
9705
  }
9798
9706
  orphans.sort();
@@ -9821,7 +9729,7 @@ var JOBS_TEXTS = {
9821
9729
  // cli/commands/jobs.ts
9822
9730
  var JobPruneCommand = class extends SmCommand {
9823
9731
  static paths = [["job", "prune"]];
9824
- static usage = Command12.Usage({
9732
+ static usage = Command11.Usage({
9825
9733
  category: "Jobs",
9826
9734
  description: "Retention GC for completed / failed jobs (per config policy). --orphan-files removes MD files with no DB row.",
9827
9735
  details: `
@@ -9848,10 +9756,10 @@ var JobPruneCommand = class extends SmCommand {
9848
9756
  ["Preview without touching the DB", "$0 job prune --dry-run --json"]
9849
9757
  ]
9850
9758
  });
9851
- orphanFiles = Option12.Boolean("--orphan-files", false, {
9759
+ orphanFiles = Option11.Boolean("--orphan-files", false, {
9852
9760
  description: "Also remove MD files in .skill-map/jobs/ that have no matching state_jobs row."
9853
9761
  });
9854
- dryRun = Option12.Boolean("-n,--dry-run", false, {
9762
+ dryRun = Option11.Boolean("-n,--dry-run", false, {
9855
9763
  description: "Report what would be pruned without touching the DB or filesystem."
9856
9764
  });
9857
9765
  async run() {
@@ -9968,7 +9876,7 @@ function formatPolicy(seconds) {
9968
9876
  }
9969
9877
 
9970
9878
  // cli/commands/list.ts
9971
- import { Command as Command13, Option as Option13 } from "clipanion";
9879
+ import { Command as Command12, Option as Option12 } from "clipanion";
9972
9880
 
9973
9881
  // cli/i18n/list.texts.ts
9974
9882
  var LIST_TEXTS = {
@@ -9996,7 +9904,7 @@ var SORT_BY = {
9996
9904
  var PATH_COL_WIDTH = 50;
9997
9905
  var ListCommand = class extends SmCommand {
9998
9906
  static paths = [["list"]];
9999
- static usage = Command13.Usage({
9907
+ static usage = Command12.Usage({
10000
9908
  category: "Browse",
10001
9909
  description: "Tabular listing of nodes. --json emits an array conforming to node.schema.json.",
10002
9910
  details: `
@@ -10017,10 +9925,10 @@ var ListCommand = class extends SmCommand {
10017
9925
  ["Only nodes with issues, machine-readable", "$0 list --issue --json"]
10018
9926
  ]
10019
9927
  });
10020
- kind = Option13.String("--kind", { required: false });
10021
- issue = Option13.Boolean("--issue", false);
10022
- sortBy = Option13.String("--sort-by", { required: false });
10023
- limit = Option13.String("--limit", { required: false });
9928
+ kind = Option12.String("--kind", { required: false });
9929
+ issue = Option12.Boolean("--issue", false);
9930
+ sortBy = Option12.String("--sort-by", { required: false });
9931
+ limit = Option12.String("--limit", { required: false });
10024
9932
  async run() {
10025
9933
  let sortColumn = "path";
10026
9934
  let sortDirection = "asc";
@@ -10122,7 +10030,7 @@ function formatRow2(path, kind, out, inCount, ext, issues, bytes) {
10122
10030
  }
10123
10031
 
10124
10032
  // cli/commands/orphans.ts
10125
- import { Command as Command14, Option as Option14 } from "clipanion";
10033
+ import { Command as Command13, Option as Option13 } from "clipanion";
10126
10034
 
10127
10035
  // cli/i18n/orphans.texts.ts
10128
10036
  var ORPHANS_TEXTS = {
@@ -10177,7 +10085,7 @@ function isStringArray(v) {
10177
10085
  }
10178
10086
  var OrphansCommand = class extends SmCommand {
10179
10087
  static paths = [["orphans"]];
10180
- static usage = Command14.Usage({
10088
+ static usage = Command13.Usage({
10181
10089
  category: "Browse",
10182
10090
  description: "List orphan / auto-rename issues from the last scan. --json emits an array conforming to issue.schema.json.",
10183
10091
  details: `
@@ -10192,7 +10100,7 @@ var OrphansCommand = class extends SmCommand {
10192
10100
  ["Just the ambiguous ones, JSON", "$0 orphans --kind ambiguous --json"]
10193
10101
  ]
10194
10102
  });
10195
- kind = Option14.String("--kind", { required: false });
10103
+ kind = Option13.String("--kind", { required: false });
10196
10104
  async run() {
10197
10105
  let ruleFilter = null;
10198
10106
  if (this.kind !== void 0) {
@@ -10230,7 +10138,7 @@ var OrphansCommand = class extends SmCommand {
10230
10138
  };
10231
10139
  var OrphansReconcileCommand = class extends SmCommand {
10232
10140
  static paths = [["orphans", "reconcile"]];
10233
- static usage = Command14.Usage({
10141
+ static usage = Command13.Usage({
10234
10142
  category: "Browse",
10235
10143
  description: "Migrate state_* FKs from an orphan path to a live node, resolving the orphan issue.",
10236
10144
  details: `
@@ -10246,9 +10154,9 @@ var OrphansReconcileCommand = class extends SmCommand {
10246
10154
  ["Reattach orphan history", "$0 orphans reconcile skills/old.md --to skills/new.md"]
10247
10155
  ]
10248
10156
  });
10249
- orphanPath = Option14.String({ required: true });
10250
- to = Option14.String("--to", { required: true });
10251
- dryRun = Option14.Boolean("-n,--dry-run", false);
10157
+ orphanPath = Option13.String({ required: true });
10158
+ to = Option13.String("--to", { required: true });
10159
+ dryRun = Option13.Boolean("-n,--dry-run", false);
10252
10160
  async run() {
10253
10161
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
10254
10162
  if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
@@ -10318,7 +10226,7 @@ var OrphansReconcileCommand = class extends SmCommand {
10318
10226
  };
10319
10227
  var OrphansUndoRenameCommand = class extends SmCommand {
10320
10228
  static paths = [["orphans", "undo-rename"]];
10321
- static usage = Command14.Usage({
10229
+ static usage = Command13.Usage({
10322
10230
  category: "Browse",
10323
10231
  description: "Reverse a medium- or ambiguous-confidence auto-rename. Migrates state_* FKs back, emits a new orphan on the prior path.",
10324
10232
  details: `
@@ -10338,10 +10246,10 @@ var OrphansUndoRenameCommand = class extends SmCommand {
10338
10246
  ["Undo an ambiguous, picking a candidate", "$0 orphans undo-rename skills/new.md --from skills/old-a.md"]
10339
10247
  ]
10340
10248
  });
10341
- newPath = Option14.String({ required: true });
10342
- from = Option14.String("--from", { required: false });
10343
- force = Option14.Boolean("--force", false);
10344
- dryRun = Option14.Boolean("-n,--dry-run", false);
10249
+ newPath = Option13.String({ required: true });
10250
+ from = Option13.String("--from", { required: false });
10251
+ force = Option13.Boolean("--force", false);
10252
+ dryRun = Option13.Boolean("-n,--dry-run", false);
10345
10253
  async run() {
10346
10254
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
10347
10255
  if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
@@ -10505,9 +10413,9 @@ var ORPHANS_COMMANDS = [
10505
10413
  ];
10506
10414
 
10507
10415
  // cli/commands/plugins.ts
10508
- import { existsSync as existsSync14 } from "fs";
10509
- import { join as join13, resolve as resolve17 } from "path";
10510
- import { Command as Command15, Option as Option15 } from "clipanion";
10416
+ import { existsSync as existsSync13 } from "fs";
10417
+ import { join as join12, resolve as resolve16 } from "path";
10418
+ import { Command as Command14, Option as Option14 } from "clipanion";
10511
10419
 
10512
10420
  // cli/i18n/plugins.texts.ts
10513
10421
  var PLUGINS_TEXTS = {
@@ -10554,8 +10462,8 @@ var PLUGINS_TEXTS = {
10554
10462
  rowStatusOkPad: "ok ",
10555
10463
  rowStatusOffPad: "off ",
10556
10464
  builtInBundleHeader: "{{status}} {{id}}@built-in (granularity={{granularity}})",
10557
- builtInBundleKindsLine: " {{kinds}}",
10558
- builtInExtensionRow: " {{stat}} {{kind}}:{{qualifiedId}}@{{version}}",
10465
+ builtInBundleKindsLine: " {{kinds}}",
10466
+ builtInExtensionRow: "{{stat}} {{kind}}:{{qualifiedId}}@{{version}}",
10559
10467
  pluginRow: "{{statusIcon}} {{id}}@{{version}}{{granularitySuffix}}{{tail}}",
10560
10468
  pluginRowGranularitySuffix: " (granularity={{granularity}})",
10561
10469
  pluginRowTailEnabled: " \xB7 {{kinds}}",
@@ -10582,9 +10490,9 @@ var PLUGINS_TEXTS = {
10582
10490
  };
10583
10491
 
10584
10492
  // cli/commands/plugins.ts
10585
- function resolveSearchPaths2(opts, cwd, homedir2) {
10586
- if (opts.pluginDir) return [resolve17(opts.pluginDir)];
10587
- const ctx = { cwd, homedir: homedir2 };
10493
+ function resolveSearchPaths2(opts, cwd, homedir3) {
10494
+ if (opts.pluginDir) return [resolve16(opts.pluginDir)];
10495
+ const ctx = { cwd, homedir: homedir3 };
10588
10496
  const project = defaultProjectPluginsDir(ctx);
10589
10497
  const user = defaultUserPluginsDir(ctx);
10590
10498
  return opts.global ? [user] : [project, user];
@@ -10652,12 +10560,12 @@ function builtInRows(resolveEnabled) {
10652
10560
  }
10653
10561
  var PluginsListCommand = class extends SmCommand {
10654
10562
  static paths = [["plugins", "list"]];
10655
- static usage = Command15.Usage({
10563
+ static usage = Command14.Usage({
10656
10564
  category: "Plugins",
10657
10565
  description: "List discovered plugins and their load status.",
10658
10566
  details: "Scans <scope>/.skill-map/plugins and ~/.skill-map/plugins (or --plugin-dir <path>). Built-in bundles (claude, core) are listed alongside user plugins."
10659
10567
  });
10660
- pluginDir = Option15.String("--plugin-dir", { required: false });
10568
+ pluginDir = Option14.String("--plugin-dir", { required: false });
10661
10569
  async run() {
10662
10570
  const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
10663
10571
  const resolveEnabled = await buildResolver(this.global);
@@ -10687,13 +10595,15 @@ function renderBuiltInBundleRow(bundle) {
10687
10595
  })
10688
10596
  );
10689
10597
  if (bundle.granularity === "bundle") {
10690
- const kinds = bundle.extensions.map((e) => `${e.kind}:${qualifiedExtensionId(bundle.id, e.id)}`).join(", ");
10691
- lines.push(tx(PLUGINS_TEXTS.builtInBundleKindsLine, { kinds }));
10598
+ for (const ext of bundle.extensions) {
10599
+ const line = `${ext.kind}:${qualifiedExtensionId(bundle.id, ext.id)}@${ext.version}`;
10600
+ lines.push(tx(PLUGINS_TEXTS.builtInBundleKindsLine, { kinds: line }));
10601
+ }
10692
10602
  } else {
10693
10603
  for (const ext of bundle.extensions) {
10694
10604
  lines.push(
10695
10605
  tx(PLUGINS_TEXTS.builtInExtensionRow, {
10696
- stat: ext.enabled ? PLUGINS_TEXTS.rowStatusOkPad : PLUGINS_TEXTS.rowStatusOffPad,
10606
+ stat: ext.enabled ? PLUGINS_TEXTS.rowStatusOk : PLUGINS_TEXTS.rowStatusOff,
10697
10607
  kind: ext.kind,
10698
10608
  qualifiedId: qualifiedExtensionId(bundle.id, ext.id),
10699
10609
  version: ext.version
@@ -10719,12 +10629,12 @@ function renderPluginRow(p) {
10719
10629
  }
10720
10630
  var PluginsShowCommand = class extends SmCommand {
10721
10631
  static paths = [["plugins", "show"]];
10722
- static usage = Command15.Usage({
10632
+ static usage = Command14.Usage({
10723
10633
  category: "Plugins",
10724
10634
  description: "Show a single plugin's manifest + loaded extensions."
10725
10635
  });
10726
- id = Option15.String({ required: true });
10727
- pluginDir = Option15.String("--plugin-dir", { required: false });
10636
+ id = Option14.String({ required: true });
10637
+ pluginDir = Option14.String("--plugin-dir", { required: false });
10728
10638
  async run() {
10729
10639
  const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
10730
10640
  const resolveEnabled = await buildResolver(this.global);
@@ -10899,18 +10809,18 @@ function appendUnknownKindWarnings(out, extractorQualifiedId, applicableKinds, k
10899
10809
  if (!knownKinds.has(k)) out.push({ extractorQualifiedId, unknownKind: k });
10900
10810
  }
10901
10811
  }
10902
- function expandHome(p, homedir2) {
10903
- if (p === "~") return homedir2;
10904
- if (p.startsWith("~/")) return join13(homedir2, p.slice(2));
10812
+ function expandHome(p, homedir3) {
10813
+ if (p === "~") return homedir3;
10814
+ if (p.startsWith("~/")) return join12(homedir3, p.slice(2));
10905
10815
  return p;
10906
10816
  }
10907
- function collectExplorationDirWarnings(plugins, homedir2) {
10817
+ function collectExplorationDirWarnings(plugins, homedir3) {
10908
10818
  const out = [];
10909
10819
  forEachProviderInstance(plugins, ({ id, pluginId, instance }) => {
10910
10820
  const dir = instance["explorationDir"];
10911
10821
  if (typeof dir !== "string" || dir.length === 0) return;
10912
- const resolved = expandHome(dir, homedir2);
10913
- if (!existsSync14(resolved)) {
10822
+ const resolved = expandHome(dir, homedir3);
10823
+ if (!existsSync13(resolved)) {
10914
10824
  out.push({
10915
10825
  providerQualifiedId: qualifiedExtensionId(pluginId, id),
10916
10826
  explorationDir: dir,
@@ -10922,12 +10832,12 @@ function collectExplorationDirWarnings(plugins, homedir2) {
10922
10832
  }
10923
10833
  var PluginsDoctorCommand = class extends SmCommand {
10924
10834
  static paths = [["plugins", "doctor"]];
10925
- static usage = Command15.Usage({
10835
+ static usage = Command14.Usage({
10926
10836
  category: "Plugins",
10927
10837
  description: "Run the full load pass and summarise by failure mode.",
10928
10838
  details: "Exit code 0 when every plugin loads or is intentionally disabled; 1 when any plugin is in an error / incompat state."
10929
10839
  });
10930
- pluginDir = Option15.String("--plugin-dir", { required: false });
10840
+ pluginDir = Option14.String("--plugin-dir", { required: false });
10931
10841
  // Doctor verb: counts by status + applicableKinds warnings +
10932
10842
  // explorationDir warnings + bad-plugins issues, each with its own
10933
10843
  // gated render. Branching is intrinsic to the multi-section diagnostic
@@ -11093,8 +11003,8 @@ function resolveToggleTarget(id, catalogue, verb) {
11093
11003
  return { key: bundle.id };
11094
11004
  }
11095
11005
  var TogglePluginsBase = class extends SmCommand {
11096
- all = Option15.Boolean("--all", false);
11097
- id = Option15.String({ required: false });
11006
+ all = Option14.Boolean("--all", false);
11007
+ id = Option14.String({ required: false });
11098
11008
  // eslint-disable-next-line complexity
11099
11009
  async toggle(enabled) {
11100
11010
  const verb = enabled ? "enable" : "disable";
@@ -11145,7 +11055,7 @@ var TogglePluginsBase = class extends SmCommand {
11145
11055
  };
11146
11056
  var PluginsEnableCommand = class extends TogglePluginsBase {
11147
11057
  static paths = [["plugins", "enable"]];
11148
- static usage = Command15.Usage({
11058
+ static usage = Command14.Usage({
11149
11059
  category: "Plugins",
11150
11060
  description: "Enable a plugin (or --all). Persists in config_plugins.",
11151
11061
  details: `
@@ -11167,7 +11077,7 @@ var PluginsEnableCommand = class extends TogglePluginsBase {
11167
11077
  };
11168
11078
  var PluginsDisableCommand = class extends TogglePluginsBase {
11169
11079
  static paths = [["plugins", "disable"]];
11170
- static usage = Command15.Usage({
11080
+ static usage = Command14.Usage({
11171
11081
  category: "Plugins",
11172
11082
  description: "Disable a plugin (or --all). Persists in config_plugins; does not delete files.",
11173
11083
  details: `
@@ -11203,8 +11113,8 @@ var PLUGIN_COMMANDS = [
11203
11113
 
11204
11114
  // cli/commands/refresh.ts
11205
11115
  import { readFile as readFile3 } from "fs/promises";
11206
- import { resolve as resolve19 } from "path";
11207
- import { Command as Command16, Option as Option16 } from "clipanion";
11116
+ import { resolve as resolve18 } from "path";
11117
+ import { Command as Command15, Option as Option15 } from "clipanion";
11208
11118
 
11209
11119
  // cli/i18n/refresh.texts.ts
11210
11120
  var REFRESH_TEXTS = {
@@ -11233,12 +11143,12 @@ var REFRESH_TEXTS = {
11233
11143
  };
11234
11144
 
11235
11145
  // cli/util/path-guard.ts
11236
- import { isAbsolute as isAbsolute3, resolve as resolve18, sep as sep3 } from "path";
11146
+ import { isAbsolute as isAbsolute3, resolve as resolve17, sep as sep3 } from "path";
11237
11147
  function assertContained2(cwd, rel) {
11238
11148
  if (isAbsolute3(rel)) {
11239
11149
  throw new Error(`node path is absolute, refusing to read: ${rel}`);
11240
11150
  }
11241
- const abs = resolve18(cwd, rel);
11151
+ const abs = resolve17(cwd, rel);
11242
11152
  if (abs !== cwd && !abs.startsWith(cwd + sep3)) {
11243
11153
  throw new Error(`node path escapes repo root: ${rel}`);
11244
11154
  }
@@ -11247,7 +11157,7 @@ function assertContained2(cwd, rel) {
11247
11157
  // cli/commands/refresh.ts
11248
11158
  var RefreshCommand = class extends SmCommand {
11249
11159
  static paths = [["refresh"]];
11250
- static usage = Command16.Usage({
11160
+ static usage = Command15.Usage({
11251
11161
  category: "Scan",
11252
11162
  description: "Refresh enrichment rows: granular (single node) or batch (every stale row).",
11253
11163
  details: `
@@ -11272,11 +11182,11 @@ var RefreshCommand = class extends SmCommand {
11272
11182
  ["Refresh every node with stale enrichments", "$0 refresh --stale"]
11273
11183
  ]
11274
11184
  });
11275
- nodePath = Option16.String({ name: "node", required: false });
11276
- stale = Option16.Boolean("--stale", false, {
11185
+ nodePath = Option15.String({ name: "node", required: false });
11186
+ stale = Option15.Boolean("--stale", false, {
11277
11187
  description: "Refresh every node whose probabilistic enrichment row is flagged stale=1."
11278
11188
  });
11279
- noPlugins = Option16.Boolean("--no-plugins", false, {
11189
+ noPlugins = Option15.Boolean("--no-plugins", false, {
11280
11190
  description: "Skip drop-in plugin discovery; use only the built-in extractor set."
11281
11191
  });
11282
11192
  // The remaining cyclomatic count comes from CLI ergonomics that don't
@@ -11412,7 +11322,7 @@ var RefreshCommand = class extends SmCommand {
11412
11322
  let body;
11413
11323
  try {
11414
11324
  assertContained2(cwd, node.path);
11415
- const raw = await readFile3(resolve19(cwd, node.path), "utf8");
11325
+ const raw = await readFile3(resolve18(cwd, node.path), "utf8");
11416
11326
  body = stripFrontmatterFence(raw);
11417
11327
  } catch (err) {
11418
11328
  this.context.stderr.write(
@@ -11461,7 +11371,7 @@ function stripFrontmatterFence(text) {
11461
11371
  var REFRESH_COMMANDS = [RefreshCommand];
11462
11372
 
11463
11373
  // cli/commands/scan.ts
11464
- import { Command as Command18, Option as Option18 } from "clipanion";
11374
+ import { Command as Command17, Option as Option17 } from "clipanion";
11465
11375
 
11466
11376
  // cli/i18n/scan.texts.ts
11467
11377
  var SCAN_TEXTS = {
@@ -11653,7 +11563,7 @@ async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
11653
11563
  }
11654
11564
 
11655
11565
  // cli/commands/watch.ts
11656
- import { Command as Command17, Option as Option17 } from "clipanion";
11566
+ import { Command as Command16, Option as Option16 } from "clipanion";
11657
11567
 
11658
11568
  // cli/i18n/watch.texts.ts
11659
11569
  var WATCH_TEXTS = {
@@ -11832,7 +11742,7 @@ async function runWatchLoop(opts) {
11832
11742
  }
11833
11743
  var WatchCommand = class extends SmCommand {
11834
11744
  static paths = [["watch"]];
11835
- static usage = Command17.Usage({
11745
+ static usage = Command16.Usage({
11836
11746
  category: "Scan",
11837
11747
  description: "Watch roots and run an incremental scan after each debounced batch of filesystem events.",
11838
11748
  details: `
@@ -11856,17 +11766,17 @@ var WatchCommand = class extends SmCommand {
11856
11766
  ["Stream ScanResult per batch as ndjson", "$0 watch --json"]
11857
11767
  ]
11858
11768
  });
11859
- roots = Option17.Rest({ name: "roots" });
11860
- noTokens = Option17.Boolean("--no-tokens", false, {
11769
+ roots = Option16.Rest({ name: "roots" });
11770
+ noTokens = Option16.Boolean("--no-tokens", false, {
11861
11771
  description: "Skip per-node token counts (cl100k_base BPE)."
11862
11772
  });
11863
- strict = Option17.Boolean("--strict", false, {
11773
+ strict = Option16.Boolean("--strict", false, {
11864
11774
  description: "Promote frontmatter-validation findings from warn to error inside each batch. Does not change the watcher exit code."
11865
11775
  });
11866
- noPlugins = Option17.Boolean("--no-plugins", false, {
11776
+ noPlugins = Option16.Boolean("--no-plugins", false, {
11867
11777
  description: "Skip drop-in plugin discovery for the watcher session."
11868
11778
  });
11869
- maxConsecutiveFailures = Option17.String("--max-consecutive-failures", {
11779
+ maxConsecutiveFailures = Option16.String("--max-consecutive-failures", {
11870
11780
  required: false,
11871
11781
  description: "Shut down with exit 2 after N consecutive batch failures (default 5; 0 disables the breaker)."
11872
11782
  });
@@ -11904,7 +11814,7 @@ function parseBreakerLimit(raw, stderr) {
11904
11814
  // cli/commands/scan.ts
11905
11815
  var ScanCommand = class extends SmCommand {
11906
11816
  static paths = [["scan"]];
11907
- static usage = Command18.Usage({
11817
+ static usage = Command17.Usage({
11908
11818
  category: "Scan",
11909
11819
  description: "Scan roots for markdown nodes, run extractors and rules.",
11910
11820
  details: `
@@ -11933,29 +11843,29 @@ var ScanCommand = class extends SmCommand {
11933
11843
  ["What would the next incremental scan persist?", "$0 scan --changed -n --json"]
11934
11844
  ]
11935
11845
  });
11936
- roots = Option18.Rest({ name: "roots" });
11937
- noBuiltIns = Option18.Boolean("--no-built-ins", false, {
11846
+ roots = Option17.Rest({ name: "roots" });
11847
+ noBuiltIns = Option17.Boolean("--no-built-ins", false, {
11938
11848
  description: "Skip the built-in extension set. Yields a zero-filled ScanResult (kernel-empty-boot parity); skips DB persistence."
11939
11849
  });
11940
- noPlugins = Option18.Boolean("--no-plugins", false, {
11850
+ noPlugins = Option17.Boolean("--no-plugins", false, {
11941
11851
  description: "Skip drop-in plugin discovery. Only the built-in set runs. Combine with --no-built-ins for a fully empty pipeline."
11942
11852
  });
11943
- noTokens = Option18.Boolean("--no-tokens", false, {
11853
+ noTokens = Option17.Boolean("--no-tokens", false, {
11944
11854
  description: "Skip per-node token counts (cl100k_base BPE). Leaves node.tokens undefined; spec-valid since the field is optional."
11945
11855
  });
11946
- dryRun = Option18.Boolean("-n,--dry-run", false, {
11856
+ dryRun = Option17.Boolean("-n,--dry-run", false, {
11947
11857
  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."
11948
11858
  });
11949
- changed = Option18.Boolean("--changed", false, {
11859
+ changed = Option17.Boolean("--changed", false, {
11950
11860
  description: "Incremental scan: reuse unchanged nodes from the persisted prior snapshot. Degrades to a full scan if no prior snapshot exists."
11951
11861
  });
11952
- allowEmpty = Option18.Boolean("--allow-empty", false, {
11862
+ allowEmpty = Option17.Boolean("--allow-empty", false, {
11953
11863
  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."
11954
11864
  });
11955
- strict = Option18.Boolean("--strict", false, {
11865
+ strict = Option17.Boolean("--strict", false, {
11956
11866
  description: "Promote frontmatter-validation findings from warn to error (exit code 1 on any violation). Overrides scan.strict from config when both are set."
11957
11867
  });
11958
- watch = Option18.Boolean("--watch", false, {
11868
+ watch = Option17.Boolean("--watch", false, {
11959
11869
  description: "Long-running mode: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`."
11960
11870
  });
11961
11871
  async run() {
@@ -12053,11 +11963,11 @@ var ScanCommand = class extends SmCommand {
12053
11963
  };
12054
11964
 
12055
11965
  // cli/commands/scan-compare.ts
12056
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
12057
- import { Command as Command19, Option as Option19 } from "clipanion";
11966
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
11967
+ import { Command as Command18, Option as Option18 } from "clipanion";
12058
11968
  var ScanCompareCommand = class extends SmCommand {
12059
11969
  static paths = [["scan", "compare-with"]];
12060
- static usage = Command19.Usage({
11970
+ static usage = Command18.Usage({
12061
11971
  category: "Scan",
12062
11972
  description: "Run a fresh scan in memory and emit a delta against the saved ScanResult dump at <dump>. Read-only.",
12063
11973
  details: `
@@ -12085,15 +11995,15 @@ var ScanCompareCommand = class extends SmCommand {
12085
11995
  ["JSON output for tooling", "$0 scan compare-with baseline.json --json"]
12086
11996
  ]
12087
11997
  });
12088
- dump = Option19.String({ required: true });
12089
- roots = Option19.Rest({ name: "roots" });
12090
- noTokens = Option19.Boolean("--no-tokens", false, {
11998
+ dump = Option18.String({ required: true });
11999
+ roots = Option18.Rest({ name: "roots" });
12000
+ noTokens = Option18.Boolean("--no-tokens", false, {
12091
12001
  description: "Skip per-node token counts during the fresh scan."
12092
12002
  });
12093
- strict = Option19.Boolean("--strict", false, {
12003
+ strict = Option18.Boolean("--strict", false, {
12094
12004
  description: "Promote layered-config warnings and frontmatter-validation findings from warn to error."
12095
12005
  });
12096
- noPlugins = Option19.Boolean("--no-plugins", false, {
12006
+ noPlugins = Option18.Boolean("--no-plugins", false, {
12097
12007
  description: "Skip drop-in plugin discovery."
12098
12008
  });
12099
12009
  // Cyclomatic count comes from CLI ergonomics: 3 distinct try/catch
@@ -12164,12 +12074,12 @@ var ScanCompareCommand = class extends SmCommand {
12164
12074
  }
12165
12075
  };
12166
12076
  function loadAndValidateDump(path) {
12167
- if (!existsSync15(path)) {
12077
+ if (!existsSync14(path)) {
12168
12078
  throw new Error(tx(SCAN_TEXTS.compareDumpNotFound, { path }));
12169
12079
  }
12170
12080
  let raw;
12171
12081
  try {
12172
- raw = readFileSync12(path, "utf8");
12082
+ raw = readFileSync11(path, "utf8");
12173
12083
  } catch (err) {
12174
12084
  const message = formatErrorMessage(err);
12175
12085
  throw new Error(tx(SCAN_TEXTS.compareDumpReadFailed, { path, message }), { cause: err });
@@ -12278,8 +12188,8 @@ function renderDeltaIssues(issues) {
12278
12188
 
12279
12189
  // cli/commands/serve.ts
12280
12190
  import { spawn } from "child_process";
12281
- import { existsSync as existsSync19 } from "fs";
12282
- import { Command as Command20, Option as Option20 } from "clipanion";
12191
+ import { existsSync as existsSync18 } from "fs";
12192
+ import { Command as Command19, Option as Option19 } from "clipanion";
12283
12193
 
12284
12194
  // server/index.ts
12285
12195
  import { serve } from "@hono/node-server";
@@ -12460,7 +12370,7 @@ function contentTypeFor(format) {
12460
12370
  }
12461
12371
 
12462
12372
  // server/health.ts
12463
- import { existsSync as existsSync16 } from "fs";
12373
+ import { existsSync as existsSync15 } from "fs";
12464
12374
  var FALLBACK_SCHEMA_VERSION = "1";
12465
12375
  function buildHealth(deps) {
12466
12376
  return {
@@ -12469,7 +12379,7 @@ function buildHealth(deps) {
12469
12379
  specVersion: deps.specVersion,
12470
12380
  implVersion: VERSION,
12471
12381
  scope: deps.scope,
12472
- db: existsSync16(deps.dbPath) ? "present" : "missing"
12382
+ db: existsSync15(deps.dbPath) ? "present" : "missing"
12473
12383
  };
12474
12384
  }
12475
12385
  async function resolveSpecVersion2() {
@@ -12909,9 +12819,9 @@ function emptyScanResult() {
12909
12819
  }
12910
12820
 
12911
12821
  // server/static.ts
12912
- import { existsSync as existsSync17 } from "fs";
12822
+ import { existsSync as existsSync16 } from "fs";
12913
12823
  import { readFile as readFile5 } from "fs/promises";
12914
- import { extname, join as join14 } from "path";
12824
+ import { extname, join as join13 } from "path";
12915
12825
  import { serveStatic } from "@hono/node-server/serve-static";
12916
12826
  var INDEX_HTML = "index.html";
12917
12827
  var PLACEHOLDER_HTML = `<!doctype html>
@@ -12942,8 +12852,8 @@ function createSpaFallback(uiDist) {
12942
12852
  return async (c, _next) => {
12943
12853
  if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
12944
12854
  if (uiDist === null) return htmlResponse(c, PLACEHOLDER_HTML);
12945
- const indexPath = join14(uiDist, INDEX_HTML);
12946
- if (!existsSync17(indexPath)) return htmlResponse(c, PLACEHOLDER_HTML);
12855
+ const indexPath = join13(uiDist, INDEX_HTML);
12856
+ if (!existsSync16(indexPath)) return htmlResponse(c, PLACEHOLDER_HTML);
12947
12857
  return fileResponse(c, indexPath);
12948
12858
  };
12949
12859
  }
@@ -13347,6 +13257,7 @@ function createWatcherService(opts) {
13347
13257
  if ("ready" in chokidarHandle && chokidarHandle.ready instanceof Promise) {
13348
13258
  await chokidarHandle.ready;
13349
13259
  }
13260
+ await runInitialBatch({ isStopped: () => stopped, runOneBatch });
13350
13261
  opts.broadcaster.broadcast(
13351
13262
  buildWatcherStartedEvent({ roots: [WATCH_ROOT], debounceMs })
13352
13263
  );
@@ -13429,6 +13340,19 @@ async function persistOutcome(dbPath, ran) {
13429
13340
  (writer) => writer.scans.persist(result, { renameOps, extractorRuns, enrichments })
13430
13341
  );
13431
13342
  }
13343
+ async function runInitialBatch(deps) {
13344
+ if (deps.isStopped()) return;
13345
+ try {
13346
+ await deps.runOneBatch();
13347
+ } catch (err) {
13348
+ const message = formatErrorMessage(err);
13349
+ log.warn(
13350
+ tx(SERVER_TEXTS.watcherBatchFailed, {
13351
+ message: sanitizeForTerminal(message)
13352
+ })
13353
+ );
13354
+ }
13355
+ }
13432
13356
 
13433
13357
  // server/options.ts
13434
13358
  var DEFAULT_PORT = 4242;
@@ -13533,10 +13457,10 @@ function validateWatcherDebounce(value) {
13533
13457
  }
13534
13458
 
13535
13459
  // server/paths.ts
13536
- import { existsSync as existsSync18, statSync as statSync5 } from "fs";
13537
- import { dirname as dirname10, isAbsolute as isAbsolute5, join as join15, resolve as resolve20 } from "path";
13538
- import { fileURLToPath as fileURLToPath6 } from "url";
13539
- var DEFAULT_UI_REL = join15("ui", "dist", "ui", "browser");
13460
+ 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";
13462
+ import { fileURLToPath as fileURLToPath5 } from "url";
13463
+ var DEFAULT_UI_REL = join14("ui", "dist", "ui", "browser");
13540
13464
  var PACKAGE_UI_REL = "ui";
13541
13465
  var INDEX_HTML2 = "index.html";
13542
13466
  function resolveDefaultUiDist(ctx) {
@@ -13545,13 +13469,13 @@ function resolveDefaultUiDist(ctx) {
13545
13469
  return walkUpForUi(ctx.cwd);
13546
13470
  }
13547
13471
  function resolveExplicitUiDist(ctx, raw) {
13548
- return isAbsolute5(raw) ? raw : resolve20(ctx.cwd, raw);
13472
+ return isAbsolute5(raw) ? raw : resolve19(ctx.cwd, raw);
13549
13473
  }
13550
13474
  function isUiBundleDir(path) {
13551
- if (!existsSync18(path)) return false;
13475
+ if (!existsSync17(path)) return false;
13552
13476
  try {
13553
13477
  if (!statSync5(path).isDirectory()) return false;
13554
- return existsSync18(join15(path, INDEX_HTML2));
13478
+ return existsSync17(join14(path, INDEX_HTML2));
13555
13479
  } catch {
13556
13480
  return false;
13557
13481
  }
@@ -13559,7 +13483,7 @@ function isUiBundleDir(path) {
13559
13483
  function resolvePackageBundledUi() {
13560
13484
  let here;
13561
13485
  try {
13562
- here = dirname10(fileURLToPath6(import.meta.url));
13486
+ here = dirname9(fileURLToPath5(import.meta.url));
13563
13487
  } catch {
13564
13488
  return null;
13565
13489
  }
@@ -13568,22 +13492,22 @@ function resolvePackageBundledUi() {
13568
13492
  function resolvePackageBundledUiFrom(here) {
13569
13493
  let current = here;
13570
13494
  for (let i = 0; i < 8; i++) {
13571
- const candidate = join15(current, PACKAGE_UI_REL);
13495
+ const candidate = join14(current, PACKAGE_UI_REL);
13572
13496
  if (isUiBundleDir(candidate)) return candidate;
13573
- const distHere = join15(current, "dist", PACKAGE_UI_REL);
13497
+ const distHere = join14(current, "dist", PACKAGE_UI_REL);
13574
13498
  if (isUiBundleDir(distHere)) return distHere;
13575
- const parent = dirname10(current);
13499
+ const parent = dirname9(current);
13576
13500
  if (parent === current) return null;
13577
13501
  current = parent;
13578
13502
  }
13579
13503
  return null;
13580
13504
  }
13581
13505
  function walkUpForUi(startDir) {
13582
- let current = resolve20(startDir);
13506
+ let current = resolve19(startDir);
13583
13507
  for (let i = 0; i < 64; i++) {
13584
- const candidate = join15(current, DEFAULT_UI_REL);
13508
+ const candidate = join14(current, DEFAULT_UI_REL);
13585
13509
  if (isUiBundleDir(candidate)) return candidate;
13586
- const parent = dirname10(current);
13510
+ const parent = dirname9(current);
13587
13511
  if (parent === current) return null;
13588
13512
  current = parent;
13589
13513
  }
@@ -13706,17 +13630,13 @@ function normalizeAddress(addr, fallbackHost, fallbackPort) {
13706
13630
 
13707
13631
  // cli/i18n/serve.texts.ts
13708
13632
  var SERVE_TEXTS = {
13709
- // Banner emitted to stderr after the listener binds. Mirrors `sm watch`
13710
- // by writing operational status to stderr (stdout is reserved for
13711
- // future `--json` boot payloads).
13712
- boot: "sm serve: listening on http://{{host}}:{{port}} (scope={{scope}}, db={{db}})\n",
13713
- // Hint shown after the boot line. Branches on --open: when auto-open
13714
- // is on (default), the message states intent ("opening …"); when
13715
- // --no-open, it instructs the user to visit the URL manually.
13716
- // Both end with the Ctrl+C reminder so the operational tail is
13717
- // identical regardless of branch.
13718
- bootOpening: "sm serve: opening http://{{host}}:{{port}}/ in your browser. Press Ctrl+C to stop.\n",
13719
- bootVisitHint: "sm serve: visit http://{{host}}:{{port}}/ in your browser. Press Ctrl+C to stop.\n",
13633
+ // The boot banner (TTY box / flat-line fallback) is rendered by
13634
+ // `cli/util/serve-banner.ts` rather than templated through `tx`
13635
+ // ANSI escapes + box-drawing aren't a good fit for the flat
13636
+ // `{{name}}` interpolation surface. The flat-mode strings live in
13637
+ // that helper and stay byte-equivalent to the pre-banner format so
13638
+ // existing pipes / redirects ('listening on <url>' scrapers) don't
13639
+ // break.
13720
13640
  // Browser-open failure. Non-fatal — the URL is already printed; the
13721
13641
  // user can open it manually.
13722
13642
  openFailed: "sm serve: could not auto-open browser ({{message}}). Visit {{url}} manually.\n",
@@ -13740,10 +13660,140 @@ var SERVE_TEXTS = {
13740
13660
  shutdown: "sm serve: shutdown complete.\n"
13741
13661
  };
13742
13662
 
13663
+ // cli/util/serve-banner.ts
13664
+ import { homedir as homedir2 } from "os";
13665
+ import { relative as relative5, isAbsolute as isAbsolute6 } from "path";
13666
+ var ESC = {
13667
+ reset: "\x1B[0m",
13668
+ bold: "\x1B[1m",
13669
+ dim: "\x1B[2m",
13670
+ underline: "\x1B[4m",
13671
+ /** 256-color violet (xterm 141). */
13672
+ violet: "\x1B[38;5;141m",
13673
+ /** 256-color green (xterm 42). */
13674
+ green: "\x1B[38;5;42m"
13675
+ };
13676
+ var LOGO_LINES = [
13677
+ " ____ _ _ _ _ __ __ ",
13678
+ " / ___|| | _(_) | | | \\/ | __ _ _ __ ",
13679
+ " \\___ \\| |/ / | | | | |\\/| |/ _` | '_ \\ ",
13680
+ " ___) | <| | | | | | | | (_| | |_) |",
13681
+ " |____/|_|\\_\\_|_|_| |_| |_|\\__,_| .__/ ",
13682
+ " |_| "
13683
+ ];
13684
+ var LOGO_WIDTH = 40;
13685
+ function renderBanner(input) {
13686
+ const url = `http://${input.host}:${input.port}`;
13687
+ const dbDisplay = formatDbPath(input.dbPath, input.cwd);
13688
+ const browserLine = input.openBrowser ? "Opening browser\u2026 Press Ctrl+C to stop." : `Visit ${url}/ in your browser. Press Ctrl+C to stop.`;
13689
+ if (!input.isTTY) {
13690
+ return renderFlat({
13691
+ host: input.host,
13692
+ port: input.port,
13693
+ scope: input.scope,
13694
+ dbPath: input.dbPath,
13695
+ openBrowser: input.openBrowser
13696
+ });
13697
+ }
13698
+ return renderFiglet({
13699
+ version: input.version,
13700
+ url,
13701
+ scope: input.scope,
13702
+ dbDisplay,
13703
+ pathDisplay: formatCwdPath(input.cwd),
13704
+ browserLine,
13705
+ colorEnabled: input.colorEnabled
13706
+ });
13707
+ }
13708
+ function resolveColorEnabled(opts) {
13709
+ if (opts.noColorFlag) return false;
13710
+ const noColor = opts.env["NO_COLOR"];
13711
+ if (noColor !== void 0 && noColor !== "") return false;
13712
+ const forceColor = opts.env["FORCE_COLOR"];
13713
+ if (forceColor !== void 0 && forceColor !== "") return true;
13714
+ return opts.isTTY;
13715
+ }
13716
+ function renderFlat(input) {
13717
+ const safeHost = sanitizeForTerminal(input.host);
13718
+ const safeDb = sanitizeForTerminal(input.dbPath);
13719
+ const url = `http://${safeHost}:${input.port}`;
13720
+ const linesOut = [];
13721
+ linesOut.push(`sm serve: listening on ${url} (scope=${input.scope}, db=${safeDb})`);
13722
+ if (input.openBrowser) {
13723
+ linesOut.push(`sm serve: opening ${url}/ in your browser. Press Ctrl+C to stop.`);
13724
+ } else {
13725
+ linesOut.push(`sm serve: visit ${url}/ in your browser. Press Ctrl+C to stop.`);
13726
+ }
13727
+ return linesOut.join("\n") + "\n";
13728
+ }
13729
+ function renderFiglet(input) {
13730
+ const {
13731
+ dimOpen,
13732
+ dimClose,
13733
+ greenUnderline,
13734
+ greenUnderlineClose,
13735
+ violetOpen,
13736
+ violetClose
13737
+ } = resolveAnsi(input.colorEnabled);
13738
+ const logoLines = LOGO_LINES.map((line) => `${violetOpen}${line}${violetClose}`);
13739
+ const versionText = `v${input.version}`;
13740
+ const versionPad = Math.max(0, LOGO_WIDTH - versionText.length);
13741
+ const versionLine = `${" ".repeat(versionPad)}${dimOpen}${versionText}${dimClose}`;
13742
+ const lines = [];
13743
+ lines.push(...logoLines);
13744
+ lines.push("");
13745
+ lines.push(versionLine);
13746
+ lines.push("");
13747
+ lines.push(` ${dimOpen}Server${dimClose} ${greenUnderline}${input.url}${greenUnderlineClose}`);
13748
+ lines.push(` ${dimOpen}Scope${dimClose} ${input.scope}`);
13749
+ lines.push(` ${dimOpen}Path${dimClose} ${input.pathDisplay}`);
13750
+ lines.push(` ${dimOpen}DB${dimClose} ${input.dbDisplay}`);
13751
+ lines.push("");
13752
+ lines.push(` ${dimOpen}${input.browserLine}${dimClose}`);
13753
+ lines.push("");
13754
+ return lines.join("\n") + "\n";
13755
+ }
13756
+ var EMPTY_ANSI = {
13757
+ dimOpen: "",
13758
+ dimClose: "",
13759
+ greenUnderline: "",
13760
+ greenUnderlineClose: "",
13761
+ violetOpen: "",
13762
+ violetClose: ""
13763
+ };
13764
+ var ENABLED_ANSI = {
13765
+ dimOpen: ESC.dim,
13766
+ dimClose: ESC.reset,
13767
+ greenUnderline: `${ESC.green}${ESC.underline}`,
13768
+ greenUnderlineClose: ESC.reset,
13769
+ violetOpen: ESC.violet,
13770
+ violetClose: ESC.reset
13771
+ };
13772
+ function resolveAnsi(colorEnabled) {
13773
+ return colorEnabled ? ENABLED_ANSI : EMPTY_ANSI;
13774
+ }
13775
+ function formatDbPath(dbPath, cwd) {
13776
+ const safe = sanitizeForTerminal(dbPath);
13777
+ if (!isAbsolute6(safe)) return safe;
13778
+ const rel = relative5(cwd, safe);
13779
+ if (rel === "" || rel.startsWith("..") || isAbsolute6(rel)) {
13780
+ return safe;
13781
+ }
13782
+ return rel;
13783
+ }
13784
+ function formatCwdPath(cwd) {
13785
+ const safe = sanitizeForTerminal(cwd);
13786
+ const home = homedir2();
13787
+ if (home && (safe === home || safe.startsWith(`${home}/`))) {
13788
+ return `~${safe.slice(home.length)}`;
13789
+ }
13790
+ return safe;
13791
+ }
13792
+
13743
13793
  // cli/commands/serve.ts
13744
13794
  var ServeCommand = class extends SmCommand {
13745
13795
  static paths = [["serve"]];
13746
- static usage = Command20.Usage({
13796
+ static usage = Command19.Usage({
13747
13797
  category: "Setup",
13748
13798
  description: "Start the Hono BFF (single-port: REST + WebSocket + SPA bundle).",
13749
13799
  details: `
@@ -13768,22 +13818,22 @@ var ServeCommand = class extends SmCommand {
13768
13818
  ["Point at a pre-built UI bundle", "$0 serve --ui-dist ./ui/dist/browser"]
13769
13819
  ]
13770
13820
  });
13771
- port = Option20.String("--port", {
13821
+ port = Option19.String("--port", {
13772
13822
  required: false,
13773
13823
  description: "Listening port (default 4242). 0 = OS-assigned."
13774
13824
  });
13775
- host = Option20.String("--host", {
13825
+ host = Option19.String("--host", {
13776
13826
  required: false,
13777
13827
  description: "Listening host (default 127.0.0.1). Loopback-only enforced when --dev-cors is set."
13778
13828
  });
13779
- scope = Option20.String("--scope", {
13829
+ scope = Option19.String("--scope", {
13780
13830
  required: false,
13781
13831
  description: "project | global. Alias for -g/--global. Default: project."
13782
13832
  });
13783
- noBuiltIns = Option20.Boolean("--no-built-ins", false, {
13833
+ noBuiltIns = Option19.Boolean("--no-built-ins", false, {
13784
13834
  description: "Skip built-in plugin registration (parity with sm scan --no-built-ins)."
13785
13835
  });
13786
- noPlugins = Option20.Boolean("--no-plugins", false, {
13836
+ noPlugins = Option19.Boolean("--no-plugins", false, {
13787
13837
  description: "Skip drop-in plugin discovery."
13788
13838
  });
13789
13839
  // `Option.Boolean('--open', true)` — Clipanion's parser auto-derives
@@ -13793,24 +13843,24 @@ var ServeCommand = class extends SmCommand {
13793
13843
  // two registrations for the same flag and rejects the invocation
13794
13844
  // with "Ambiguous Syntax Error". Same convention shipped by every
13795
13845
  // other `--no-...` flag in the CLI tree.
13796
- open = Option20.Boolean("--open", true, {
13846
+ open = Option19.Boolean("--open", true, {
13797
13847
  description: "Auto-open the SPA in the user's default browser after listen. --no-open opts out."
13798
13848
  });
13799
- devCors = Option20.Boolean("--dev-cors", false, {
13849
+ devCors = Option19.Boolean("--dev-cors", false, {
13800
13850
  description: "Enable permissive CORS for the Angular dev-server proxy workflow."
13801
13851
  });
13802
13852
  // `--ui-dist` is intentionally undocumented in the Usage block above
13803
13853
  // (the demo build pipeline + tests rely on it; everyday users never
13804
13854
  // need it). Clipanion still exposes it on the parser; the Usage
13805
13855
  // omission is the "hidden" contract per the 14.1 brief.
13806
- uiDist = Option20.String("--ui-dist", { required: false, hidden: true });
13807
- noWatcher = Option20.Boolean("--no-watcher", false, {
13856
+ uiDist = Option19.String("--ui-dist", { required: false, hidden: true });
13857
+ noWatcher = Option19.Boolean("--no-watcher", false, {
13808
13858
  description: "Disable the chokidar-fed scan-and-broadcast loop. Use only for CI / read-only deployments."
13809
13859
  });
13810
13860
  // `--watcher-debounce-ms` is undocumented sugar for advanced users
13811
13861
  // who want to tighten / relax the watcher's batching window without
13812
13862
  // editing settings.json. Hidden flag — the Usage block omits it.
13813
- watcherDebounceMs = Option20.String("--watcher-debounce-ms", { required: false, hidden: true });
13863
+ watcherDebounceMs = Option19.String("--watcher-debounce-ms", { required: false, hidden: true });
13814
13864
  // Long-running daemon — `done in <…>` after a graceful shutdown is
13815
13865
  // noise. Mirrors `sm watch`'s opt-out.
13816
13866
  emitElapsed = false;
@@ -13838,7 +13888,7 @@ var ServeCommand = class extends SmCommand {
13838
13888
  return ExitCode.Error;
13839
13889
  }
13840
13890
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...runtimeCtx });
13841
- if (this.db !== void 0 && !existsSync19(dbPath)) {
13891
+ if (this.db !== void 0 && !existsSync18(dbPath)) {
13842
13892
  this.context.stderr.write(
13843
13893
  tx(SERVE_TEXTS.dbNotFound, { path: sanitizeForTerminal(dbPath) })
13844
13894
  );
@@ -13892,18 +13942,24 @@ var ServeCommand = class extends SmCommand {
13892
13942
  );
13893
13943
  return ExitCode.Error;
13894
13944
  }
13945
+ const stderr = this.context.stderr;
13946
+ const isTTY = stderr.isTTY === true;
13947
+ const colorEnabled = resolveColorEnabled({
13948
+ isTTY,
13949
+ noColorFlag: this.noColor,
13950
+ env: process.env
13951
+ });
13895
13952
  this.context.stderr.write(
13896
- tx(SERVE_TEXTS.boot, {
13953
+ renderBanner({
13954
+ version: VERSION,
13897
13955
  host: sanitizeForTerminal(handle.address.host),
13898
13956
  port: handle.address.port,
13899
13957
  scope,
13900
- db: sanitizeForTerminal(dbPath)
13901
- })
13902
- );
13903
- this.context.stderr.write(
13904
- tx(validation.options.open ? SERVE_TEXTS.bootOpening : SERVE_TEXTS.bootVisitHint, {
13905
- host: sanitizeForTerminal(handle.address.host),
13906
- port: handle.address.port
13958
+ dbPath,
13959
+ cwd: runtimeCtx.cwd,
13960
+ openBrowser: validation.options.open,
13961
+ isTTY,
13962
+ colorEnabled
13907
13963
  })
13908
13964
  );
13909
13965
  if (validation.options.open) {
@@ -14019,7 +14075,7 @@ function tryOpenBrowser(url, stderr) {
14019
14075
  }
14020
14076
 
14021
14077
  // cli/commands/show.ts
14022
- import { Command as Command21, Option as Option21 } from "clipanion";
14078
+ import { Command as Command20, Option as Option20 } from "clipanion";
14023
14079
 
14024
14080
  // cli/i18n/show.texts.ts
14025
14081
  var SHOW_TEXTS = {
@@ -14059,7 +14115,7 @@ var SHOW_TEXTS = {
14059
14115
  // cli/commands/show.ts
14060
14116
  var ShowCommand = class extends SmCommand {
14061
14117
  static paths = [["show"]];
14062
- static usage = Command21.Usage({
14118
+ static usage = Command20.Usage({
14063
14119
  category: "Browse",
14064
14120
  description: "Node detail: weight, frontmatter, links, issues.",
14065
14121
  details: `
@@ -14075,7 +14131,7 @@ var ShowCommand = class extends SmCommand {
14075
14131
  ["Machine-readable detail", "$0 show .claude/agents/architect.md --json"]
14076
14132
  ]
14077
14133
  });
14078
- nodePath = Option21.String({ required: true });
14134
+ nodePath = Option20.String({ required: true });
14079
14135
  async run() {
14080
14136
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
14081
14137
  if (!assertDbExists(dbPath, this.context.stderr)) return ExitCode.NotFound;
@@ -14224,7 +14280,7 @@ function rankConfidenceForGrouping(c) {
14224
14280
  }
14225
14281
 
14226
14282
  // cli/commands/stubs.ts
14227
- import { Command as Command22, Option as Option22 } from "clipanion";
14283
+ import { Command as Command21, Option as Option21 } from "clipanion";
14228
14284
 
14229
14285
  // cli/i18n/stubs.texts.ts
14230
14286
  var STUBS_TEXTS = {
@@ -14239,9 +14295,9 @@ function notImplemented(cmd, verb) {
14239
14295
  cmd.context.stderr.write(tx(STUBS_TEXTS.notImplemented, { verb }));
14240
14296
  return ExitCode.Error;
14241
14297
  }
14242
- var DoctorCommand = class extends Command22 {
14298
+ var DoctorCommand = class extends Command21 {
14243
14299
  static paths = [["doctor"]];
14244
- static usage = Command22.Usage({
14300
+ static usage = Command21.Usage({
14245
14301
  category: "Setup",
14246
14302
  description: planned("Diagnostic report: DB integrity, pending migrations, orphan rows, plugin status, runner availability.")
14247
14303
  });
@@ -14249,23 +14305,23 @@ var DoctorCommand = class extends Command22 {
14249
14305
  return notImplemented(this, "doctor");
14250
14306
  }
14251
14307
  };
14252
- var FindingsCommand = class extends Command22 {
14308
+ var FindingsCommand = class extends Command21 {
14253
14309
  static paths = [["findings"]];
14254
- static usage = Command22.Usage({
14310
+ static usage = Command21.Usage({
14255
14311
  category: "Browse",
14256
14312
  description: planned("Probabilistic findings: injection, stale summaries, low confidence.")
14257
14313
  });
14258
- kind = Option22.String("--kind", { required: false });
14259
- since = Option22.String("--since", { required: false });
14260
- threshold = Option22.String("--threshold", { required: false });
14261
- json = Option22.Boolean("--json", false);
14314
+ kind = Option21.String("--kind", { required: false });
14315
+ since = Option21.String("--since", { required: false });
14316
+ threshold = Option21.String("--threshold", { required: false });
14317
+ json = Option21.Boolean("--json", false);
14262
14318
  async execute() {
14263
14319
  return notImplemented(this, "findings");
14264
14320
  }
14265
14321
  };
14266
- var ActionsListCommand = class extends Command22 {
14322
+ var ActionsListCommand = class extends Command21 {
14267
14323
  static paths = [["actions", "list"]];
14268
- static usage = Command22.Usage({
14324
+ static usage = Command21.Usage({
14269
14325
  category: "Jobs",
14270
14326
  description: planned("Registered action types (manifest view).")
14271
14327
  });
@@ -14273,121 +14329,121 @@ var ActionsListCommand = class extends Command22 {
14273
14329
  return notImplemented(this, "actions list");
14274
14330
  }
14275
14331
  };
14276
- var ActionsShowCommand = class extends Command22 {
14332
+ var ActionsShowCommand = class extends Command21 {
14277
14333
  static paths = [["actions", "show"]];
14278
- static usage = Command22.Usage({
14334
+ static usage = Command21.Usage({
14279
14335
  category: "Jobs",
14280
14336
  description: planned("Full action manifest, including preconditions and expected duration.")
14281
14337
  });
14282
- id = Option22.String({ required: true });
14338
+ id = Option21.String({ required: true });
14283
14339
  async execute() {
14284
14340
  return notImplemented(this, "actions show");
14285
14341
  }
14286
14342
  };
14287
- var JobSubmitCommand = class extends Command22 {
14343
+ var JobSubmitCommand = class extends Command21 {
14288
14344
  static paths = [["job", "submit"]];
14289
- static usage = Command22.Usage({
14345
+ static usage = Command21.Usage({
14290
14346
  category: "Jobs",
14291
14347
  description: planned("Enqueue a single job or fan out to every matching node (--all).")
14292
14348
  });
14293
- action = Option22.String({ required: true });
14294
- node = Option22.String("-n", { required: false });
14295
- all = Option22.Boolean("--all", false);
14296
- run = Option22.Boolean("--run", false);
14297
- force = Option22.Boolean("--force", false);
14298
- ttl = Option22.String("--ttl", { required: false });
14299
- priority = Option22.String("--priority", { required: false });
14349
+ action = Option21.String({ required: true });
14350
+ node = Option21.String("-n", { required: false });
14351
+ all = Option21.Boolean("--all", false);
14352
+ run = Option21.Boolean("--run", false);
14353
+ force = Option21.Boolean("--force", false);
14354
+ ttl = Option21.String("--ttl", { required: false });
14355
+ priority = Option21.String("--priority", { required: false });
14300
14356
  async execute() {
14301
14357
  return notImplemented(this, "job submit");
14302
14358
  }
14303
14359
  };
14304
- var JobListCommand = class extends Command22 {
14360
+ var JobListCommand = class extends Command21 {
14305
14361
  static paths = [["job", "list"]];
14306
- static usage = Command22.Usage({ category: "Jobs", description: planned("List jobs.") });
14307
- status = Option22.String("--status", { required: false });
14308
- action = Option22.String("--action", { required: false });
14309
- node = Option22.String("--node", { required: false });
14362
+ static usage = Command21.Usage({ category: "Jobs", description: planned("List jobs.") });
14363
+ status = Option21.String("--status", { required: false });
14364
+ action = Option21.String("--action", { required: false });
14365
+ node = Option21.String("--node", { required: false });
14310
14366
  async execute() {
14311
14367
  return notImplemented(this, "job list");
14312
14368
  }
14313
14369
  };
14314
- var JobShowCommand = class extends Command22 {
14370
+ var JobShowCommand = class extends Command21 {
14315
14371
  static paths = [["job", "show"]];
14316
- static usage = Command22.Usage({ category: "Jobs", description: planned("Job detail: state, claim time, TTL, runner, content hash.") });
14317
- id = Option22.String({ required: true });
14372
+ static usage = Command21.Usage({ category: "Jobs", description: planned("Job detail: state, claim time, TTL, runner, content hash.") });
14373
+ id = Option21.String({ required: true });
14318
14374
  async execute() {
14319
14375
  return notImplemented(this, "job show");
14320
14376
  }
14321
14377
  };
14322
- var JobPreviewCommand = class extends Command22 {
14378
+ var JobPreviewCommand = class extends Command21 {
14323
14379
  static paths = [["job", "preview"]];
14324
- static usage = Command22.Usage({ category: "Jobs", description: planned("Render the job MD file without executing.") });
14325
- id = Option22.String({ required: true });
14380
+ static usage = Command21.Usage({ category: "Jobs", description: planned("Render the job MD file without executing.") });
14381
+ id = Option21.String({ required: true });
14326
14382
  async execute() {
14327
14383
  return notImplemented(this, "job preview");
14328
14384
  }
14329
14385
  };
14330
- var JobClaimCommand = class extends Command22 {
14386
+ var JobClaimCommand = class extends Command21 {
14331
14387
  static paths = [["job", "claim"]];
14332
- static usage = Command22.Usage({
14388
+ static usage = Command21.Usage({
14333
14389
  category: "Jobs",
14334
14390
  description: planned("Atomic primitive: return next queued job id, mark it running.")
14335
14391
  });
14336
- filter = Option22.String("--filter", { required: false });
14392
+ filter = Option21.String("--filter", { required: false });
14337
14393
  async execute() {
14338
14394
  return notImplemented(this, "job claim");
14339
14395
  }
14340
14396
  };
14341
- var JobRunCommand = class extends Command22 {
14397
+ var JobRunCommand = class extends Command21 {
14342
14398
  static paths = [["job", "run"]];
14343
- static usage = Command22.Usage({
14399
+ static usage = Command21.Usage({
14344
14400
  category: "Jobs",
14345
14401
  description: planned("Full CLI-runner loop: claim + spawn + record.")
14346
14402
  });
14347
- all = Option22.Boolean("--all", false);
14348
- max = Option22.String("--max", { required: false });
14403
+ all = Option21.Boolean("--all", false);
14404
+ max = Option21.String("--max", { required: false });
14349
14405
  async execute() {
14350
14406
  return notImplemented(this, "job run");
14351
14407
  }
14352
14408
  };
14353
- var JobStatusCommand = class extends Command22 {
14409
+ var JobStatusCommand = class extends Command21 {
14354
14410
  static paths = [["job", "status"]];
14355
- static usage = Command22.Usage({
14411
+ static usage = Command21.Usage({
14356
14412
  category: "Jobs",
14357
14413
  description: planned("Counts (per status) or single-job status.")
14358
14414
  });
14359
- id = Option22.String({ required: false });
14415
+ id = Option21.String({ required: false });
14360
14416
  async execute() {
14361
14417
  return notImplemented(this, "job status");
14362
14418
  }
14363
14419
  };
14364
- var JobCancelCommand = class extends Command22 {
14420
+ var JobCancelCommand = class extends Command21 {
14365
14421
  static paths = [["job", "cancel"]];
14366
- static usage = Command22.Usage({
14422
+ static usage = Command21.Usage({
14367
14423
  category: "Jobs",
14368
14424
  description: planned("Force a running job to failed with reason user-cancelled.")
14369
14425
  });
14370
- id = Option22.String({ required: false });
14371
- all = Option22.Boolean("--all", false);
14426
+ id = Option21.String({ required: false });
14427
+ all = Option21.Boolean("--all", false);
14372
14428
  async execute() {
14373
14429
  return notImplemented(this, "job cancel");
14374
14430
  }
14375
14431
  };
14376
- var RecordCommand = class extends Command22 {
14432
+ var RecordCommand = class extends Command21 {
14377
14433
  static paths = [["record"]];
14378
- static usage = Command22.Usage({
14434
+ static usage = Command21.Usage({
14379
14435
  category: "Jobs",
14380
14436
  description: planned("Close a running job with success or failure. Nonce is the sole credential.")
14381
14437
  });
14382
- id = Option22.String("--id", { required: true });
14383
- nonce = Option22.String("--nonce", { required: true });
14384
- status = Option22.String("--status", { required: true });
14385
- report = Option22.String("--report", { required: false });
14386
- tokensIn = Option22.String("--tokens-in", { required: false });
14387
- tokensOut = Option22.String("--tokens-out", { required: false });
14388
- durationMs = Option22.String("--duration-ms", { required: false });
14389
- model = Option22.String("--model", { required: false });
14390
- error = Option22.String("--error", { required: false });
14438
+ id = Option21.String("--id", { required: true });
14439
+ nonce = Option21.String("--nonce", { required: true });
14440
+ status = Option21.String("--status", { required: true });
14441
+ report = Option21.String("--report", { required: false });
14442
+ tokensIn = Option21.String("--tokens-in", { required: false });
14443
+ tokensOut = Option21.String("--tokens-out", { required: false });
14444
+ durationMs = Option21.String("--duration-ms", { required: false });
14445
+ model = Option21.String("--model", { required: false });
14446
+ error = Option21.String("--error", { required: false });
14391
14447
  async execute() {
14392
14448
  return notImplemented(this, "record");
14393
14449
  }
@@ -14408,6 +14464,99 @@ var STUB_COMMANDS = [
14408
14464
  RecordCommand
14409
14465
  ];
14410
14466
 
14467
+ // cli/commands/tutorial.ts
14468
+ import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
14469
+ import { writeFile as writeFile2 } from "fs/promises";
14470
+ import { dirname as dirname10, join as join15, resolve as resolve20 } from "path";
14471
+ import { fileURLToPath as fileURLToPath6 } from "url";
14472
+ import { Command as Command22, Option as Option22 } from "clipanion";
14473
+
14474
+ // cli/i18n/tutorial.texts.ts
14475
+ var TUTORIAL_TEXTS = {
14476
+ // Success — written to stdout after `<cwd>/sm-tutorial.md` is created.
14477
+ 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',
14478
+ // Refusal — `sm-tutorial.md` already exists and `--force` was not set.
14479
+ // Goes to stderr, exit code 2 (operational error per spec § Exit codes).
14480
+ alreadyExists: "sm tutorial: sm-tutorial.md already exists at {{cwd}}. Pass `--force` to overwrite.\n",
14481
+ // I/O failure on write or on reading the bundled SKILL source.
14482
+ writeFailed: "sm tutorial: failed to write sm-tutorial.md: {{message}}\n",
14483
+ sourceMissing: "sm tutorial: could not read the bundled tutorial (SKILL.md) from the install. Reinstall @skill-map/cli or report the bug.\n"
14484
+ };
14485
+
14486
+ // cli/commands/tutorial.ts
14487
+ var SM_TUTORIAL_FILENAME = "sm-tutorial.md";
14488
+ var TutorialCommand = class extends SmCommand {
14489
+ static paths = [["tutorial"]];
14490
+ static usage = Command22.Usage({
14491
+ category: "Setup",
14492
+ description: "Materialize the interactive tester tutorial (sm-tutorial.md) in the current directory.",
14493
+ details: `
14494
+ Drops the canonical SKILL.md content as ./sm-tutorial.md so a tester
14495
+ can open Claude Code in the cwd and load the file as a skill by
14496
+ typing "ejecut\xE1 @sm-tutorial.md". Top-level only \u2014 no subdirectory
14497
+ is created.
14498
+
14499
+ Does NOT require an initialized .skill-map/ project. Refuses to
14500
+ overwrite an existing sm-tutorial.md unless --force is passed.
14501
+ `,
14502
+ examples: [
14503
+ ["Materialize the tutorial in the cwd", "$0 tutorial"],
14504
+ ["Overwrite an existing sm-tutorial.md", "$0 tutorial --force"]
14505
+ ]
14506
+ });
14507
+ force = Option22.Boolean("--force", false, {
14508
+ description: "Overwrite an existing sm-tutorial.md without prompting."
14509
+ });
14510
+ async run() {
14511
+ const ctx = defaultRuntimeContext();
14512
+ const target = join15(ctx.cwd, SM_TUTORIAL_FILENAME);
14513
+ if (await pathExists(target) && !this.force) {
14514
+ this.context.stderr.write(tx(TUTORIAL_TEXTS.alreadyExists, { cwd: ctx.cwd }));
14515
+ return ExitCode.Error;
14516
+ }
14517
+ let body;
14518
+ try {
14519
+ body = loadBundledTutorialText();
14520
+ } catch {
14521
+ this.context.stderr.write(TUTORIAL_TEXTS.sourceMissing);
14522
+ return ExitCode.Error;
14523
+ }
14524
+ try {
14525
+ await writeFile2(target, body);
14526
+ } catch (err) {
14527
+ this.context.stderr.write(
14528
+ tx(TUTORIAL_TEXTS.writeFailed, { message: formatErrorMessage(err) })
14529
+ );
14530
+ return ExitCode.Error;
14531
+ }
14532
+ this.context.stdout.write(tx(TUTORIAL_TEXTS.written, { cwd: ctx.cwd }));
14533
+ return ExitCode.Ok;
14534
+ }
14535
+ };
14536
+ var cachedTutorial = null;
14537
+ function loadBundledTutorialText() {
14538
+ if (cachedTutorial !== null) return cachedTutorial;
14539
+ cachedTutorial = readTutorialFromDisk();
14540
+ return cachedTutorial;
14541
+ }
14542
+ function readTutorialFromDisk() {
14543
+ const here = dirname10(fileURLToPath6(import.meta.url));
14544
+ const candidates = [
14545
+ // dev: src/cli/commands/ → repo-root .claude/skills/sm-tutorial/SKILL.md
14546
+ resolve20(here, "../../../.claude/skills/sm-tutorial/SKILL.md"),
14547
+ // bundled: dist/cli.js → dist/cli/tutorial/sm-tutorial.md (sibling)
14548
+ resolve20(here, "cli/tutorial/sm-tutorial.md"),
14549
+ // bundled fallback: any-depth → cli/tutorial/sm-tutorial.md
14550
+ resolve20(here, "../cli/tutorial/sm-tutorial.md")
14551
+ ];
14552
+ for (const candidate of candidates) {
14553
+ if (existsSync19(candidate)) {
14554
+ return readFileSync12(candidate, "utf8");
14555
+ }
14556
+ }
14557
+ throw new Error(`SKILL.md not found in any candidate location (last tried: ${candidates[candidates.length - 1]})`);
14558
+ }
14559
+
14411
14560
  // cli/commands/version.ts
14412
14561
  import { Command as Command23 } from "clipanion";
14413
14562
 
@@ -14491,7 +14640,7 @@ cli.register(Builtins.VersionCommand);
14491
14640
  cli.register(RootHelpCommand);
14492
14641
  cli.register(HelpCommand);
14493
14642
  cli.register(InitCommand);
14494
- cli.register(GuideCommand);
14643
+ cli.register(TutorialCommand);
14495
14644
  cli.register(ScanCommand);
14496
14645
  cli.register(ScanCompareCommand);
14497
14646
  cli.register(ServeCommand);