@skill-map/cli 0.23.1 → 0.24.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
@@ -2512,7 +2512,7 @@ function readSidecarFor(mdAbsolutePath) {
2512
2512
  }
2513
2513
  let parsedYaml;
2514
2514
  try {
2515
- parsedYaml = yaml.load(raw);
2515
+ parsedYaml = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
2516
2516
  } catch (err) {
2517
2517
  return {
2518
2518
  parsed: null,
@@ -5747,7 +5747,7 @@ var UPDATE_CHECK_TEXTS = {
5747
5747
  // package.json
5748
5748
  var package_default = {
5749
5749
  name: "@skill-map/cli",
5750
- version: "0.23.1",
5750
+ version: "0.24.1",
5751
5751
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
5752
5752
  license: "MIT",
5753
5753
  type: "module",
@@ -5814,7 +5814,7 @@ var package_default = {
5814
5814
  },
5815
5815
  dependencies: {
5816
5816
  "@hono/node-server": "2.0.1",
5817
- "@skill-map/spec": "0.23.0",
5817
+ "@skill-map/spec": "0.24.0",
5818
5818
  ajv: "8.18.0",
5819
5819
  "ajv-formats": "3.0.1",
5820
5820
  chokidar: "5.0.0",
@@ -6317,7 +6317,9 @@ var ExitCode = {
6317
6317
  Ok: 0,
6318
6318
  Issues: 1,
6319
6319
  Error: 2,
6320
+ // TODO Step 10: emitted by RecordCommand once the job callback ships.
6320
6321
  Duplicate: 3,
6322
+ // TODO Step 10: emitted by RecordCommand once the job callback ships.
6321
6323
  NonceMismatch: 4,
6322
6324
  NotFound: 5
6323
6325
  };
@@ -6636,7 +6638,7 @@ function isPlainObject4(value) {
6636
6638
  function readSidecarObject(sidecarAbsPath) {
6637
6639
  if (!existsSync9(sidecarAbsPath)) return {};
6638
6640
  const raw = readFileSync8(sidecarAbsPath, "utf8");
6639
- const parsed = yaml2.load(raw);
6641
+ const parsed = yaml2.load(raw, { schema: yaml2.JSON_SCHEMA });
6640
6642
  if (parsed === null || parsed === void 0) return {};
6641
6643
  if (!isPlainObject4(parsed)) {
6642
6644
  throw new Error(
@@ -6910,6 +6912,22 @@ var SmCommand = class extends Command {
6910
6912
  const level = this.verbose >= 3 ? "trace" : this.verbose === 2 ? "debug" : "info";
6911
6913
  configureLogger(new Logger({ level, stream: process.stderr }));
6912
6914
  }
6915
+ /**
6916
+ * Resolve the ANSI helper for either output stream. Reads `--no-color`
6917
+ * from this command and the target stream's `isTTY` from the
6918
+ * Clipanion-injected context. Centralises what was previously a
6919
+ * 67-occurrence boilerplate sprinkled across every verb.
6920
+ *
6921
+ * Pass `'stdout'` for the result stream, `'stderr'` for banners /
6922
+ * advisories / errors. The two streams may have different TTY status
6923
+ * (e.g. piping stdout but keeping stderr interactive), so the channel
6924
+ * matters.
6925
+ */
6926
+ ansiFor(stream) {
6927
+ const target = stream === "stdout" ? this.context.stdout : this.context.stderr;
6928
+ const isTTY = target.isTTY === true;
6929
+ return ansiFor({ isTTY, noColorFlag: this.noColor });
6930
+ }
6913
6931
  };
6914
6932
 
6915
6933
  // cli/commands/bump-plan.ts
@@ -7028,8 +7046,7 @@ var BumpCommand = class extends SmCommand {
7028
7046
  description: "Confirm writing .sm sidecar files in this project (sets allowEditSmFiles=true on first run)."
7029
7047
  });
7030
7048
  async run() {
7031
- const stderr = this.context.stderr;
7032
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
7049
+ const ansi = this.ansiFor("stderr");
7033
7050
  const flagError = this.#validateFlagCombo(ansi);
7034
7051
  if (flagError !== null) return flagError;
7035
7052
  const ctx = defaultRuntimeContext();
@@ -8908,8 +8925,7 @@ var CheckCommand = class extends SmCommand {
8908
8925
  if (analyzerFilter !== void 0) {
8909
8926
  issues = issues.filter((i) => matchesAnalyzerFilter(i.analyzerId, analyzerFilter));
8910
8927
  }
8911
- const stdout = this.context.stdout;
8912
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
8928
+ const ansi = this.ansiFor("stdout");
8913
8929
  if (this.json) {
8914
8930
  this.printer.data(JSON.stringify(issues) + "\n");
8915
8931
  } else if (issues.length === 0) {
@@ -9210,8 +9226,7 @@ var ConfigListCommand = class extends SmCommand {
9210
9226
  this.printer.data(JSON.stringify(effective, null, 2) + "\n");
9211
9227
  return ExitCode.Ok;
9212
9228
  }
9213
- const stdout = this.context.stdout;
9214
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
9229
+ const ansi = this.ansiFor("stdout");
9215
9230
  this.printer.data(renderConfigSections(Array.from(iterDotPaths(effective)), ansi));
9216
9231
  return ExitCode.Ok;
9217
9232
  }
@@ -9323,8 +9338,7 @@ var ConfigGetCommand = class extends SmCommand {
9323
9338
  if (!lookup.ok) return lookup.exitCode;
9324
9339
  const { value } = lookup;
9325
9340
  if (value === void 0) {
9326
- const stderr = this.context.stderr;
9327
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
9341
+ const ansi = this.ansiFor("stderr");
9328
9342
  this.printer.info(
9329
9343
  tx(CONFIG_TEXTS.unknownKey, { glyph: ansi.red("\u2715"), key: this.key })
9330
9344
  );
@@ -9369,8 +9383,7 @@ var ConfigShowCommand = class extends SmCommand {
9369
9383
  if (!result.ok) return result.exitCode;
9370
9384
  const { effective, sources, warnings } = result.loaded;
9371
9385
  for (const w of warnings) this.printer.info(w + "\n");
9372
- const stderrShow = this.context.stderr;
9373
- const ansiShow = ansiFor({ isTTY: stderrShow.isTTY === true, noColorFlag: this.noColor });
9386
+ const ansiShow = this.ansiFor("stderr");
9374
9387
  const errGlyphShow = ansiShow.red("\u2715");
9375
9388
  let value;
9376
9389
  try {
@@ -9400,8 +9413,7 @@ var ConfigShowCommand = class extends SmCommand {
9400
9413
  return ExitCode.Ok;
9401
9414
  }
9402
9415
  if (this.source) {
9403
- const stdout = this.context.stdout;
9404
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
9416
+ const ansi = this.ansiFor("stdout");
9405
9417
  this.printer.data(
9406
9418
  tx(CONFIG_TEXTS.valueWithLayer, {
9407
9419
  value: formatValueHuman(value),
@@ -9468,8 +9480,7 @@ var ConfigSetCommand = class extends SmCommand {
9468
9480
  const ctx = defaultRuntimeContext();
9469
9481
  const target = resolveWriteTarget(this.key, this.global);
9470
9482
  const path = targetSettingsPath2(target, ctx.cwd, ctx.homedir);
9471
- const stderr = this.context.stderr;
9472
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
9483
+ const stderrAnsi = this.ansiFor("stderr");
9473
9484
  const errGlyph = stderrAnsi.red("\u2715");
9474
9485
  const value = parseCliValue(this.value);
9475
9486
  if (PRIVACY_SENSITIVE_KEYS.has(this.key)) {
@@ -9546,8 +9557,7 @@ var ConfigSetCommand = class extends SmCommand {
9546
9557
  }
9547
9558
  throw err;
9548
9559
  }
9549
- const stdout = this.context.stdout;
9550
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
9560
+ const ansi = this.ansiFor("stdout");
9551
9561
  this.printer.data(
9552
9562
  tx(CONFIG_TEXTS.setWritten, {
9553
9563
  glyph: ansi.green("\u2713"),
@@ -9582,8 +9592,7 @@ var ConfigResetCommand = class extends SmCommand {
9582
9592
  const ctx = defaultRuntimeContext();
9583
9593
  const target = resolveWriteTarget(this.key, this.global);
9584
9594
  const path = targetSettingsPath2(target, ctx.cwd, ctx.homedir);
9585
- const stdout = this.context.stdout;
9586
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
9595
+ const ansi = this.ansiFor("stdout");
9587
9596
  const okGlyph = ansi.green("\u2713");
9588
9597
  if (!existsSync13(path)) {
9589
9598
  this.printer.data(
@@ -10182,8 +10191,7 @@ var ConformanceRunCommand = class extends SmCommand {
10182
10191
  // per-result render branches + global pass/fail decision.
10183
10192
  // eslint-disable-next-line complexity
10184
10193
  async run() {
10185
- const stderr = this.context.stderr;
10186
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
10194
+ const stderrAnsi = this.ansiFor("stderr");
10187
10195
  const errGlyph = stderrAnsi.red("\u2715");
10188
10196
  let scopes;
10189
10197
  try {
@@ -10223,7 +10231,7 @@ var ConformanceRunCommand = class extends SmCommand {
10223
10231
  const cases = listCaseFiles(scope);
10224
10232
  if (cases.length === 0) {
10225
10233
  if (!this.json) {
10226
- this.printer.data(
10234
+ this.printer.info(
10227
10235
  tx(CONFORMANCE_TEXTS.scopeEmpty, { label: scope.label })
10228
10236
  );
10229
10237
  }
@@ -10231,7 +10239,7 @@ var ConformanceRunCommand = class extends SmCommand {
10231
10239
  continue;
10232
10240
  }
10233
10241
  if (!this.json) {
10234
- this.printer.data(
10242
+ this.printer.info(
10235
10243
  tx(CONFORMANCE_TEXTS.scopeHeader, {
10236
10244
  label: scope.label,
10237
10245
  caseCount: cases.length
@@ -10251,7 +10259,7 @@ var ConformanceRunCommand = class extends SmCommand {
10251
10259
  });
10252
10260
  if (result.passed) {
10253
10261
  if (!this.json) {
10254
- this.printer.data(
10262
+ this.printer.info(
10255
10263
  tx(CONFORMANCE_TEXTS.caseOk, { caseId: result.caseId })
10256
10264
  );
10257
10265
  }
@@ -10261,7 +10269,7 @@ var ConformanceRunCommand = class extends SmCommand {
10261
10269
  anyFailure = true;
10262
10270
  const failures = projectAssertionFailures(result.assertions);
10263
10271
  if (!this.json) {
10264
- this.printer.data(
10272
+ this.printer.info(
10265
10273
  tx(CONFORMANCE_TEXTS.caseFail, { caseId: result.caseId })
10266
10274
  );
10267
10275
  for (const a of result.assertions) {
@@ -10290,7 +10298,7 @@ var ConformanceRunCommand = class extends SmCommand {
10290
10298
  this.printer.error(
10291
10299
  tx(CONFORMANCE_TEXTS.runtimeError, { glyph: errGlyph, message })
10292
10300
  );
10293
- this.printer.data(tx(CONFORMANCE_TEXTS.caseFail, { caseId }));
10301
+ this.printer.info(tx(CONFORMANCE_TEXTS.caseFail, { caseId }));
10294
10302
  }
10295
10303
  caseReports.push({
10296
10304
  id: caseId,
@@ -10303,7 +10311,7 @@ var ConformanceRunCommand = class extends SmCommand {
10303
10311
  }
10304
10312
  }
10305
10313
  if (!this.json) {
10306
- this.printer.data(
10314
+ this.printer.info(
10307
10315
  tx(CONFORMANCE_TEXTS.scopeSummary, {
10308
10316
  label: scope.label,
10309
10317
  passCount: scopePass,
@@ -10480,8 +10488,7 @@ var DbBackupCommand = class extends SmCommand {
10480
10488
  await withSqlite({ databasePath: path, autoMigrate: false }, async (storage) => {
10481
10489
  storage.migrations.writeBackup(outPath);
10482
10490
  });
10483
- const stdout = this.context.stdout;
10484
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
10491
+ const ansi = this.ansiFor("stdout");
10485
10492
  this.printer.data(
10486
10493
  tx(DB_TEXTS.backupWritten, {
10487
10494
  glyph: ansi.green("\u2713"),
@@ -10545,8 +10552,7 @@ var DbRestoreCommand = class extends SmCommand {
10545
10552
  async run() {
10546
10553
  const target = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
10547
10554
  const sourcePath = resolve23(this.source);
10548
- const stderr = this.context.stderr;
10549
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
10555
+ const stderrAnsi = this.ansiFor("stderr");
10550
10556
  const sourceStat = await statOrNull(sourcePath);
10551
10557
  if (!sourceStat) {
10552
10558
  this.printer.error(
@@ -10584,8 +10590,7 @@ var DbRestoreCommand = class extends SmCommand {
10584
10590
  for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
10585
10591
  if (await pathExists(sidecar)) await rm(sidecar);
10586
10592
  }
10587
- const stdout = this.context.stdout;
10588
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
10593
+ const ansi = this.ansiFor("stdout");
10589
10594
  const cwd = defaultRuntimeContext().cwd;
10590
10595
  this.printer.data(
10591
10596
  tx(DB_TEXTS.restoreDone, {
@@ -10640,11 +10645,7 @@ var DbResetCommand = class extends SmCommand {
10640
10645
  // distance the validations from their guards.
10641
10646
  // eslint-disable-next-line complexity
10642
10647
  async run() {
10643
- const stderrReset = this.context.stderr;
10644
- const stderrAnsiReset = ansiFor({
10645
- isTTY: stderrReset.isTTY === true,
10646
- noColorFlag: this.noColor
10647
- });
10648
+ const stderrAnsiReset = this.ansiFor("stderr");
10648
10649
  if (this.state && this.hard) {
10649
10650
  this.printer.error(
10650
10651
  tx(DB_TEXTS.resetStateAndHardMutex, { glyph: stderrAnsiReset.red("\u2715") })
@@ -10676,8 +10677,7 @@ var DbResetCommand = class extends SmCommand {
10676
10677
  const p = `${path}${suffix}`;
10677
10678
  if (await pathExists(p)) await rm2(p);
10678
10679
  }
10679
- const stdoutHard = this.context.stdout;
10680
- const ansiHard = ansiFor({ isTTY: stdoutHard.isTTY === true, noColorFlag: this.noColor });
10680
+ const ansiHard = this.ansiFor("stdout");
10681
10681
  this.printer.data(
10682
10682
  tx(DB_TEXTS.resetHardDeleted, {
10683
10683
  glyph: ansiHard.green("\u2713"),
@@ -10730,8 +10730,7 @@ var DbResetCommand = class extends SmCommand {
10730
10730
  db.exec(`DELETE FROM "${name}"`);
10731
10731
  }
10732
10732
  db.exec("COMMIT");
10733
- const stdoutReset = this.context.stdout;
10734
- const ansiReset = ansiFor({ isTTY: stdoutReset.isTTY === true, noColorFlag: this.noColor });
10733
+ const ansiReset = this.ansiFor("stdout");
10735
10734
  this.printer.data(
10736
10735
  rows.length === 0 ? tx(DB_TEXTS.resetClearedNone, { glyph: ansiReset.green("\u2713") }) : tx(DB_TEXTS.resetCleared, {
10737
10736
  glyph: ansiReset.green("\u2713"),
@@ -10770,8 +10769,7 @@ var DbShellCommand = class extends SmCommand {
10770
10769
  if (exit !== null) return exit;
10771
10770
  const result = spawnSync3("sqlite3", [path], { stdio: "inherit" });
10772
10771
  if (result.error && result.error.code === "ENOENT") {
10773
- const stderr = this.context.stderr;
10774
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
10772
+ const ansi = this.ansiFor("stderr");
10775
10773
  this.printer.error(
10776
10774
  tx(DB_TEXTS.shellSqlite3NotFound, {
10777
10775
  glyph: ansi.red("\u2715"),
@@ -10827,11 +10825,7 @@ var DbBrowserCommand = class extends SmCommand {
10827
10825
  }
10828
10826
  const probe = spawnSync4("sqlitebrowser", ["--version"], { stdio: "ignore" });
10829
10827
  if (probe.error || probe.status !== 0) {
10830
- const stderrBrowser = this.context.stderr;
10831
- const ansiBrowser = ansiFor({
10832
- isTTY: stderrBrowser.isTTY === true,
10833
- noColorFlag: this.noColor
10834
- });
10828
+ const ansiBrowser = this.ansiFor("stderr");
10835
10829
  this.printer.error(tx(DB_TEXTS.browserNotFound, { glyph: ansiBrowser.red("\u2715") }));
10836
10830
  return ExitCode.Error;
10837
10831
  }
@@ -10861,8 +10855,7 @@ var DbDumpCommand = class extends SmCommand {
10861
10855
  const path = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
10862
10856
  const exit = requireDbOrExit(path, this.context.stderr);
10863
10857
  if (exit !== null) return exit;
10864
- const stderr = this.context.stderr;
10865
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
10858
+ const ansi = this.ansiFor("stderr");
10866
10859
  const errGlyph = ansi.red("\u2715");
10867
10860
  if (this.tables && this.tables.length > 0) {
10868
10861
  for (const t of this.tables) {
@@ -11018,11 +11011,7 @@ var DbMigrateCommand = class extends SmCommand {
11018
11011
  // the verb easier to follow.
11019
11012
  // eslint-disable-next-line complexity
11020
11013
  async run() {
11021
- const stderrMig = this.context.stderr;
11022
- const stderrAnsiMig = ansiFor({
11023
- isTTY: stderrMig.isTTY === true,
11024
- noColorFlag: this.noColor
11025
- });
11014
+ const stderrAnsiMig = this.ansiFor("stderr");
11026
11015
  const errGlyphMig = stderrAnsiMig.red("\u2715");
11027
11016
  if (this.kernelOnly && this.pluginId !== void 0) {
11028
11017
  this.printer.error(
@@ -11106,8 +11095,7 @@ var DbMigrateCommand = class extends SmCommand {
11106
11095
  }
11107
11096
  toValue = parsed;
11108
11097
  }
11109
- const stdoutMig = this.context.stdout;
11110
- const ansiMig = ansiFor({ isTTY: stdoutMig.isTTY === true, noColorFlag: this.noColor });
11098
+ const ansiMig = this.ansiFor("stdout");
11111
11099
  const okGlyph = ansiMig.green("\u2713");
11112
11100
  const cwdMig = defaultRuntimeContext().cwd;
11113
11101
  let kernelApplied;
@@ -11470,7 +11458,37 @@ function serialiseSubset(subset) {
11470
11458
  issues: subset.issues
11471
11459
  };
11472
11460
  }
11461
+ function buildSanitizedRows(subset) {
11462
+ const nodes = subset.nodes.map((node) => {
11463
+ const title = pickTitle2(node);
11464
+ return {
11465
+ pathRaw: node.path,
11466
+ path: sanitizeForTerminal(node.path),
11467
+ kindRaw: node.kind,
11468
+ kind: sanitizeForTerminal(node.kind),
11469
+ title: title === null ? null : sanitizeForTerminal(title)
11470
+ };
11471
+ });
11472
+ const links = subset.links.map((link2) => ({
11473
+ // Sort key uses raw bytes to preserve today's deterministic order
11474
+ // (sanitisation strips control chars; comparing on raw fields keeps
11475
+ // the output byte-identical with the legacy inline pattern).
11476
+ sortKey: `${link2.source}\0${link2.kind}\0${link2.target}`,
11477
+ source: sanitizeForTerminal(link2.source),
11478
+ kind: sanitizeForTerminal(link2.kind),
11479
+ target: sanitizeForTerminal(link2.target),
11480
+ confidence: link2.confidence
11481
+ }));
11482
+ const issues = subset.issues.map((issue) => ({
11483
+ severity: issue.severity,
11484
+ analyzerId: sanitizeForTerminal(issue.analyzerId),
11485
+ message: sanitizeForTerminal(issue.message)
11486
+ }));
11487
+ const issuesPerNode = countIssuesPerNode(subset.issues);
11488
+ return { nodes, links, issues, issuesPerNode };
11489
+ }
11473
11490
  function renderMarkdown(subset) {
11491
+ const rows = buildSanitizedRows(subset);
11474
11492
  const out = [];
11475
11493
  out.push(EXPORT_TEXTS.mdTitle);
11476
11494
  out.push("");
@@ -11481,43 +11499,38 @@ function renderMarkdown(subset) {
11481
11499
  );
11482
11500
  out.push(
11483
11501
  tx(EXPORT_TEXTS.mdCounts, {
11484
- nodes: subset.nodes.length,
11485
- links: subset.links.length,
11486
- issues: subset.issues.length
11502
+ nodes: rows.nodes.length,
11503
+ links: rows.links.length,
11504
+ issues: rows.issues.length
11487
11505
  })
11488
11506
  );
11489
11507
  out.push("");
11490
- const issuesPerNode = countIssuesPerNode(subset.issues);
11491
- out.push(...renderNodesByKindSection(subset.nodes, issuesPerNode));
11492
- if (subset.links.length > 0) {
11493
- out.push(tx(EXPORT_TEXTS.mdLinksSectionHeader, { count: subset.links.length }));
11508
+ out.push(...renderNodesByKindSection(rows.nodes, rows.issuesPerNode));
11509
+ if (rows.links.length > 0) {
11510
+ out.push(tx(EXPORT_TEXTS.mdLinksSectionHeader, { count: rows.links.length }));
11494
11511
  out.push("");
11495
- const sorted = [...subset.links].sort((a, b) => {
11496
- const aKey = `${a.source}\0${a.kind}\0${a.target}`;
11497
- const bKey = `${b.source}\0${b.kind}\0${b.target}`;
11498
- return aKey.localeCompare(bKey);
11499
- });
11512
+ const sorted = [...rows.links].sort((a, b) => a.sortKey.localeCompare(b.sortKey));
11500
11513
  for (const link2 of sorted) {
11501
11514
  out.push(
11502
11515
  tx(EXPORT_TEXTS.mdLinkBullet, {
11503
- source: sanitizeForTerminal(link2.source),
11504
- kind: sanitizeForTerminal(link2.kind),
11505
- target: sanitizeForTerminal(link2.target),
11516
+ source: link2.source,
11517
+ kind: link2.kind,
11518
+ target: link2.target,
11506
11519
  confidence: link2.confidence
11507
11520
  })
11508
11521
  );
11509
11522
  }
11510
11523
  out.push("");
11511
11524
  }
11512
- if (subset.issues.length > 0) {
11513
- out.push(tx(EXPORT_TEXTS.mdIssuesSectionHeader, { count: subset.issues.length }));
11525
+ if (rows.issues.length > 0) {
11526
+ out.push(tx(EXPORT_TEXTS.mdIssuesSectionHeader, { count: rows.issues.length }));
11514
11527
  out.push("");
11515
- for (const issue of subset.issues) {
11528
+ for (const issue of rows.issues) {
11516
11529
  out.push(
11517
11530
  tx(EXPORT_TEXTS.mdIssueBullet, {
11518
11531
  severity: issue.severity,
11519
- analyzerId: sanitizeForTerminal(issue.analyzerId),
11520
- message: sanitizeForTerminal(issue.message)
11532
+ analyzerId: issue.analyzerId,
11533
+ message: issue.message
11521
11534
  })
11522
11535
  );
11523
11536
  }
@@ -11537,8 +11550,8 @@ function countIssuesPerNode(issues) {
11537
11550
  function renderNodesByKindSection(nodes, issuesPerNode) {
11538
11551
  const byKind = /* @__PURE__ */ new Map();
11539
11552
  for (const node of nodes) {
11540
- if (!byKind.has(node.kind)) byKind.set(node.kind, []);
11541
- byKind.get(node.kind).push(node);
11553
+ if (!byKind.has(node.kindRaw)) byKind.set(node.kindRaw, []);
11554
+ byKind.get(node.kindRaw).push(node);
11542
11555
  }
11543
11556
  const lines = [];
11544
11557
  const renderedKinds = /* @__PURE__ */ new Set();
@@ -11550,16 +11563,16 @@ function renderNodesByKindSection(nodes, issuesPerNode) {
11550
11563
  if (renderedKinds.has(kind)) continue;
11551
11564
  const group = byKind.get(kind);
11552
11565
  if (!group || group.length === 0) continue;
11553
- appendKindSection(lines, kind, group, issuesPerNode);
11566
+ appendKindSection(lines, group, issuesPerNode);
11554
11567
  renderedKinds.add(kind);
11555
11568
  }
11556
11569
  return lines;
11557
11570
  }
11558
- function appendKindSection(lines, kind, group, issuesPerNode) {
11559
- const sorted = [...group].sort((a, b) => a.path.localeCompare(b.path));
11571
+ function appendKindSection(lines, group, issuesPerNode) {
11572
+ const sorted = [...group].sort((a, b) => a.pathRaw.localeCompare(b.pathRaw));
11560
11573
  lines.push(
11561
11574
  tx(EXPORT_TEXTS.mdKindSectionHeader, {
11562
- kind: sanitizeForTerminal(kind),
11575
+ kind: sorted[0].kind,
11563
11576
  count: sorted.length
11564
11577
  })
11565
11578
  );
@@ -11568,15 +11581,14 @@ function appendKindSection(lines, kind, group, issuesPerNode) {
11568
11581
  lines.push("");
11569
11582
  }
11570
11583
  function renderNodeBullet(node, issuesPerNode) {
11571
- const title = pickTitle2(node);
11572
- const issueCount = issuesPerNode.get(node.path) ?? 0;
11573
- const titleSegment = title ? tx(EXPORT_TEXTS.mdNodeTitleSuffix, { title: sanitizeForTerminal(title) }) : "";
11584
+ const issueCount = issuesPerNode.get(node.pathRaw) ?? 0;
11585
+ const titleSegment = node.title !== null ? tx(EXPORT_TEXTS.mdNodeTitleSuffix, { title: node.title }) : "";
11574
11586
  const issuesSegment = issueCount > 0 ? tx(EXPORT_TEXTS.mdNodeIssueSuffix, {
11575
11587
  count: issueCount,
11576
11588
  label: issueCount === 1 ? EXPORT_TEXTS.mdNodeIssueLabelSingular : EXPORT_TEXTS.mdNodeIssueLabelPlural
11577
11589
  }) : "";
11578
11590
  return tx(EXPORT_TEXTS.mdNodeBullet, {
11579
- path: sanitizeForTerminal(node.path),
11591
+ path: node.path,
11580
11592
  title: titleSegment,
11581
11593
  issues: issuesSegment
11582
11594
  });
@@ -11632,8 +11644,7 @@ var GraphCommand = class extends SmCommand {
11632
11644
  const formatter = formatters.find((f) => f.formatId === this.format);
11633
11645
  if (!formatter) {
11634
11646
  const available = formatters.map((f) => f.formatId).sort().join(", ");
11635
- const stderr = this.context.stderr;
11636
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
11647
+ const ansi = this.ansiFor("stderr");
11637
11648
  this.printer.error(
11638
11649
  tx(GRAPH_TEXTS.noFormatterRegistered, {
11639
11650
  glyph: ansi.red("\u2715"),
@@ -11766,8 +11777,7 @@ var HelpCommand = class extends Command15 {
11766
11777
  this.context.stderr.write(tx(HELP_TEXTS.invalidFormat, { format: this.format }));
11767
11778
  return ExitCode.Error;
11768
11779
  }
11769
- const rawDefs = this.cli.definitions();
11770
- const verbs = rawDefs.filter((d) => !isBuiltin(d)).map(normalizeDefinition).sort(byPath);
11780
+ const verbs = buildVerbCatalog(this.cli);
11771
11781
  const verb = this.verbParts.join(" ").trim();
11772
11782
  if (verb) {
11773
11783
  const target = verbs.find((v) => v.name === verb);
@@ -11802,6 +11812,10 @@ function normalizeFormat(raw) {
11802
11812
  if (raw === "human" || raw === "md" || raw === "json") return raw;
11803
11813
  return null;
11804
11814
  }
11815
+ function buildVerbCatalog(cli2) {
11816
+ const rawDefs = cli2.definitions();
11817
+ return rawDefs.filter((d) => !isBuiltin(d)).map(normalizeDefinition).sort(byPath);
11818
+ }
11805
11819
  function isBuiltin(def) {
11806
11820
  const path = (def.path ?? "").trim();
11807
11821
  return path === "sm -h" || path === "sm --help" || path === "sm --version";
@@ -12080,8 +12094,7 @@ function renderCompactOverview(verbs) {
12080
12094
  var RootHelpCommand = class extends Command15 {
12081
12095
  static paths = [["-h"], ["--help"]];
12082
12096
  async execute() {
12083
- const rawDefs = this.cli.definitions();
12084
- const verbs = rawDefs.filter((d) => !isBuiltin(d)).map(normalizeDefinition).sort(byPath);
12097
+ const verbs = buildVerbCatalog(this.cli);
12085
12098
  this.context.stdout.write(renderCompactOverview(verbs));
12086
12099
  return ExitCode.Ok;
12087
12100
  }
@@ -12209,8 +12222,7 @@ var HooksInstallCommand = class extends SmCommand {
12209
12222
  // in `computePlannedHookContent` and `findGitRepoRoot`.
12210
12223
  // eslint-disable-next-line complexity
12211
12224
  async run() {
12212
- const stdout = this.context.stdout;
12213
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
12225
+ const ansi = this.ansiFor("stdout");
12214
12226
  const okGlyph = ansi.green("\u2713");
12215
12227
  const errGlyph = ansi.red("\u2715");
12216
12228
  if (this.flavour !== "pre-commit-bump") {
@@ -14297,8 +14309,7 @@ var InitCommand = class extends SmCommand {
14297
14309
  const ignorePath = defaultIgnoreFilePath(scopeRoot);
14298
14310
  const dbPath = defaultDbPath(scopeRoot);
14299
14311
  if (await pathExists(settingsPath) && !this.force) {
14300
- const stderr = this.context.stderr;
14301
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
14312
+ const stderrAnsi = this.ansiFor("stderr");
14302
14313
  this.printer.error(
14303
14314
  tx(INIT_TEXTS.alreadyInitialised, {
14304
14315
  glyph: stderrAnsi.red("\u2715"),
@@ -14328,15 +14339,14 @@ var InitCommand = class extends SmCommand {
14328
14339
  return ExitCode.Ok;
14329
14340
  }
14330
14341
  await mkdir3(skillMapDir, { recursive: true });
14331
- await writeFile(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
14342
+ writeFileAtomicExclusive(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
14332
14343
  if (!await pathExists(localPath) || this.force) {
14333
- await writeFile(localPath, "{}\n");
14344
+ writeFileAtomicExclusive(localPath, "{}\n");
14334
14345
  }
14335
14346
  if (!await pathExists(ignorePath) || this.force) {
14336
- await writeFile(ignorePath, loadBundledIgnoreText());
14347
+ writeFileAtomicExclusive(ignorePath, loadBundledIgnoreText());
14337
14348
  }
14338
- const stdout = this.context.stdout;
14339
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
14349
+ const ansi = this.ansiFor("stdout");
14340
14350
  const okGlyph = ansi.green("\u2713");
14341
14351
  if (!this.global) {
14342
14352
  const updated = await ensureGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
@@ -14625,8 +14635,7 @@ var HistoryCommand = class extends SmCommand {
14625
14635
  // validations from the filter they shape.
14626
14636
  // eslint-disable-next-line complexity
14627
14637
  async run() {
14628
- const stderr = this.context.stderr;
14629
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
14638
+ const stderrAnsi = this.ansiFor("stderr");
14630
14639
  const filter = {};
14631
14640
  if (this.node !== void 0) filter.nodePath = this.node;
14632
14641
  if (this.action !== void 0) filter.actionId = this.action;
@@ -14660,8 +14669,7 @@ var HistoryCommand = class extends SmCommand {
14660
14669
  } else if (rows.length === 0) {
14661
14670
  this.printer.data(HISTORY_TEXTS.noExecutionsFound);
14662
14671
  } else {
14663
- const stdout = this.context.stdout;
14664
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
14672
+ const ansi = this.ansiFor("stdout");
14665
14673
  this.printer.data(renderTable(rows, ansi));
14666
14674
  }
14667
14675
  return ExitCode.Ok;
@@ -14698,8 +14706,7 @@ var HistoryStatsCommand = class extends SmCommand {
14698
14706
  // eslint-disable-next-line complexity
14699
14707
  async run() {
14700
14708
  const elapsed = this.elapsed;
14701
- const stderr = this.context.stderr;
14702
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
14709
+ const stderrAnsi = this.ansiFor("stderr");
14703
14710
  const errGlyph = stderrAnsi.red("\u2715");
14704
14711
  let sinceMs = null;
14705
14712
  let untilMs = Date.now();
@@ -14772,8 +14779,7 @@ var HistoryStatsCommand = class extends SmCommand {
14772
14779
  }
14773
14780
  this.printer.data(JSON.stringify(stats) + "\n");
14774
14781
  } else {
14775
- const stdout = this.context.stdout;
14776
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
14782
+ const ansi = this.ansiFor("stdout");
14777
14783
  this.printer.data(renderStats(stats, ansi));
14778
14784
  }
14779
14785
  return ExitCode.Ok;
@@ -15054,12 +15060,11 @@ var JobPruneCommand = class extends SmCommand {
15054
15060
  });
15055
15061
  async run() {
15056
15062
  const ctx = defaultRuntimeContext();
15057
- const dbPath = defaultProjectDbPath(ctx);
15063
+ const dbPath = resolveDbPath({ global: this.global, db: this.db, ...ctx });
15058
15064
  const jobsDir = defaultProjectJobsDir(ctx);
15059
15065
  const exit = requireDbOrExit(dbPath, this.context.stderr);
15060
15066
  if (exit !== null) return exit;
15061
- const stderr = this.context.stderr;
15062
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15067
+ const stderrAnsi = this.ansiFor("stderr");
15063
15068
  const errGlyph = stderrAnsi.red("\u2715");
15064
15069
  let cfg;
15065
15070
  try {
@@ -15258,8 +15263,7 @@ var ListCommand = class extends SmCommand {
15258
15263
  tag = Option19.String("--tag", { required: false });
15259
15264
  tagSource = Option19.String("--tag-source", { required: false });
15260
15265
  async run() {
15261
- const stderr = this.context.stderr;
15262
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15266
+ const stderrAnsi = this.ansiFor("stderr");
15263
15267
  const flags = this.#parseFlags(stderrAnsi);
15264
15268
  if (!flags.ok) return flags.exit;
15265
15269
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
@@ -15336,8 +15340,7 @@ var ListCommand = class extends SmCommand {
15336
15340
  return ExitCode.Ok;
15337
15341
  }
15338
15342
  if (nodes.length === 0) return this.#renderEmpty();
15339
- const stdout = this.context.stdout;
15340
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
15343
+ const ansi = this.ansiFor("stdout");
15341
15344
  this.printer.data(renderTable2(nodes, issuesByNode, ansi));
15342
15345
  return ExitCode.Ok;
15343
15346
  }
@@ -15551,8 +15554,7 @@ var OrphansCommand = class extends SmCommand {
15551
15554
  });
15552
15555
  kind = Option20.String("--kind", { required: false });
15553
15556
  async run() {
15554
- const stderr = this.context.stderr;
15555
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15557
+ const stderrAnsi = this.ansiFor("stderr");
15556
15558
  let analyzerFilter = null;
15557
15559
  if (this.kind !== void 0) {
15558
15560
  const map = {
@@ -15581,8 +15583,7 @@ var OrphansCommand = class extends SmCommand {
15581
15583
  if (analyzerFilter !== null) return issue.analyzerId === analyzerFilter;
15582
15584
  return true;
15583
15585
  });
15584
- const stdout = this.context.stdout;
15585
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
15586
+ const ansi = this.ansiFor("stdout");
15586
15587
  if (this.json) {
15587
15588
  this.printer.data(
15588
15589
  JSON.stringify(found.map((f) => f.issue)) + "\n"
@@ -15623,8 +15624,7 @@ var OrphansReconcileCommand = class extends SmCommand {
15623
15624
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
15624
15625
  const exit = requireDbOrExit(dbPath, this.context.stderr);
15625
15626
  if (exit !== null) return exit;
15626
- const stderr = this.context.stderr;
15627
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15627
+ const stderrAnsi = this.ansiFor("stderr");
15628
15628
  const errGlyph = stderrAnsi.red("\u2715");
15629
15629
  return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
15630
15630
  const target = await adapter.scans.findNode(this.to);
@@ -15669,8 +15669,7 @@ var OrphansReconcileCommand = class extends SmCommand {
15669
15669
  },
15670
15670
  dryRun
15671
15671
  );
15672
- const stdout = this.context.stdout;
15673
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
15672
+ const ansi = this.ansiFor("stdout");
15674
15673
  const totalRows = summaryTotal(summary);
15675
15674
  const breakdown = tx(ORPHANS_TEXTS.reconcileBreakdown, {
15676
15675
  rows: totalRows,
@@ -15749,8 +15748,7 @@ var OrphansUndoRenameCommand = class extends SmCommand {
15749
15748
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...defaultRuntimeContext() });
15750
15749
  const exit = requireDbOrExit(dbPath, this.context.stderr);
15751
15750
  if (exit !== null) return exit;
15752
- const stderr = this.context.stderr;
15753
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15751
+ const stderrAnsi = this.ansiFor("stderr");
15754
15752
  const errGlyph = stderrAnsi.red("\u2715");
15755
15753
  return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
15756
15754
  const candidates = await findActiveOrphanIssues(adapter, (issue2) => {
@@ -15820,8 +15818,7 @@ var OrphansUndoRenameCommand = class extends SmCommand {
15820
15818
  },
15821
15819
  dryRun
15822
15820
  );
15823
- const stdout = this.context.stdout;
15824
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
15821
+ const ansi = this.ansiFor("stdout");
15825
15822
  const rows = summaryTotal(summary);
15826
15823
  if (dryRun) {
15827
15824
  this.printer.data(
@@ -15869,8 +15866,7 @@ var OrphansUndoRenameCommand = class extends SmCommand {
15869
15866
  return this.#resolveFromAmbiguous(issue);
15870
15867
  }
15871
15868
  #resolveFromMedium(issue) {
15872
- const stderr = this.context.stderr;
15873
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15869
+ const stderrAnsi = this.ansiFor("stderr");
15874
15870
  const errGlyph = stderrAnsi.red("\u2715");
15875
15871
  const dataFrom = issue.data ? issue.data["from"] : void 0;
15876
15872
  if (typeof dataFrom !== "string") {
@@ -15895,8 +15891,7 @@ var OrphansUndoRenameCommand = class extends SmCommand {
15895
15891
  return { ok: true, from: dataFrom };
15896
15892
  }
15897
15893
  #resolveFromAmbiguous(issue) {
15898
- const stderr = this.context.stderr;
15899
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
15894
+ const stderrAnsi = this.ansiFor("stderr");
15900
15895
  const errGlyph = stderrAnsi.red("\u2715");
15901
15896
  if (this.from === void 0) {
15902
15897
  this.printer.error(
@@ -16227,8 +16222,7 @@ var PluginsListCommand = class extends SmCommand {
16227
16222
  this.printer.data(PLUGINS_TEXTS.listEmpty);
16228
16223
  return ExitCode.Ok;
16229
16224
  }
16230
- const stdout = this.context.stdout;
16231
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
16225
+ const ansi = this.ansiFor("stdout");
16232
16226
  this.printer.data(renderListHuman(builtIns2, plugins, ansi));
16233
16227
  return ExitCode.Ok;
16234
16228
  }
@@ -16330,8 +16324,7 @@ var PluginsShowCommand = class extends SmCommand {
16330
16324
  const plugins = await loadAll({ global: this.global, pluginDir: this.pluginDir });
16331
16325
  const resolveEnabled = await buildResolver(this.global);
16332
16326
  const builtIns2 = builtInRows(resolveEnabled);
16333
- const stderr = this.context.stderr;
16334
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
16327
+ const stderrAnsi = this.ansiFor("stderr");
16335
16328
  const lookupResult = resolveShowLookupId(this.id, builtIns2, plugins, stderrAnsi);
16336
16329
  if ("error" in lookupResult) {
16337
16330
  this.printer.error(lookupResult.error);
@@ -16355,8 +16348,7 @@ var PluginsShowCommand = class extends SmCommand {
16355
16348
  this.printer.data(JSON.stringify(payload, omitModule, 2) + "\n");
16356
16349
  return ExitCode.Ok;
16357
16350
  }
16358
- const stdout = this.context.stdout;
16359
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
16351
+ const ansi = this.ansiFor("stdout");
16360
16352
  const text = builtIn ? renderBuiltInDetail(builtIn, ansi) : renderPluginDetail(match, ansi);
16361
16353
  this.printer.data(text);
16362
16354
  return ExitCode.Ok;
@@ -16569,8 +16561,7 @@ var PluginsDoctorCommand = class extends SmCommand {
16569
16561
  this.printer.data(JSON.stringify(envelope) + "\n");
16570
16562
  return bad.length > 0 ? ExitCode.Issues : ExitCode.Ok;
16571
16563
  }
16572
- const stdout = this.context.stdout;
16573
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
16564
+ const ansi = this.ansiFor("stdout");
16574
16565
  this.#renderSummaryHeader(counts.enabled, bad.length, totalWarnings);
16575
16566
  this.#renderSourceBreakdown(builtIns2.length, plugins.length);
16576
16567
  this.#renderStatusBreakdown(counts, ansi);
@@ -16890,8 +16881,7 @@ var TogglePluginsBase = class extends SmCommand {
16890
16881
  id = Option24.String({ required: false });
16891
16882
  async toggle(enabled) {
16892
16883
  const verb = enabled ? "enable" : "disable";
16893
- const stderr = this.context.stderr;
16894
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
16884
+ const stderrAnsi = this.ansiFor("stderr");
16895
16885
  const argError = this.#validateArgs(stderrAnsi);
16896
16886
  if (argError !== null) return argError;
16897
16887
  const plugins = await loadAll({ global: this.global, pluginDir: void 0 });
@@ -17155,8 +17145,7 @@ var PluginsCreateCommand = class extends SmCommand {
17155
17145
  at = Option25.String("--at", { required: false });
17156
17146
  force = Option25.Boolean("--force", false);
17157
17147
  async run() {
17158
- const stderr = this.context.stderr;
17159
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
17148
+ const ansi = this.ansiFor("stderr");
17160
17149
  const errGlyph = ansi.red("\u2715");
17161
17150
  if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(this.pluginId)) {
17162
17151
  this.printer.error(
@@ -17342,8 +17331,7 @@ var PluginsSlotsListCommand = class extends SmCommand {
17342
17331
  );
17343
17332
  return ExitCode.Ok;
17344
17333
  }
17345
- const stdout = this.context.stdout;
17346
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
17334
+ const ansi = this.ansiFor("stdout");
17347
17335
  const idWidth = Math.max(
17348
17336
  ...VIEW_SLOTS_CATALOG.map((c) => c.id.length),
17349
17337
  ...INPUT_TYPES_CATALOG.map((t) => t.id.length)
@@ -17489,8 +17477,7 @@ var RefreshCommand = class extends SmCommand {
17489
17477
  // `#resolveTargetNodes` and `#runExtractorsAcrossNodes`.
17490
17478
  // eslint-disable-next-line complexity
17491
17479
  async run() {
17492
- const stderrEarly = this.context.stderr;
17493
- const ansiEarly = ansiFor({ isTTY: stderrEarly.isTTY === true, noColorFlag: this.noColor });
17480
+ const ansiEarly = this.ansiFor("stderr");
17494
17481
  const errGlyph = ansiEarly.red("\u2715");
17495
17482
  if (this.stale && this.nodePath !== void 0) {
17496
17483
  this.printer.info(tx(REFRESH_TEXTS.nodeAndStaleMutex, { glyph: errGlyph }));
@@ -17501,7 +17488,7 @@ var RefreshCommand = class extends SmCommand {
17501
17488
  return ExitCode.Error;
17502
17489
  }
17503
17490
  const ctx = defaultRuntimeContext();
17504
- const dbPath = defaultProjectDbPath(ctx);
17491
+ const dbPath = resolveDbPath({ global: this.global, db: this.db, ...ctx });
17505
17492
  const pluginRuntime = this.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: "project" });
17506
17493
  pluginRuntime.emitWarnings(this.printer);
17507
17494
  listBuiltIns();
@@ -17511,8 +17498,7 @@ var RefreshCommand = class extends SmCommand {
17511
17498
  killSwitches: readConformanceKillSwitches()
17512
17499
  });
17513
17500
  const allExtractors = composed?.extractors ?? [];
17514
- const stdout = this.context.stdout;
17515
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
17501
+ const ansi = this.ansiFor("stdout");
17516
17502
  const persisted = await tryWithSqlite(
17517
17503
  { databasePath: dbPath, autoBackup: false },
17518
17504
  async (adapter) => {
@@ -17697,8 +17683,7 @@ var RefreshCommand = class extends SmCommand {
17697
17683
  body = stripFrontmatterFence(raw);
17698
17684
  } catch (err) {
17699
17685
  if (!this.json) {
17700
- const stderr = this.context.stderr;
17701
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
17686
+ const ansi = this.ansiFor("stderr");
17702
17687
  this.printer.info(
17703
17688
  tx(REFRESH_TEXTS.refreshFailed, {
17704
17689
  glyph: ansi.red("\u2715"),
@@ -17749,7 +17734,21 @@ import { Command as Command31, Option as Option29 } from "clipanion";
17749
17734
  // cli/i18n/scan.texts.ts
17750
17735
  var SCAN_TEXTS = {
17751
17736
  // --- scan command ----------------------------------------------------
17752
- watchCannotCombine: "{{glyph}} --watch cannot be combined with --no-built-ins, --dry-run, --changed, or --allow-empty.\n",
17737
+ /**
17738
+ * Per-flag rejection messages for `sm scan --watch` combos. The
17739
+ * watcher is incremental-only and always persists, so flags that
17740
+ * change those invariants are mutually exclusive with `--watch`.
17741
+ * Each entry follows the §3.1b two-line block (headline + dim hint)
17742
+ * so the user can act without re-reading `--help`.
17743
+ */
17744
+ watchVsNoBuiltIns: "{{glyph}} --watch cannot be combined with --no-built-ins.\n {{hint}}\n",
17745
+ watchVsNoBuiltInsHint: "Drop --no-built-ins or use --watch alone. The watcher always persists, an empty pipeline has nothing to persist.",
17746
+ watchVsDryRun: "{{glyph}} --watch cannot be combined with --dry-run.\n {{hint}}\n",
17747
+ watchVsDryRunHint: "Drop --dry-run or use --watch alone. The watcher always persists each incremental batch.",
17748
+ watchVsChanged: "{{glyph}} --watch cannot be combined with --changed.\n {{hint}}\n",
17749
+ watchVsChangedHint: "Drop --changed or use --watch alone. The watcher is incremental by definition.",
17750
+ watchVsAllowEmpty: "{{glyph}} --watch cannot be combined with --allow-empty.\n {{hint}}\n",
17751
+ watchVsAllowEmptyHint: "Drop --allow-empty or use --watch alone. The watcher never produces a zero-result wipe.",
17753
17752
  changedWithoutBuiltIns: "{{glyph}} --changed and --no-built-ins cannot be combined.\n {{hint}}\n",
17754
17753
  changedWithoutBuiltInsHint: "--no-built-ins yields a zero-filled ScanResult, leaving nothing to merge against.",
17755
17754
  /**
@@ -18154,12 +18153,12 @@ async function runWatchLoop(opts) {
18154
18153
  stderr: context.stderr
18155
18154
  });
18156
18155
  const runtimeCtx = defaultRuntimeContext();
18157
- const dbPath = defaultProjectDbPath(runtimeCtx);
18156
+ const dbPath = resolveDbPath({ global: opts.global, db: opts.db, ...runtimeCtx });
18158
18157
  const breakerLimit = opts.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
18159
18158
  const stdoutTty = context.stdout;
18160
- const ansi = ansiFor({ isTTY: stdoutTty.isTTY === true, noColorFlag: false });
18159
+ const ansi = ansiFor({ isTTY: stdoutTty.isTTY === true, noColorFlag: opts.noColor });
18161
18160
  const stderrTty = context.stderr;
18162
- const stderrAnsi = ansiFor({ isTTY: stderrTty.isTTY === true, noColorFlag: false });
18161
+ const stderrAnsi = ansiFor({ isTTY: stderrTty.isTTY === true, noColorFlag: opts.noColor });
18163
18162
  const errGlyph = stderrAnsi.red("\u2715");
18164
18163
  let initialDone = false;
18165
18164
  const renderBatch = (result) => {
@@ -18324,13 +18323,16 @@ var WatchCommand = class extends SmCommand {
18324
18323
  emitElapsed = false;
18325
18324
  async run() {
18326
18325
  const roots = this.roots.length > 0 ? this.roots : ["."];
18327
- const breaker = parseBreakerLimit(this.maxConsecutiveFailures, this.context.stderr);
18326
+ const breaker = parseBreakerLimit(this.maxConsecutiveFailures, this.context.stderr, this.noColor);
18328
18327
  if (breaker === null) return ExitCode.Error;
18329
18328
  const watchOpts = {
18330
18329
  roots,
18331
18330
  json: this.json,
18332
18331
  noTokens: this.noTokens,
18333
18332
  strict: this.strict,
18333
+ noColor: this.noColor,
18334
+ global: this.global,
18335
+ db: this.db,
18334
18336
  noPlugins: this.noPlugins,
18335
18337
  context: this.context,
18336
18338
  printer: this.printer
@@ -18339,12 +18341,12 @@ var WatchCommand = class extends SmCommand {
18339
18341
  return runWatchLoop(watchOpts);
18340
18342
  }
18341
18343
  };
18342
- function parseBreakerLimit(raw, stderr) {
18344
+ function parseBreakerLimit(raw, stderr, noColor) {
18343
18345
  if (raw === void 0) return void 0;
18344
18346
  const parsed = tryParseNonNegativeInt(raw);
18345
18347
  if (parsed === null) {
18346
18348
  const stderrTty = stderr;
18347
- const ansi = ansiFor({ isTTY: stderrTty.isTTY === true, noColorFlag: false });
18349
+ const ansi = ansiFor({ isTTY: stderrTty.isTTY === true, noColorFlag: noColor });
18348
18350
  stderr.write(
18349
18351
  tx(WATCH_TEXTS.maxConsecutiveFailuresInvalid, { glyph: ansi.red("\u2715"), raw })
18350
18352
  );
@@ -18423,8 +18425,7 @@ var ScanCommand = class extends SmCommand {
18423
18425
  async run() {
18424
18426
  if (this.watch) return this.runWatchAlias();
18425
18427
  if (this.changed && this.noBuiltIns) {
18426
- const stderr = this.context.stderr;
18427
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
18428
+ const ansi = this.ansiFor("stderr");
18428
18429
  this.printer.info(
18429
18430
  tx(SCAN_TEXTS.changedWithoutBuiltIns, {
18430
18431
  glyph: ansi.red("\u2715"),
@@ -18434,8 +18435,7 @@ var ScanCommand = class extends SmCommand {
18434
18435
  return ExitCode.Error;
18435
18436
  }
18436
18437
  if (this.global === true) {
18437
- const stderr = this.context.stderr;
18438
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
18438
+ const ansi = this.ansiFor("stderr");
18439
18439
  this.printer.info(
18440
18440
  tx(SCAN_TEXTS.globalNotSupported, { glyph: ansi.red("\u2715") })
18441
18441
  );
@@ -18466,10 +18466,15 @@ var ScanCommand = class extends SmCommand {
18466
18466
  * always persists incrementally over the prior snapshot.
18467
18467
  */
18468
18468
  async runWatchAlias() {
18469
- if (this.noBuiltIns || this.dryRun || this.changed || this.allowEmpty) {
18470
- const stderr = this.context.stderr;
18471
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
18472
- this.printer.info(tx(SCAN_TEXTS.watchCannotCombine, { glyph: ansi.red("\u2715") }));
18469
+ const conflict = this.#firstWatchConflict();
18470
+ if (conflict !== null) {
18471
+ const ansi = this.ansiFor("stderr");
18472
+ this.printer.info(
18473
+ tx(conflict.template, {
18474
+ glyph: ansi.red("\u2715"),
18475
+ hint: ansi.dim(conflict.hint)
18476
+ })
18477
+ );
18473
18478
  return ExitCode.Error;
18474
18479
  }
18475
18480
  this.emitElapsed = false;
@@ -18479,15 +18484,39 @@ var ScanCommand = class extends SmCommand {
18479
18484
  json: this.json,
18480
18485
  noTokens: this.noTokens,
18481
18486
  strict: this.strict,
18487
+ noColor: this.noColor,
18488
+ global: this.global,
18489
+ db: this.db,
18482
18490
  noPlugins: this.noPlugins,
18483
18491
  context: this.context,
18484
18492
  printer: this.printer
18485
18493
  });
18486
18494
  }
18495
+ /**
18496
+ * Detect the first `--watch` combo conflict in flag-declaration order
18497
+ * and return the catalog entries (full template + dim hint) that
18498
+ * `runWatchAlias` renders for it. Returns `null` when no conflict
18499
+ * is active. Order matches the historic message so reading the
18500
+ * branch top-down still tells the user which flag fired first.
18501
+ */
18502
+ #firstWatchConflict() {
18503
+ if (this.noBuiltIns) {
18504
+ return { template: SCAN_TEXTS.watchVsNoBuiltIns, hint: SCAN_TEXTS.watchVsNoBuiltInsHint };
18505
+ }
18506
+ if (this.dryRun) {
18507
+ return { template: SCAN_TEXTS.watchVsDryRun, hint: SCAN_TEXTS.watchVsDryRunHint };
18508
+ }
18509
+ if (this.changed) {
18510
+ return { template: SCAN_TEXTS.watchVsChanged, hint: SCAN_TEXTS.watchVsChangedHint };
18511
+ }
18512
+ if (this.allowEmpty) {
18513
+ return { template: SCAN_TEXTS.watchVsAllowEmpty, hint: SCAN_TEXTS.watchVsAllowEmptyHint };
18514
+ }
18515
+ return null;
18516
+ }
18487
18517
  /** Render the failure branch of `IScanRunResult` to stderr. */
18488
18518
  renderFailure(outcome) {
18489
- const stderr = this.context.stderr;
18490
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
18519
+ const ansi = this.ansiFor("stderr");
18491
18520
  const errGlyph = ansi.red("\u2715");
18492
18521
  if (outcome.kind === "guard-trip") {
18493
18522
  this.printer.info(
@@ -18514,8 +18543,7 @@ var ScanCommand = class extends SmCommand {
18514
18543
  if (this.json) {
18515
18544
  return this.#renderJsonOutcome(result, exitCode2, strict);
18516
18545
  }
18517
- const stdout = this.context.stdout;
18518
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
18546
+ const ansi = this.ansiFor("stdout");
18519
18547
  const cwd = defaultRuntimeContext().cwd;
18520
18548
  const hasErrors = exitCode2 === ExitCode.Issues;
18521
18549
  const issuesCount = result.stats.issuesCount;
@@ -18557,8 +18585,7 @@ var ScanCommand = class extends SmCommand {
18557
18585
  const validators = loadSchemaValidators();
18558
18586
  const validation = validators.validate("scan-result", result);
18559
18587
  if (!validation.ok) {
18560
- const stderr = this.context.stderr;
18561
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
18588
+ const ansi = this.ansiFor("stderr");
18562
18589
  this.printer.info(
18563
18590
  tx(SCAN_TEXTS.jsonSelfValidationFailed, {
18564
18591
  glyph: ansi.red("\u2715"),
@@ -18690,8 +18717,7 @@ var ScanCompareCommand = class extends SmCommand {
18690
18717
  this.printer.data(JSON.stringify(delta) + "\n");
18691
18718
  return exitCode2;
18692
18719
  }
18693
- const stdout = this.context.stdout;
18694
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
18720
+ const ansi = this.ansiFor("stdout");
18695
18721
  this.printer.data(renderDeltaHuman(delta, ansi));
18696
18722
  return exitCode2;
18697
18723
  }
@@ -19169,6 +19195,19 @@ function originGuarded(path) {
19169
19195
  return false;
19170
19196
  }
19171
19197
 
19198
+ // server/security-headers.ts
19199
+ var DEFAULT_CSP = "frame-ancestors 'none'; base-uri 'self'; form-action 'self'";
19200
+ function createSecurityHeaders() {
19201
+ return async (c, next) => {
19202
+ await next();
19203
+ const h = c.res.headers;
19204
+ if (!h.has("content-security-policy")) h.set("content-security-policy", DEFAULT_CSP);
19205
+ if (!h.has("x-frame-options")) h.set("x-frame-options", "DENY");
19206
+ if (!h.has("x-content-type-options")) h.set("x-content-type-options", "nosniff");
19207
+ if (!h.has("referrer-policy")) h.set("referrer-policy", "no-referrer");
19208
+ };
19209
+ }
19210
+
19172
19211
  // server/routes/annotations.ts
19173
19212
  var ENVELOPE_KIND = "annotations.registered";
19174
19213
  function registerAnnotationsRoute(app, deps) {
@@ -20482,6 +20521,16 @@ var ScanBusyError = class extends Error {
20482
20521
  }
20483
20522
  };
20484
20523
 
20524
+ // server/util/noop-writable.ts
20525
+ import { Writable } from "stream";
20526
+ function noopWritable() {
20527
+ return new Writable({
20528
+ write(_chunk, _encoding, callback) {
20529
+ callback();
20530
+ }
20531
+ });
20532
+ }
20533
+
20485
20534
  // server/events.ts
20486
20535
  function buildWatcherStartedEvent(data) {
20487
20536
  return {
@@ -20604,7 +20653,7 @@ async function runPersistedScan(c, deps) {
20604
20653
  changed: false,
20605
20654
  allowEmpty: true,
20606
20655
  strict: false,
20607
- stderr: process.stderr,
20656
+ stderr: noopWritable(),
20608
20657
  ctx: deps.runtimeContext,
20609
20658
  pluginRuntime: deps.pluginRuntime,
20610
20659
  resolveEnabledOverride,
@@ -20696,7 +20745,7 @@ async function runFreshScan(deps) {
20696
20745
  changed: false,
20697
20746
  allowEmpty: true,
20698
20747
  strict: false,
20699
- stderr: process.stderr,
20748
+ stderr: noopWritable(),
20700
20749
  ctx: deps.runtimeContext,
20701
20750
  // M3: reuse the boot-cached pluginRuntime so a fresh scan over
20702
20751
  // the BFF doesn't re-walk `.skill-map/plugins/` per request. A
@@ -20855,7 +20904,7 @@ async function loadNode(deps, nodePath) {
20855
20904
  const node = persisted?.nodes.find((n) => n.path === nodePath);
20856
20905
  if (!node) {
20857
20906
  throw new HTTPException12(404, {
20858
- message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
20907
+ message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
20859
20908
  });
20860
20909
  }
20861
20910
  return node;
@@ -21080,6 +21129,7 @@ function createApp(deps) {
21080
21129
  homedir: deps.runtimeContext.homedir
21081
21130
  });
21082
21131
  app.use("*", createLoopbackGate({ port: deps.options.port }));
21132
+ app.use("*", createSecurityHeaders());
21083
21133
  app.use(
21084
21134
  "/api/*",
21085
21135
  bodyLimit({
@@ -22330,8 +22380,7 @@ var ShowCommand = class extends SmCommand {
22330
22380
  this.printer.data(JSON.stringify(doc) + "\n");
22331
22381
  return ExitCode.Ok;
22332
22382
  }
22333
- const stdout = this.context.stdout;
22334
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
22383
+ const ansi = this.ansiFor("stdout");
22335
22384
  this.printer.data(renderHuman2(doc, ansi));
22336
22385
  return ExitCode.Ok;
22337
22386
  });
@@ -22653,8 +22702,7 @@ var SidecarRefreshCommand = class extends SmCommand {
22653
22702
  async run() {
22654
22703
  const ctx = defaultRuntimeContext();
22655
22704
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...ctx });
22656
- const stdout = this.context.stdout;
22657
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
22705
+ const ansi = this.ansiFor("stdout");
22658
22706
  const okGlyph = ansi.green("\u2713");
22659
22707
  const errGlyph = ansi.red("\u2715");
22660
22708
  return runWithSidecarConsent(
@@ -22801,8 +22849,7 @@ var SidecarPruneCommand = class extends SmCommand {
22801
22849
  async run() {
22802
22850
  const ctx = defaultRuntimeContext();
22803
22851
  const orphans = discoverOrphanSidecars([ctx.cwd]);
22804
- const stdout = this.context.stdout;
22805
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
22852
+ const ansi = this.ansiFor("stdout");
22806
22853
  const okGlyph = ansi.green("\u2713");
22807
22854
  const warnGlyph = ansi.yellow("\u26A0");
22808
22855
  const infoGlyph = ansi.cyan("\u2139");
@@ -22937,8 +22984,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
22937
22984
  async run() {
22938
22985
  const ctx = defaultRuntimeContext();
22939
22986
  const dbPath = resolveDbPath({ global: this.global, db: this.db, ...ctx });
22940
- const stdout = this.context.stdout;
22941
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
22987
+ const ansi = this.ansiFor("stdout");
22942
22988
  const errGlyph = ansi.red("\u2715");
22943
22989
  return runWithSidecarConsent(
22944
22990
  {
@@ -23082,8 +23128,7 @@ function planned(description) {
23082
23128
  var StubCommand = class extends SmCommand {
23083
23129
  emitElapsed = false;
23084
23130
  async run() {
23085
- const stderr = this.context.stderr;
23086
- const ansi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
23131
+ const ansi = this.ansiFor("stderr");
23087
23132
  this.printer.error(
23088
23133
  tx(STUBS_TEXTS.notImplemented, {
23089
23134
  glyph: ansi.yellow("\u22EF"),
@@ -23301,7 +23346,7 @@ var TutorialCommand = class extends SmCommand {
23301
23346
  const ctx = defaultRuntimeContext();
23302
23347
  const target = join17(ctx.cwd, SM_TUTORIAL_FILENAME);
23303
23348
  const stderr = this.context.stderr;
23304
- const stderrAnsi = ansiFor({ isTTY: stderr.isTTY === true, noColorFlag: this.noColor });
23349
+ const stderrAnsi = this.ansiFor("stderr");
23305
23350
  const errGlyph = stderrAnsi.red("\u2715");
23306
23351
  if (await pathExists(target) && !this.force) {
23307
23352
  this.printer.error(
@@ -23342,8 +23387,7 @@ var TutorialCommand = class extends SmCommand {
23342
23387
  env: process.env
23343
23388
  });
23344
23389
  this.printer.info(renderLogoBlock({ version: VERSION, colorEnabled }));
23345
- const stdout = this.context.stdout;
23346
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
23390
+ const ansi = this.ansiFor("stdout");
23347
23391
  this.printer.data(
23348
23392
  tx(TUTORIAL_TEXTS.written, {
23349
23393
  glyph: ansi.green("\u2713"),
@@ -23429,8 +23473,7 @@ var VersionCommand = class extends SmCommand {
23429
23473
  ["runtime", runtime],
23430
23474
  ["db-schema", dbSchema]
23431
23475
  ];
23432
- const stdout = this.context.stdout;
23433
- const ansi = ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: this.noColor });
23476
+ const ansi = this.ansiFor("stdout");
23434
23477
  const pad = Math.max(...lines.map(([k]) => k.length));
23435
23478
  for (const [k, v] of lines) {
23436
23479
  this.printer.data(
@@ -23458,7 +23501,8 @@ async function resolveDbSchemaVersion() {
23458
23501
  );
23459
23502
  if (v === null || v === void 0) return "-";
23460
23503
  return String(v);
23461
- } catch {
23504
+ } catch (error) {
23505
+ log.debug(`sm version: dbSchema read failed: ${error instanceof Error ? error.message : String(error)}`);
23462
23506
  return "-";
23463
23507
  }
23464
23508
  }