@skill-map/cli 0.37.0 → 0.38.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
@@ -1,5 +1,5 @@
1
1
  // cli/entry.ts
2
- import { existsSync as existsSync33 } from "fs";
2
+ import { existsSync as existsSync30 } from "fs";
3
3
  import { Builtins, Cli as Cli2 } from "clipanion";
4
4
 
5
5
  // kernel/adapters/in-memory-progress.ts
@@ -61,7 +61,7 @@ var DuplicateExtensionError = class extends Error {
61
61
  }
62
62
  };
63
63
  var Registry = class {
64
- /** kind → qualifiedId → Extension. */
64
+ /** kind → qualifiedId → IExtension. */
65
65
  #byKind;
66
66
  constructor() {
67
67
  this.#byKind = new Map(
@@ -429,10 +429,17 @@ var command_schema_default = {
429
429
  properties: {}
430
430
  };
431
431
 
432
+ // plugins/ids.ts
433
+ var CORE_PLUGIN_ID = "core";
434
+ var CLAUDE_PLUGIN_ID = "claude";
435
+ var OPENAI_PLUGIN_ID = "openai";
436
+ var ANTIGRAVITY_PLUGIN_ID = "antigravity";
437
+ var AGENT_SKILLS_PLUGIN_ID = "agent-skills";
438
+
432
439
  // plugins/claude/providers/claude/index.ts
433
440
  var claudeProvider = {
434
441
  id: "claude",
435
- pluginId: "claude",
442
+ pluginId: CLAUDE_PLUGIN_ID,
436
443
  kind: "provider",
437
444
  version: "1.0.0",
438
445
  description: "Walks Claude Code scope conventions (.claude/{agents,commands,skills}).",
@@ -657,9 +664,9 @@ function stripFences(input) {
657
664
  }
658
665
  continue;
659
666
  }
660
- const open = FENCE_RE.exec(line);
661
- if (open?.groups) {
662
- openFence = open.groups["fence"];
667
+ const open2 = FENCE_RE.exec(line);
668
+ if (open2?.groups) {
669
+ openFence = open2.groups["fence"];
663
670
  out.push(blank(line));
664
671
  continue;
665
672
  }
@@ -717,7 +724,7 @@ var AT_RE = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-
717
724
  var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
718
725
  var atDirectiveExtractor = {
719
726
  id: ID,
720
- pluginId: "claude",
727
+ pluginId: CLAUDE_PLUGIN_ID,
721
728
  kind: "extractor",
722
729
  version: "1.0.0",
723
730
  description: "Detects `@<token>` directives in a node's body using Claude Code interpretation rules. A bare handle (e.g. `@team`) becomes a `mentions` link; a file-flavoured token (e.g. `@docs/api.md`, `@./readme.md`) becomes a `references` link. Gated by `precondition.provider: ['claude']` so Antigravity / Cursor / Codex apply their own at-directive flavours via their own extractors.",
@@ -806,7 +813,7 @@ var ID2 = "slash";
806
813
  var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
807
814
  var slashExtractor = {
808
815
  id: ID2,
809
- pluginId: "claude",
816
+ pluginId: CLAUDE_PLUGIN_ID,
810
817
  kind: "extractor",
811
818
  version: "1.0.0",
812
819
  description: "Detects `/command` invocations in a node's body using Claude Code routing rules and turns each one into an arrow between nodes in the graph. Gated by `precondition.provider: ['claude']` so Antigravity / Cursor / Codex apply their own slash flavours (Antigravity ships subagent and skill panels, Codex deprecated user slash commands, etc.) via their own extractors.",
@@ -856,7 +863,7 @@ var slashExtractor = {
856
863
  // plugins/antigravity/providers/antigravity/index.ts
857
864
  var antigravityProvider = {
858
865
  id: "antigravity",
859
- pluginId: "antigravity",
866
+ pluginId: ANTIGRAVITY_PLUGIN_ID,
860
867
  kind: "provider",
861
868
  version: "1.0.0",
862
869
  description: "Google Antigravity CLI. Replaces the retired Gemini CLI; skills route through the neutral `agent-skills` Provider via `.agents/skills/`. This Provider contributes lens identity and a reserved-name seed catalog.",
@@ -1005,7 +1012,7 @@ var agent_schema_default2 = {
1005
1012
  // plugins/openai/providers/openai/index.ts
1006
1013
  var openaiProvider = {
1007
1014
  id: "openai",
1008
- pluginId: "openai",
1015
+ pluginId: OPENAI_PLUGIN_ID,
1009
1016
  kind: "provider",
1010
1017
  version: "1.0.0",
1011
1018
  description: "Walks OpenAI Codex CLI scope conventions (.codex/agents/*.toml).",
@@ -1063,7 +1070,7 @@ var skill_schema_default2 = {
1063
1070
  // plugins/agent-skills/providers/agent-skills/index.ts
1064
1071
  var agentSkillsProvider = {
1065
1072
  id: "agent-skills",
1066
- pluginId: "agent-skills",
1073
+ pluginId: AGENT_SKILLS_PLUGIN_ID,
1067
1074
  kind: "provider",
1068
1075
  version: "1.0.0",
1069
1076
  description: "Agent Skills open standard. Vendor-neutral path `.agents/skills/<name>/SKILL.md` (Anthropic, OpenAI, Google). See agentskills.io.",
@@ -1111,7 +1118,7 @@ var markdown_schema_default = {
1111
1118
  // plugins/core/providers/core-markdown/index.ts
1112
1119
  var coreMarkdownProvider = {
1113
1120
  id: "markdown",
1114
- pluginId: "core",
1121
+ pluginId: CORE_PLUGIN_ID,
1115
1122
  kind: "provider",
1116
1123
  version: "1.0.0",
1117
1124
  description: "Universal `.md` fallback. Claims any markdown file no vendor-specific Provider classifies.",
@@ -1162,7 +1169,7 @@ var coreMarkdownProvider = {
1162
1169
  var ID3 = "annotations";
1163
1170
  var annotationsExtractor = {
1164
1171
  id: ID3,
1165
- pluginId: "core",
1172
+ pluginId: CORE_PLUGIN_ID,
1166
1173
  kind: "extractor",
1167
1174
  version: "1.0.0",
1168
1175
  description: "Turns the `supersedes` and `supersededBy` entries you write in a node's `.sm` sidecar into the arrows (edges) shown between nodes in the graph.",
@@ -1224,7 +1231,7 @@ var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
1224
1231
  var TRAILING_PUNCT = /[.,;:!?]+$/;
1225
1232
  var externalUrlCounterExtractor = {
1226
1233
  id: ID4,
1227
- pluginId: "core",
1234
+ pluginId: CORE_PLUGIN_ID,
1228
1235
  kind: "extractor",
1229
1236
  version: "1.0.0",
1230
1237
  description: "Counts the distinct external URLs in a node's body and shows the total on the card.",
@@ -1313,7 +1320,7 @@ var LINK_RE = /(?<!!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
1313
1320
  var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
1314
1321
  var markdownLinkExtractor = {
1315
1322
  id: ID5,
1316
- pluginId: "core",
1323
+ pluginId: CORE_PLUGIN_ID,
1317
1324
  kind: "extractor",
1318
1325
  version: "1.0.0",
1319
1326
  description: "Detects markdown links (`[text](path)`) in a node's body and turns each one into an arrow between nodes in the graph.",
@@ -1375,7 +1382,7 @@ var ID6 = "mcp-tools";
1375
1382
  var MCP_PATTERN = /^mcp__([a-z0-9][a-z0-9_-]*)__[a-z0-9_-]+$/i;
1376
1383
  var mcpToolsExtractor = {
1377
1384
  id: ID6,
1378
- pluginId: "core",
1385
+ pluginId: CORE_PLUGIN_ID,
1379
1386
  kind: "extractor",
1380
1387
  version: "1.0.0",
1381
1388
  description: "Detects `tools: [mcp__<server>__<tool>]` entries in a node's frontmatter and turns each unique server into an MCP node + a reference edge from the source.",
@@ -1437,7 +1444,7 @@ var ID7 = "tools-count";
1437
1444
  var TOOLTIP_MAX = 255;
1438
1445
  var toolsCountExtractor = {
1439
1446
  id: ID7,
1440
- pluginId: "core",
1447
+ pluginId: CORE_PLUGIN_ID,
1441
1448
  kind: "extractor",
1442
1449
  version: "1.0.0",
1443
1450
  description: "Counts the tools an agent declares in its frontmatter and shows the total on the agent card.",
@@ -1482,7 +1489,7 @@ var ANNOTATION_ORPHAN_TEXTS = {
1482
1489
  var ID8 = "annotation-orphan";
1483
1490
  var annotationOrphanAnalyzer = {
1484
1491
  id: ID8,
1485
- pluginId: "core",
1492
+ pluginId: CORE_PLUGIN_ID,
1486
1493
  kind: "analyzer",
1487
1494
  version: "1.0.0",
1488
1495
  description: "Detects and flags sidecars (`.sm`) whose `.md` no longer exists.",
@@ -1534,7 +1541,7 @@ var ANNOTATION_STALE_TEXTS = {
1534
1541
  var ID9 = "annotation-stale";
1535
1542
  var annotationStaleAnalyzer = {
1536
1543
  id: ID9,
1537
- pluginId: "core",
1544
+ pluginId: CORE_PLUGIN_ID,
1538
1545
  kind: "analyzer",
1539
1546
  version: "1.0.0",
1540
1547
  description: "Detects and marks sidecars (`.sm`) out of date of their `.md`.",
@@ -1617,7 +1624,7 @@ var BROKEN_REF_TEXTS = {
1617
1624
  var ID10 = "broken-ref";
1618
1625
  var brokenRefAnalyzer = {
1619
1626
  id: ID10,
1620
- pluginId: "core",
1627
+ pluginId: CORE_PLUGIN_ID,
1621
1628
  kind: "analyzer",
1622
1629
  version: "1.0.0",
1623
1630
  description: "Detects and flags arrows pointing at a node not part of the current scan.",
@@ -1782,7 +1789,7 @@ function isPathStyleLink(link) {
1782
1789
  var ID11 = "contribution-orphan";
1783
1790
  var contributionOrphanAnalyzer = {
1784
1791
  id: ID11,
1785
- pluginId: "core",
1792
+ pluginId: CORE_PLUGIN_ID,
1786
1793
  kind: "analyzer",
1787
1794
  version: "0.0.0",
1788
1795
  description: "Detects and warns about plugin data referencing nodes renamed or deleted in the latest scan.",
@@ -1805,7 +1812,7 @@ var JOB_ORPHAN_FILE_TEXTS = {
1805
1812
  var ID12 = "job-orphan-file";
1806
1813
  var jobOrphanFileAnalyzer = {
1807
1814
  id: ID12,
1808
- pluginId: "core",
1815
+ pluginId: CORE_PLUGIN_ID,
1809
1816
  kind: "analyzer",
1810
1817
  version: "1.0.0",
1811
1818
  description: "Detects and flags leftover job result files (no live job references them). Cleanup via `sm job prune --orphan-files`.",
@@ -1954,7 +1961,7 @@ function resolveLinkTargetToPath(link, nameIndex) {
1954
1961
  var ID14 = "link-counts";
1955
1962
  var linkCountsAnalyzer = {
1956
1963
  id: ID14,
1957
- pluginId: "core",
1964
+ pluginId: CORE_PLUGIN_ID,
1958
1965
  kind: "analyzer",
1959
1966
  version: "1.0.0",
1960
1967
  description: "Counts incoming and outgoing links per node.",
@@ -2037,7 +2044,7 @@ var REDUNDANT_TARGET_REFERENCE_TEXTS = {
2037
2044
  var ID15 = "redundant-target-reference";
2038
2045
  var redundantTargetReferenceAnalyzer = {
2039
2046
  id: ID15,
2040
- pluginId: "core",
2047
+ pluginId: CORE_PLUGIN_ID,
2041
2048
  kind: "analyzer",
2042
2049
  version: "1.0.0",
2043
2050
  description: "Flags when one node references the same resolved target via two or more syntactic surfaces (cross-extractor multi-form OR cross-kind multi-edge). Emits a warn on the source listing every occurrence (kind + trigger + line). Helps consolidate authorial redundancy and avoid duplicate runtime inlining.",
@@ -2288,7 +2295,7 @@ var RESERVED_NAME_TEXTS = {
2288
2295
  var ID16 = "reserved-name";
2289
2296
  var reservedNameAnalyzer = {
2290
2297
  id: ID16,
2291
- pluginId: "core",
2298
+ pluginId: CORE_PLUGIN_ID,
2292
2299
  kind: "analyzer",
2293
2300
  version: "1.0.0",
2294
2301
  description: "Flags reserved-name collisions on two surfaces. Target side: a user file whose name collides with a Provider runtime's built-in invocable (the runtime shadows the file silently). Source side: a link that resolves to a reserved name, which the post-walk lift transform downgrades to the sentinel `RESERVED_TARGET_CONFIDENCE` (0.1). The two findings share the analyzer id so consumers can group by root cause; the source-side issue carries `data.target` matching the link so UIs can correlate per-row.",
@@ -2397,7 +2404,7 @@ var SELF_LOOP_TEXTS = {
2397
2404
  var ID17 = "self-loop";
2398
2405
  var selfLoopAnalyzer = {
2399
2406
  id: ID17,
2400
- pluginId: "core",
2407
+ pluginId: CORE_PLUGIN_ID,
2401
2408
  kind: "analyzer",
2402
2409
  version: "1.0.0",
2403
2410
  description: "Flags links whose source is its own resolved target (a body heading like `# /deploy` inside the file that defines `/deploy`). One warn per self-looping link, attached to the source. UI consumers hide self-loops by default; this analyzer is the authoritative detector.",
@@ -2474,7 +2481,7 @@ var SIGNAL_COLLISION_TEXTS = {
2474
2481
  var ID18 = "signal-collision";
2475
2482
  var signalCollisionAnalyzer = {
2476
2483
  id: ID18,
2477
- pluginId: "core",
2484
+ pluginId: CORE_PLUGIN_ID,
2478
2485
  kind: "analyzer",
2479
2486
  version: "1.0.0",
2480
2487
  description: "Surfaces Signal IR resolver rejections (range-overlap losers, disabled extractors, below-floor candidates) as warn issues attached to the Signal's source node.",
@@ -2576,7 +2583,7 @@ var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
2576
2583
  var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
2577
2584
  var stabilityAnalyzer = {
2578
2585
  id: ID19,
2579
- pluginId: "core",
2586
+ pluginId: CORE_PLUGIN_ID,
2580
2587
  kind: "analyzer",
2581
2588
  version: "1.0.0",
2582
2589
  description: "Reports node lifecycle stage (`experimental`, `deprecated`) on the card.",
@@ -2657,7 +2664,7 @@ var SUPERSEDED_TEXTS = {
2657
2664
  var ID20 = "superseded";
2658
2665
  var supersededAnalyzer = {
2659
2666
  id: ID20,
2660
- pluginId: "core",
2667
+ pluginId: CORE_PLUGIN_ID,
2661
2668
  kind: "analyzer",
2662
2669
  version: "1.0.0",
2663
2670
  description: "Detects and marks nodes replaced by a newer one via `supersededBy`.",
@@ -2725,7 +2732,7 @@ var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
2725
2732
  ]);
2726
2733
  var triggerCollisionAnalyzer = {
2727
2734
  id: ID21,
2728
- pluginId: "core",
2735
+ pluginId: CORE_PLUGIN_ID,
2729
2736
  kind: "analyzer",
2730
2737
  mode: "deterministic",
2731
2738
  version: "1.0.0",
@@ -2867,7 +2874,7 @@ var ID22 = "unknown-field";
2867
2874
  var RESERVED_ROOT_BLOCKS = /* @__PURE__ */ new Set(["identity", "annotations", "settings", "audit"]);
2868
2875
  var unknownFieldAnalyzer = {
2869
2876
  id: ID22,
2870
- pluginId: "core",
2877
+ pluginId: CORE_PLUGIN_ID,
2871
2878
  kind: "analyzer",
2872
2879
  version: "1.0.0",
2873
2880
  description: "Detects and flags typos or unrecognized keys in sidecars (`.sm`).",
@@ -3263,7 +3270,7 @@ var VALIDATE_ALL_TEXTS = {
3263
3270
  var ID23 = "validate-all";
3264
3271
  var validateAllAnalyzer = {
3265
3272
  id: ID23,
3266
- pluginId: "core",
3273
+ pluginId: CORE_PLUGIN_ID,
3267
3274
  kind: "analyzer",
3268
3275
  version: "1.0.0",
3269
3276
  description: "Detects and flags nodes or links violating the project schemas.",
@@ -3438,7 +3445,7 @@ var ID24 = "ascii";
3438
3445
  var KIND_ORDER = ["agent", "command", "skill", "markdown"];
3439
3446
  var asciiFormatter = {
3440
3447
  id: ID24,
3441
- pluginId: "core",
3448
+ pluginId: CORE_PLUGIN_ID,
3442
3449
  kind: "formatter",
3443
3450
  formatId: ID24,
3444
3451
  version: "1.0.0",
@@ -3538,7 +3545,7 @@ function renderSection(out, kind, group) {
3538
3545
  var ID25 = "json";
3539
3546
  var jsonFormatter = {
3540
3547
  id: ID25,
3541
- pluginId: "core",
3548
+ pluginId: CORE_PLUGIN_ID,
3542
3549
  kind: "formatter",
3543
3550
  version: "1.0.0",
3544
3551
  description: "Renders the persisted scan as JSON (conforms to `scan-result.schema.json` when the full ScanResult is available). Used by `sm graph --format json` and `GET /api/graph?format=json`.",
@@ -3682,10 +3689,9 @@ function resolveSpecRoot2() {
3682
3689
 
3683
3690
  // plugins/core/actions/bump/index.ts
3684
3691
  var ID26 = "bump";
3685
- var PLUGIN_ID = "core";
3686
3692
  var bumpAction = {
3687
3693
  id: ID26,
3688
- pluginId: PLUGIN_ID,
3694
+ pluginId: CORE_PLUGIN_ID,
3689
3695
  kind: "action",
3690
3696
  version: "1.0.0",
3691
3697
  description: "Marks a node as updated: bumps version, refreshes sidecar hashes, records the timestamp.",
@@ -3744,10 +3750,9 @@ function pickCurrentVersion(overlay) {
3744
3750
 
3745
3751
  // plugins/core/actions/mark-superseded/index.ts
3746
3752
  var ID27 = "mark-superseded";
3747
- var PLUGIN_ID2 = "core";
3748
3753
  var markSupersededAction = {
3749
3754
  id: ID27,
3750
- pluginId: PLUGIN_ID2,
3755
+ pluginId: CORE_PLUGIN_ID,
3751
3756
  kind: "action",
3752
3757
  version: "0.0.0",
3753
3758
  description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar). Paired with the `core/superseded` analyzer.",
@@ -3856,7 +3861,7 @@ var UPDATE_CHECK_TEXTS = {
3856
3861
  // package.json
3857
3862
  var package_default = {
3858
3863
  name: "@skill-map/cli",
3859
- version: "0.37.0",
3864
+ version: "0.38.0",
3860
3865
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
3861
3866
  license: "MIT",
3862
3867
  type: "module",
@@ -3939,7 +3944,7 @@ var package_default = {
3939
3944
  semver: "7.7.4",
3940
3945
  "smol-toml": "1.6.1",
3941
3946
  typanion: "3.14.0",
3942
- ws: "8.20.0"
3947
+ ws: "8.21.0"
3943
3948
  },
3944
3949
  devDependencies: {
3945
3950
  "@eslint/js": "10.0.1",
@@ -4006,10 +4011,67 @@ function ansiFor(opts) {
4006
4011
  }
4007
4012
 
4008
4013
  // cli/util/user-settings-store.ts
4009
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
4014
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4010
4015
  import { homedir } from "os";
4011
4016
  import { join as join2 } from "path";
4012
4017
 
4018
+ // core/config/atomic-write.ts
4019
+ import {
4020
+ closeSync,
4021
+ constants as fsConstants,
4022
+ existsSync as existsSync2,
4023
+ mkdirSync,
4024
+ openSync,
4025
+ readFileSync as readFileSync4,
4026
+ renameSync,
4027
+ unlinkSync,
4028
+ writeSync
4029
+ } from "fs";
4030
+ import { randomBytes } from "crypto";
4031
+ import { dirname as dirname4 } from "path";
4032
+ function readJsonObjectOrEmpty(path) {
4033
+ if (!existsSync2(path)) return {};
4034
+ try {
4035
+ const raw = JSON.parse(readFileSync4(path, "utf8"));
4036
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
4037
+ return raw;
4038
+ }
4039
+ } catch {
4040
+ }
4041
+ return {};
4042
+ }
4043
+ function writeFileAtomicExclusive(path, content, mode = 384) {
4044
+ const tmp = `${path}.tmp.${process.pid}.${randomBytes(8).toString("hex")}`;
4045
+ let fd = null;
4046
+ try {
4047
+ fd = openSync(
4048
+ tmp,
4049
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
4050
+ mode
4051
+ );
4052
+ writeSync(fd, content);
4053
+ closeSync(fd);
4054
+ fd = null;
4055
+ renameSync(tmp, path);
4056
+ } catch (err) {
4057
+ if (fd !== null) {
4058
+ try {
4059
+ closeSync(fd);
4060
+ } catch {
4061
+ }
4062
+ }
4063
+ try {
4064
+ unlinkSync(tmp);
4065
+ } catch {
4066
+ }
4067
+ throw err;
4068
+ }
4069
+ }
4070
+ function writeJsonAtomic(path, content) {
4071
+ mkdirSync(dirname4(path), { recursive: true });
4072
+ writeFileAtomicExclusive(path, JSON.stringify(content, null, 2) + "\n");
4073
+ }
4074
+
4013
4075
  // core/paths/db-path.ts
4014
4076
  import { join, resolve as resolve6 } from "path";
4015
4077
  var SKILL_MAP_DIR = ".skill-map";
@@ -4067,9 +4129,9 @@ function readUserSettings() {
4067
4129
  }
4068
4130
  function readParsedFile() {
4069
4131
  const path = userSettingsFilePath();
4070
- if (!existsSync2(path)) return null;
4132
+ if (!existsSync3(path)) return null;
4071
4133
  try {
4072
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
4134
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
4073
4135
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4074
4136
  return null;
4075
4137
  }
@@ -4102,8 +4164,8 @@ function writeUserSettings(patch) {
4102
4164
  const result = validators.validate("user-settings", merged);
4103
4165
  if (!result.ok) return;
4104
4166
  }
4105
- mkdirSync(dir, { recursive: true });
4106
- writeFileSync(path, JSON.stringify(merged, null, 2) + "\n");
4167
+ mkdirSync2(dir, { recursive: true, mode: 448 });
4168
+ writeFileAtomicExclusive(path, JSON.stringify(merged, null, 2) + "\n");
4107
4169
  } catch {
4108
4170
  }
4109
4171
  }
@@ -4214,7 +4276,7 @@ ${footer}
4214
4276
  // plugins/core/hooks/update-check/index.ts
4215
4277
  var updateCheckHook = {
4216
4278
  id: "update-check",
4217
- pluginId: "core",
4279
+ pluginId: CORE_PLUGIN_ID,
4218
4280
  kind: "hook",
4219
4281
  version: "1.0.0",
4220
4282
  description: "Checks daily for a newer skill-map version on npm. Shows an `update available` banner when one is found.",
@@ -4546,7 +4608,7 @@ function extractLogLevelFlag(argv) {
4546
4608
  var LOGGER_ENV_VAR = ENV_VAR;
4547
4609
 
4548
4610
  // cli/util/db-path.ts
4549
- import { existsSync as existsSync3 } from "fs";
4611
+ import { existsSync as existsSync4 } from "fs";
4550
4612
 
4551
4613
  // cli/i18n/util.texts.ts
4552
4614
  var UTIL_TEXTS = {
@@ -4589,7 +4651,7 @@ var ExitCode = {
4589
4651
 
4590
4652
  // cli/util/db-path.ts
4591
4653
  function assertDbExists(path, stderr) {
4592
- if (path === ":memory:" || existsSync3(path)) return true;
4654
+ if (path === ":memory:" || existsSync4(path)) return true;
4593
4655
  const stderrTty = stderr;
4594
4656
  const ansi = ansiFor({ isTTY: stderrTty.isTTY === true, noColorFlag: false });
4595
4657
  stderr.write(
@@ -4784,10 +4846,10 @@ import { Command as Command2, Option as Option2 } from "clipanion";
4784
4846
 
4785
4847
  // core/config/helper.ts
4786
4848
  import { homedir as osHomedir } from "os";
4787
- import { isAbsolute, join as join4, resolve as resolve7 } from "path";
4849
+ import { isAbsolute, join as join4, resolve as resolve7, sep } from "path";
4788
4850
 
4789
4851
  // kernel/config/loader.ts
4790
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
4852
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
4791
4853
 
4792
4854
  // kernel/i18n/config-loader.texts.ts
4793
4855
  var CONFIG_LOADER_TEXTS = {
@@ -4869,7 +4931,7 @@ function loadConfig(opts) {
4869
4931
  { path: kernelLocalSettingsPath(cwd), layer: "project-local" }
4870
4932
  ];
4871
4933
  for (const { path, layer } of filePairs) {
4872
- if (!existsSync4(path)) continue;
4934
+ if (!existsSync5(path)) continue;
4873
4935
  const partial = readJsonSafe(path, layer, warnings, strict);
4874
4936
  if (partial === null) continue;
4875
4937
  const cleaned = validateAndStrip(validators, partial, layer, warnings, strict);
@@ -4890,7 +4952,7 @@ function loadConfig(opts) {
4890
4952
  function readJsonSafe(path, layer, warnings, strict) {
4891
4953
  let text;
4892
4954
  try {
4893
- text = readFileSync5(path, "utf8");
4955
+ text = readFileSync6(path, "utf8");
4894
4956
  } catch (err) {
4895
4957
  return reportAndSkip(
4896
4958
  tx(CONFIG_LOADER_TEXTS.readFailure, { layer, path, message: formatErrorMessage(err) }),
@@ -5132,63 +5194,6 @@ function enumerateConfigPaths(obj, prefix = "") {
5132
5194
  return out;
5133
5195
  }
5134
5196
 
5135
- // core/config/atomic-write.ts
5136
- import {
5137
- closeSync,
5138
- constants as fsConstants,
5139
- existsSync as existsSync5,
5140
- mkdirSync as mkdirSync2,
5141
- openSync,
5142
- readFileSync as readFileSync6,
5143
- renameSync,
5144
- unlinkSync,
5145
- writeSync
5146
- } from "fs";
5147
- import { randomBytes } from "crypto";
5148
- import { dirname as dirname4 } from "path";
5149
- function readJsonObjectOrEmpty(path) {
5150
- if (!existsSync5(path)) return {};
5151
- try {
5152
- const raw = JSON.parse(readFileSync6(path, "utf8"));
5153
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
5154
- return raw;
5155
- }
5156
- } catch {
5157
- }
5158
- return {};
5159
- }
5160
- function writeFileAtomicExclusive(path, content, mode = 384) {
5161
- const tmp = `${path}.tmp.${process.pid}.${randomBytes(8).toString("hex")}`;
5162
- let fd = null;
5163
- try {
5164
- fd = openSync(
5165
- tmp,
5166
- fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
5167
- mode
5168
- );
5169
- writeSync(fd, content);
5170
- closeSync(fd);
5171
- fd = null;
5172
- renameSync(tmp, path);
5173
- } catch (err) {
5174
- if (fd !== null) {
5175
- try {
5176
- closeSync(fd);
5177
- } catch {
5178
- }
5179
- }
5180
- try {
5181
- unlinkSync(tmp);
5182
- } catch {
5183
- }
5184
- throw err;
5185
- }
5186
- }
5187
- function writeJsonAtomic(path, content) {
5188
- mkdirSync2(dirname4(path), { recursive: true });
5189
- writeFileAtomicExclusive(path, JSON.stringify(content, null, 2) + "\n");
5190
- }
5191
-
5192
5197
  // core/config/helper.ts
5193
5198
  var PRIVACY_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
5194
5199
  "scan.referencePaths"
@@ -5282,7 +5287,7 @@ function resolveScanPathForExposure(raw, cwd) {
5282
5287
  }
5283
5288
  function isUnderProject(absPath, cwd) {
5284
5289
  const projectRoot = resolve7(cwd);
5285
- return absPath === projectRoot || absPath.startsWith(`${projectRoot}/`);
5290
+ return absPath === projectRoot || absPath.startsWith(`${projectRoot}${sep}`);
5286
5291
  }
5287
5292
 
5288
5293
  // core/config/sidecar-consent.ts
@@ -5674,7 +5679,11 @@ var SmCommand = class extends Command {
5674
5679
  this.printer = createPrinter({
5675
5680
  stdout: this.context.stdout,
5676
5681
  stderr: this.context.stderr,
5677
- quietInfo: this.quiet
5682
+ // `--json` suppresses info banners even on stderr: users piping
5683
+ // JSON through `jq` (or asserting machine output in tests) don't
5684
+ // want decorative lines polluting either channel. Aligns CLI
5685
+ // behaviour with the printer docstring.
5686
+ quietInfo: this.quiet || this.json
5678
5687
  });
5679
5688
  try {
5680
5689
  return await this.run();
@@ -7237,9 +7246,9 @@ async function sweepPerTupleContributions(trx, contributions, freshlyRunTuples)
7237
7246
  const bufferKeys = buildContributionsBufferKeys(contributions);
7238
7247
  const tuplesByPluginExt = groupFreshlyRunTuplesByPluginExt(freshlyRunTuples);
7239
7248
  for (const [pe, nodes] of tuplesByPluginExt) {
7240
- const sep6 = pe.indexOf("\0");
7241
- if (sep6 < 0) continue;
7242
- await deleteStaleTupleRows(trx, pe.slice(0, sep6), pe.slice(sep6 + 1), [...nodes], bufferKeys);
7249
+ const sep7 = pe.indexOf("\0");
7250
+ if (sep7 < 0) continue;
7251
+ await deleteStaleTupleRows(trx, pe.slice(0, sep7), pe.slice(sep7 + 1), [...nodes], bufferKeys);
7243
7252
  }
7244
7253
  }
7245
7254
  function buildContributionsBufferKeys(contributions) {
@@ -8018,8 +8027,24 @@ function applyIssueFilters(query, filter) {
8018
8027
  )
8019
8028
  );
8020
8029
  }
8030
+ if (filter.nodePaths !== void 0) {
8031
+ q = applyNodePathsFilter(q, filter.nodePaths);
8032
+ }
8021
8033
  return q;
8022
8034
  }
8035
+ function applyNodePathsFilter(query, nodePaths) {
8036
+ if (nodePaths.length === 0) {
8037
+ return query.where(sql3`0`, "=", 1);
8038
+ }
8039
+ const targets = [...nodePaths];
8040
+ return query.where(
8041
+ ({ exists, selectFrom }) => exists(
8042
+ selectFrom(
8043
+ sql3`json_each(scan_issues.node_ids_json)`.as("je")
8044
+ ).select(sql3`1`.as("one")).where(sql3.ref("je.value"), "in", targets)
8045
+ )
8046
+ );
8047
+ }
8023
8048
  async function findActiveIssues(db, predicate) {
8024
8049
  const rows = await db.selectFrom("scan_issues").selectAll().execute();
8025
8050
  const out = [];
@@ -8308,15 +8333,32 @@ async function tryWithSqlite(options, fn) {
8308
8333
  import { resolve as resolve14 } from "path";
8309
8334
 
8310
8335
  // core/paths/path-guard.ts
8311
- import { isAbsolute as isAbsolute2, resolve as resolve13, sep } from "path";
8336
+ import { lstatSync } from "fs";
8337
+ import { isAbsolute as isAbsolute2, resolve as resolve13, sep as sep2 } from "path";
8312
8338
  function assertContained(cwd, rel) {
8313
8339
  if (isAbsolute2(rel)) {
8314
8340
  throw new Error(`node path is absolute, refusing to read: ${rel}`);
8315
8341
  }
8316
8342
  const abs = resolve13(cwd, rel);
8317
- if (abs !== cwd && !abs.startsWith(cwd + sep)) {
8343
+ if (abs !== cwd && !abs.startsWith(cwd + sep2)) {
8318
8344
  throw new Error(`node path escapes repo root: ${rel}`);
8319
8345
  }
8346
+ let isSymlink;
8347
+ try {
8348
+ isSymlink = lstatSync(abs).isSymbolicLink();
8349
+ } catch (err) {
8350
+ if (isAllowedLstatError(err)) return;
8351
+ throw err;
8352
+ }
8353
+ if (isSymlink) {
8354
+ throw new Error(`node path is a symlink, refusing to dereference: ${rel}`);
8355
+ }
8356
+ }
8357
+ var ALLOWED_LSTAT_ERROR_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
8358
+ function isAllowedLstatError(err) {
8359
+ if (err === null || typeof err !== "object") return false;
8360
+ const code = err.code;
8361
+ return typeof code === "string" && ALLOWED_LSTAT_ERROR_CODES.has(code);
8320
8362
  }
8321
8363
 
8322
8364
  // cli/commands/bump-plan.ts
@@ -9259,9 +9301,9 @@ function providerKindFailure(opts, status, fileName, errDescription) {
9259
9301
  }
9260
9302
  };
9261
9303
  }
9262
- function isDirectorySafe(path, statSync13) {
9304
+ function isDirectorySafe(path, statSync12) {
9263
9305
  try {
9264
- return statSync13(path).isDirectory();
9306
+ return statSync12(path).isDirectory();
9265
9307
  } catch {
9266
9308
  return false;
9267
9309
  }
@@ -9855,7 +9897,7 @@ async function buildEnabledResolver(ctx) {
9855
9897
 
9856
9898
  // kernel/scan/walk-content.ts
9857
9899
  import { readFile, readdir, lstat } from "fs/promises";
9858
- import { join as join9, relative as relative2, sep as sep2 } from "path";
9900
+ import { join as join9, relative as relative2, sep as sep3 } from "path";
9859
9901
 
9860
9902
  // kernel/scan/ignore.ts
9861
9903
  import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
@@ -10036,7 +10078,7 @@ async function* walkContent(roots, options) {
10036
10078
  const extensions = options.extensions;
10037
10079
  for (const root of roots) {
10038
10080
  for await (const file of walkRoot(root, root, filter, extensions)) {
10039
- const relPath = relative2(root, file).split(sep2).join("/");
10081
+ const relPath = relative2(root, file).split(sep3).join("/");
10040
10082
  let raw;
10041
10083
  try {
10042
10084
  raw = await readFile(file, "utf8");
@@ -10068,7 +10110,7 @@ async function* walkRoot(root, current, filter, extensions) {
10068
10110
  for (const entry of entries) {
10069
10111
  const name = entry.name;
10070
10112
  const full = join9(current, name);
10071
- const rel = relative2(root, full).split(sep2).join("/");
10113
+ const rel = relative2(root, full).split(sep3).join("/");
10072
10114
  if (filter.ignores(rel)) continue;
10073
10115
  if (entry.isSymbolicLink()) continue;
10074
10116
  if (entry.isDirectory()) {
@@ -11489,6 +11531,52 @@ function disableEnv(setup) {
11489
11531
  if (setup?.disableAllAnalyzers) env["SKILL_MAP_DISABLE_ALL_ANALYZERS"] = "1";
11490
11532
  return env;
11491
11533
  }
11534
+ var SAFE_CONFORMANCE_ENV_KEYS = [
11535
+ "PATH",
11536
+ "HOME",
11537
+ "USERPROFILE",
11538
+ "TMPDIR",
11539
+ "TMP",
11540
+ "TEMP",
11541
+ "SystemRoot",
11542
+ "SystemDrive",
11543
+ "OS",
11544
+ "COMSPEC",
11545
+ "PATHEXT",
11546
+ "NODE_OPTIONS",
11547
+ "NODE_PATH",
11548
+ "NODE_NO_WARNINGS",
11549
+ "NODE_DEBUG",
11550
+ "LANG",
11551
+ "TERM",
11552
+ "COLORTERM",
11553
+ "NO_COLOR",
11554
+ "FORCE_COLOR",
11555
+ "CI"
11556
+ ];
11557
+ var SAFE_CONFORMANCE_ENV_PREFIXES = [
11558
+ "LC_",
11559
+ "SKILL_MAP_",
11560
+ "SM_"
11561
+ ];
11562
+ function pickSafeEnv(source) {
11563
+ const out = {};
11564
+ for (const key of SAFE_CONFORMANCE_ENV_KEYS) {
11565
+ const value = source[key];
11566
+ if (value !== void 0) out[key] = value;
11567
+ }
11568
+ for (const key of Object.keys(source)) {
11569
+ if (out[key] !== void 0) continue;
11570
+ for (const prefix of SAFE_CONFORMANCE_ENV_PREFIXES) {
11571
+ if (key.startsWith(prefix)) {
11572
+ const value = source[key];
11573
+ if (value !== void 0) out[key] = value;
11574
+ break;
11575
+ }
11576
+ }
11577
+ }
11578
+ return out;
11579
+ }
11492
11580
  function runConformanceCase(options) {
11493
11581
  const raw = readFileSync14(options.casePath, "utf8");
11494
11582
  const c = JSON.parse(raw);
@@ -11508,7 +11596,7 @@ function runConformanceCase(options) {
11508
11596
  if (c.invoke.flags) argv.push(...c.invoke.flags);
11509
11597
  const child = spawnSync2(process.execPath, [options.binary, ...argv], {
11510
11598
  cwd: scope,
11511
- env: { ...process.env, ...options.env, ...setupEnv },
11599
+ env: { ...pickSafeEnv(process.env), ...options.env, ...setupEnv },
11512
11600
  encoding: "utf8"
11513
11601
  });
11514
11602
  const stdout = child.stdout ?? "";
@@ -11536,7 +11624,7 @@ function runPriorScansSetup(c, options, scope, fixturesRoot, setupEnv) {
11536
11624
  const stepArgv = ["scan", ...step.flags ?? []];
11537
11625
  const stepChild = spawnSync2(process.execPath, [options.binary, ...stepArgv], {
11538
11626
  cwd: scope,
11539
- env: { ...process.env, ...options.env, ...setupEnv },
11627
+ env: { ...pickSafeEnv(process.env), ...options.env, ...setupEnv },
11540
11628
  encoding: "utf8"
11541
11629
  });
11542
11630
  if ((stepChild.status ?? 0) !== 0) {
@@ -14035,14 +14123,7 @@ function registeredVerbPaths(cli2) {
14035
14123
  }
14036
14124
 
14037
14125
  // cli/commands/hooks.ts
14038
- import {
14039
- chmodSync,
14040
- existsSync as existsSync19,
14041
- mkdirSync as mkdirSync5,
14042
- readFileSync as readFileSync17,
14043
- statSync as statSync5,
14044
- writeFileSync as writeFileSync2
14045
- } from "fs";
14126
+ import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, stat as stat2, writeFile } from "fs/promises";
14046
14127
  import { dirname as dirname16, resolve as resolve27 } from "path";
14047
14128
  import { Command as Command16, Option as Option15 } from "clipanion";
14048
14129
 
@@ -14136,7 +14217,7 @@ var HooksInstallCommand = class extends SmCommand {
14136
14217
  return ExitCode.Error;
14137
14218
  }
14138
14219
  const ctx = defaultRuntimeContext();
14139
- const repoRoot = findGitRepoRoot(ctx.cwd);
14220
+ const repoRoot = await findGitRepoRoot(ctx.cwd);
14140
14221
  if (repoRoot === null) {
14141
14222
  this.printer.error(
14142
14223
  tx(HOOKS_TEXTS.notInGitRepo, {
@@ -14148,7 +14229,7 @@ var HooksInstallCommand = class extends SmCommand {
14148
14229
  }
14149
14230
  const hooksDir = resolve27(repoRoot, ".git", "hooks");
14150
14231
  const hookPath = resolve27(hooksDir, "pre-commit");
14151
- const existing = existsSync19(hookPath) ? readFileSync17(hookPath, "utf8") : null;
14232
+ const existing = await pathExists(hookPath) ? await readFile2(hookPath, "utf8") : null;
14152
14233
  const planned2 = computePlannedHookContent(existing);
14153
14234
  if (planned2.kind === "already-installed") {
14154
14235
  this.printer.info(tx(HOOKS_TEXTS.alreadyInstalled, { glyph: okGlyph, hookPath }));
@@ -14174,9 +14255,9 @@ var HooksInstallCommand = class extends SmCommand {
14174
14255
  return ExitCode.Ok;
14175
14256
  }
14176
14257
  try {
14177
- if (!existsSync19(hooksDir)) mkdirSync5(hooksDir, { recursive: true });
14178
- writeFileSync2(hookPath, planned2.content, { encoding: "utf8" });
14179
- ensureExecutableBit(hookPath);
14258
+ if (!await pathExists(hooksDir)) await mkdir3(hooksDir, { recursive: true });
14259
+ await writeFile(hookPath, planned2.content, { encoding: "utf8" });
14260
+ await ensureExecutableBit(hookPath);
14180
14261
  } catch (err) {
14181
14262
  this.printer.error(
14182
14263
  tx(HOOKS_TEXTS.installFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
@@ -14202,10 +14283,10 @@ var HooksInstallCommand = class extends SmCommand {
14202
14283
  return ExitCode.Ok;
14203
14284
  }
14204
14285
  };
14205
- function findGitRepoRoot(cwd) {
14286
+ async function findGitRepoRoot(cwd) {
14206
14287
  let current = cwd;
14207
14288
  while (true) {
14208
- if (existsSync19(resolve27(current, ".git"))) return current;
14289
+ if (await pathExists(resolve27(current, ".git"))) return current;
14209
14290
  const parent = dirname16(current);
14210
14291
  if (parent === current) return null;
14211
14292
  current = parent;
@@ -14216,22 +14297,22 @@ function computePlannedHookContent(existing) {
14216
14297
  if (existing.includes(SKILL_MAP_MARKER)) {
14217
14298
  return { kind: "already-installed", content: existing };
14218
14299
  }
14219
- const sep6 = existing.endsWith("\n") ? "" : "\n";
14220
- return { kind: "chained", content: existing + sep6 + "\n" + SKILL_MAP_BLOCK };
14300
+ const sep7 = existing.endsWith("\n") ? "" : "\n";
14301
+ return { kind: "chained", content: existing + sep7 + "\n" + SKILL_MAP_BLOCK };
14221
14302
  }
14222
- function ensureExecutableBit(path) {
14223
- const mode = statSync5(path).mode;
14224
- chmodSync(path, mode | 73);
14303
+ async function ensureExecutableBit(path) {
14304
+ const mode = (await stat2(path)).mode;
14305
+ await chmod2(path, mode | 73);
14225
14306
  }
14226
14307
  var HOOKS_COMMANDS = [HooksInstallCommand];
14227
14308
 
14228
14309
  // cli/commands/init.ts
14229
- import { mkdir as mkdir3, readFile as readFile2, unlink, writeFile } from "fs/promises";
14310
+ import { mkdir as mkdir4, readFile as readFile3, unlink, writeFile as writeFile2 } from "fs/promises";
14230
14311
  import { join as join17 } from "path";
14231
14312
  import { Command as Command17, Option as Option16 } from "clipanion";
14232
14313
 
14233
14314
  // kernel/orchestrator/index.ts
14234
- import { existsSync as existsSync22, statSync as statSync7 } from "fs";
14315
+ import { existsSync as existsSync21, statSync as statSync6 } from "fs";
14235
14316
  import { isAbsolute as isAbsolute7, resolve as resolve28 } from "path";
14236
14317
  import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
14237
14318
  import cl100k_base from "js-tiktoken/ranks/cl100k_base";
@@ -15265,8 +15346,8 @@ function computeDriftStatus(args2) {
15265
15346
  }
15266
15347
 
15267
15348
  // kernel/sidecar/discover-orphans.ts
15268
- import { existsSync as existsSync20, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
15269
- import { join as join13, relative as relative4, sep as sep3 } from "path";
15349
+ import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
15350
+ import { join as join13, relative as relative4, sep as sep4 } from "path";
15270
15351
  function discoverOrphanSidecars(roots, shouldSkip) {
15271
15352
  const out = [];
15272
15353
  for (const root of roots) {
@@ -15283,7 +15364,7 @@ function walk(root, current, shouldSkip, out) {
15283
15364
  }
15284
15365
  for (const entry of entries) {
15285
15366
  const full = join13(current, entry.name);
15286
- const rel = relative4(root, full).split(sep3).join("/");
15367
+ const rel = relative4(root, full).split(sep4).join("/");
15287
15368
  if (shouldSkip(rel)) continue;
15288
15369
  if (entry.isSymbolicLink()) continue;
15289
15370
  if (entry.isDirectory()) {
@@ -15293,13 +15374,13 @@ function walk(root, current, shouldSkip, out) {
15293
15374
  if (!entry.isFile()) continue;
15294
15375
  if (!entry.name.endsWith(".sm")) continue;
15295
15376
  const expectedMd = `${full.slice(0, -".sm".length)}.md`;
15296
- if (existsSync20(expectedMd) && safeIsFile(expectedMd)) continue;
15377
+ if (existsSync19(expectedMd) && safeIsFile(expectedMd)) continue;
15297
15378
  out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
15298
15379
  }
15299
15380
  }
15300
15381
  function safeIsFile(path) {
15301
15382
  try {
15302
- return statSync6(path).isFile();
15383
+ return statSync5(path).isFile();
15303
15384
  } catch {
15304
15385
  return false;
15305
15386
  }
@@ -15307,7 +15388,7 @@ function safeIsFile(path) {
15307
15388
 
15308
15389
  // kernel/orchestrator/node-build.ts
15309
15390
  import { createHash } from "crypto";
15310
- import { existsSync as existsSync21 } from "fs";
15391
+ import { existsSync as existsSync20 } from "fs";
15311
15392
  import { isAbsolute as isAbsolute6, resolve as resolvePath } from "path";
15312
15393
  import "js-tiktoken/lite";
15313
15394
  import yaml4 from "js-yaml";
@@ -15471,11 +15552,11 @@ function resolveSidecarOverlay(relativePath2, nodePathForIssue, roots, liveBodyH
15471
15552
  }
15472
15553
  function resolveAbsoluteMdPath(relativePath2, roots) {
15473
15554
  if (isAbsolute6(relativePath2)) {
15474
- return existsSync21(relativePath2) ? relativePath2 : null;
15555
+ return existsSync20(relativePath2) ? relativePath2 : null;
15475
15556
  }
15476
15557
  for (const root of roots) {
15477
15558
  const candidate = resolvePath(root, relativePath2);
15478
- if (existsSync21(candidate)) return candidate;
15559
+ if (existsSync20(candidate)) return candidate;
15479
15560
  }
15480
15561
  return null;
15481
15562
  }
@@ -16016,7 +16097,7 @@ function validateRoots(roots) {
16016
16097
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
16017
16098
  }
16018
16099
  for (const root of roots) {
16019
- if (!existsSync22(root) || !statSync7(root).isDirectory()) {
16100
+ if (!existsSync21(root) || !statSync6(root).isDirectory()) {
16020
16101
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
16021
16102
  }
16022
16103
  }
@@ -16025,7 +16106,7 @@ function resolveActiveProviderOption(optionValue, roots) {
16025
16106
  if (optionValue !== void 0) return optionValue;
16026
16107
  for (const root of roots) {
16027
16108
  const absRoot = isAbsolute7(root) ? root : resolve28(root);
16028
- if (!existsSync22(absRoot)) continue;
16109
+ if (!existsSync21(absRoot)) continue;
16029
16110
  const detected = resolveActiveProvider(absRoot).resolved;
16030
16111
  if (detected !== null) return detected;
16031
16112
  }
@@ -16033,7 +16114,7 @@ function resolveActiveProviderOption(optionValue, roots) {
16033
16114
  }
16034
16115
 
16035
16116
  // kernel/scan/watcher.ts
16036
- import { resolve as resolve29, relative as relative5, sep as sep4 } from "path";
16117
+ import { resolve as resolve29, relative as relative5, sep as sep5 } from "path";
16037
16118
  import chokidar from "chokidar";
16038
16119
  function createChokidarWatcher(opts) {
16039
16120
  const absRoots = opts.roots.map((r) => resolve29(opts.cwd, r));
@@ -16131,8 +16212,8 @@ function relativePathFromRoots2(absolute, absRoots) {
16131
16212
  for (const root of absRoots) {
16132
16213
  const rel = relative5(root, absolute);
16133
16214
  if (rel === "" || rel === ".") return "";
16134
- if (!rel.startsWith("..") && !rel.startsWith(`..${sep4}`)) {
16135
- return rel.split(sep4).join("/");
16215
+ if (!rel.startsWith("..") && !rel.startsWith(`..${sep5}`)) {
16216
+ return rel.split(sep5).join("/");
16136
16217
  }
16137
16218
  }
16138
16219
  return null;
@@ -16254,13 +16335,13 @@ function createKernel() {
16254
16335
  }
16255
16336
 
16256
16337
  // kernel/jobs/orphan-files.ts
16257
- import { readdirSync as readdirSync8, statSync as statSync8 } from "fs";
16338
+ import { readdirSync as readdirSync8, statSync as statSync7 } from "fs";
16258
16339
  import { join as join14, resolve as resolve30 } from "path";
16259
16340
  function findOrphanJobFiles(jobsDir, referencedPaths) {
16260
16341
  let entries;
16261
16342
  try {
16262
- const stat2 = statSync8(jobsDir);
16263
- if (!stat2.isDirectory()) {
16343
+ const stat3 = statSync7(jobsDir);
16344
+ if (!stat3.isDirectory()) {
16264
16345
  return { orphanFilePaths: [], referencedCount: referencedPaths.size };
16265
16346
  }
16266
16347
  entries = readdirSync8(jobsDir, { withFileTypes: true });
@@ -16422,7 +16503,7 @@ function resolveScanRoots(inputs) {
16422
16503
  }
16423
16504
 
16424
16505
  // core/runtime/reference-paths-walker.ts
16425
- import { readdirSync as readdirSync9, statSync as statSync9 } from "fs";
16506
+ import { readdirSync as readdirSync9, statSync as statSync8 } from "fs";
16426
16507
  import { homedir as osHomedir2 } from "os";
16427
16508
  import { isAbsolute as isAbsolute8, join as join15, resolve as resolve31 } from "path";
16428
16509
  var REFERENCE_WALK_MAX_FILES = 5e4;
@@ -16444,8 +16525,8 @@ function walkReferencePaths(rawRoots, cwd) {
16444
16525
  for (const raw of rawRoots) {
16445
16526
  if (truncated) break;
16446
16527
  const root = resolveScanPath(raw, cwd);
16447
- const stat2 = safeStat(root);
16448
- if (!stat2 || !stat2.isDirectory()) {
16528
+ const stat3 = safeStat(root);
16529
+ if (!stat3 || !stat3.isDirectory()) {
16449
16530
  missingRoots.push(root);
16450
16531
  continue;
16451
16532
  }
@@ -16476,7 +16557,7 @@ function walkInto(dir, out) {
16476
16557
  }
16477
16558
  function safeStat(path) {
16478
16559
  try {
16479
- return statSync9(path);
16560
+ return statSync8(path);
16480
16561
  } catch {
16481
16562
  return null;
16482
16563
  }
@@ -16979,7 +17060,7 @@ var InitCommand = class extends SmCommand {
16979
17060
  const printer = this.printer ?? createPrinter({
16980
17061
  stdout: this.context.stdout,
16981
17062
  stderr: this.context.stderr,
16982
- quietInfo: this.quiet
17063
+ quietInfo: this.quiet || this.json
16983
17064
  });
16984
17065
  if (this.dryRun) {
16985
17066
  await writeDryRunPlan(printer, {
@@ -16994,7 +17075,7 @@ var InitCommand = class extends SmCommand {
16994
17075
  });
16995
17076
  return ExitCode.Ok;
16996
17077
  }
16997
- await mkdir3(skillMapDir, { recursive: true });
17078
+ await mkdir4(skillMapDir, { recursive: true });
16998
17079
  writeFileAtomicExclusive(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
16999
17080
  if (!await pathExists(localPath) || this.force) {
17000
17081
  writeFileAtomicExclusive(localPath, "{}\n");
@@ -17168,7 +17249,7 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, stdin, ansi) {
17168
17249
  }
17169
17250
  async function previewGitignoreEntries(scopeRoot, entries) {
17170
17251
  const path = join17(scopeRoot, ".gitignore");
17171
- const body = await pathExists(path) ? await readFile2(path, "utf8") : "";
17252
+ const body = await pathExists(path) ? await readFile3(path, "utf8") : "";
17172
17253
  const present = new Set(
17173
17254
  body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
17174
17255
  );
@@ -17178,7 +17259,7 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
17178
17259
  const path = join17(scopeRoot, ".gitignore");
17179
17260
  let body = "";
17180
17261
  if (await pathExists(path)) {
17181
- body = await readFile2(path, "utf8");
17262
+ body = await readFile3(path, "utf8");
17182
17263
  }
17183
17264
  const present = new Set(
17184
17265
  body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
@@ -17192,7 +17273,7 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
17192
17273
  present.add(entry);
17193
17274
  changed = true;
17194
17275
  }
17195
- if (changed) await writeFile(path, body);
17276
+ if (changed) await writeFile2(path, body);
17196
17277
  return changed;
17197
17278
  }
17198
17279
 
@@ -19058,10 +19139,10 @@ function pluginToListRow(p) {
19058
19139
  }
19059
19140
  function wrapNames(names, indent, maxWidth) {
19060
19141
  const out = [];
19061
- const sep6 = ", ";
19142
+ const sep7 = ", ";
19062
19143
  let current = "";
19063
19144
  for (const name of names) {
19064
- const candidate = current === "" ? name : `${current}${sep6}${name}`;
19145
+ const candidate = current === "" ? name : `${current}${sep7}${name}`;
19065
19146
  if (indent.length + candidate.length > maxWidth && current !== "") {
19066
19147
  out.push(`${current},`);
19067
19148
  current = name;
@@ -20066,7 +20147,7 @@ function resolveBareToggle(id, catalogue, verb, ansi) {
20066
20147
  }
20067
20148
 
20068
20149
  // cli/commands/plugins/create.ts
20069
- import { existsSync as existsSync23, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3 } from "fs";
20150
+ import { existsSync as existsSync22, mkdirSync as mkdirSync5, writeFileSync } from "fs";
20070
20151
  import { join as join18, resolve as resolve33 } from "path";
20071
20152
  import { Command as Command26, Option as Option25 } from "clipanion";
20072
20153
  var PluginsCreateCommand = class extends SmCommand {
@@ -20095,7 +20176,7 @@ var PluginsCreateCommand = class extends SmCommand {
20095
20176
  const ctx = defaultRuntimeContext();
20096
20177
  const baseDir = defaultProjectPluginsDir(ctx);
20097
20178
  const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
20098
- if (existsSync23(targetDir) && !this.force) {
20179
+ if (existsSync22(targetDir) && !this.force) {
20099
20180
  this.printer.error(
20100
20181
  tx(PLUGINS_TEXTS.createRefuseOverwrite, {
20101
20182
  glyph: errGlyph,
@@ -20106,7 +20187,7 @@ var PluginsCreateCommand = class extends SmCommand {
20106
20187
  return ExitCode.Error;
20107
20188
  }
20108
20189
  const extractorName = `${this.pluginId}-extractor`;
20109
- mkdirSync6(join18(targetDir, "extractors", extractorName), { recursive: true });
20190
+ mkdirSync5(join18(targetDir, "extractors", extractorName), { recursive: true });
20110
20191
  const specVersion = installedSpecVersion();
20111
20192
  const manifest = {
20112
20193
  id: this.pluginId,
@@ -20125,15 +20206,15 @@ var PluginsCreateCommand = class extends SmCommand {
20125
20206
  }
20126
20207
  }
20127
20208
  };
20128
- writeFileSync3(
20209
+ writeFileSync(
20129
20210
  join18(targetDir, "plugin.json"),
20130
20211
  JSON.stringify(manifest, null, 2) + "\n"
20131
20212
  );
20132
- writeFileSync3(
20213
+ writeFileSync(
20133
20214
  join18(targetDir, "extractors", extractorName, "index.js"),
20134
20215
  scaffolderExtractorStub(extractorName)
20135
20216
  );
20136
- writeFileSync3(join18(targetDir, "README.md"), scaffolderReadme(this.pluginId));
20217
+ writeFileSync(join18(targetDir, "README.md"), scaffolderReadme(this.pluginId));
20137
20218
  this.printer.data(
20138
20219
  tx(PLUGINS_TEXTS.createSuccess, {
20139
20220
  targetDir: sanitizeForTerminal(targetDir),
@@ -20335,7 +20416,7 @@ var PLUGIN_COMMANDS = [
20335
20416
  ];
20336
20417
 
20337
20418
  // cli/commands/refresh.ts
20338
- import { readFile as readFile3 } from "fs/promises";
20419
+ import { readFile as readFile4 } from "fs/promises";
20339
20420
  import { resolve as resolve34 } from "path";
20340
20421
  import { Command as Command29, Option as Option27 } from "clipanion";
20341
20422
 
@@ -20642,7 +20723,7 @@ var RefreshCommand = class extends SmCommand {
20642
20723
  let body;
20643
20724
  try {
20644
20725
  assertContained(cwd, node.path);
20645
- const raw = await readFile3(resolve34(cwd, node.path), "utf8");
20726
+ const raw = await readFile4(resolve34(cwd, node.path), "utf8");
20646
20727
  body = stripFrontmatterFence(raw);
20647
20728
  } catch (err) {
20648
20729
  if (!this.json) {
@@ -21491,7 +21572,7 @@ var ScanCommand = class extends SmCommand {
21491
21572
  }
21492
21573
  return null;
21493
21574
  }
21494
- /** Render the failure branch of `IScanRunResult` to stderr. */
21575
+ /** Render the failure branch of `TScanRunResult` to stderr. */
21495
21576
  renderFailure(outcome) {
21496
21577
  const ansi = this.ansiFor("stderr");
21497
21578
  const errGlyph = ansi.red("\u2715");
@@ -21570,7 +21651,7 @@ var ScanCommand = class extends SmCommand {
21570
21651
  this.printer.info(
21571
21652
  tx(SCAN_TEXTS.jsonSelfValidationFailed, {
21572
21653
  glyph: ansi.red("\u2715"),
21573
- errors: validation.errors
21654
+ errors: JSON.stringify(validation.errors, null, 2)
21574
21655
  })
21575
21656
  );
21576
21657
  return ExitCode.Error;
@@ -21591,7 +21672,7 @@ function plural(count, word) {
21591
21672
  }
21592
21673
 
21593
21674
  // cli/commands/scan-compare.ts
21594
- import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
21675
+ import { access, readFile as readFile5 } from "fs/promises";
21595
21676
  import { Command as Command32, Option as Option30 } from "clipanion";
21596
21677
  var ScanCompareCommand = class extends SmCommand {
21597
21678
  static paths = [["scan", "compare-with"]];
@@ -21644,7 +21725,7 @@ var ScanCompareCommand = class extends SmCommand {
21644
21725
  const roots = this.roots.length > 0 ? this.roots : ["."];
21645
21726
  let prior;
21646
21727
  try {
21647
- prior = loadAndValidateDump(this.dump);
21728
+ prior = await loadAndValidateDump(this.dump);
21648
21729
  } catch (err) {
21649
21730
  const message = formatErrorMessage(err);
21650
21731
  this.printer.info(tx(SCAN_TEXTS.compareErrorPrefix, { message }));
@@ -21702,13 +21783,15 @@ var ScanCompareCommand = class extends SmCommand {
21702
21783
  return exitCode2;
21703
21784
  }
21704
21785
  };
21705
- function loadAndValidateDump(path) {
21706
- if (!existsSync24(path)) {
21786
+ async function loadAndValidateDump(path) {
21787
+ try {
21788
+ await access(path);
21789
+ } catch {
21707
21790
  throw new Error(tx(SCAN_TEXTS.compareDumpNotFound, { path }));
21708
21791
  }
21709
21792
  let raw;
21710
21793
  try {
21711
- raw = readFileSync18(path, "utf8");
21794
+ raw = await readFile5(path, "utf8");
21712
21795
  } catch (err) {
21713
21796
  const message = formatErrorMessage(err);
21714
21797
  throw new Error(tx(SCAN_TEXTS.compareDumpReadFailed, { path, message }), { cause: err });
@@ -21833,7 +21916,7 @@ function renderDeltaIssues(issues) {
21833
21916
 
21834
21917
  // cli/commands/serve.ts
21835
21918
  import { spawn as spawn2 } from "child_process";
21836
- import { existsSync as existsSync30 } from "fs";
21919
+ import { existsSync as existsSync28 } from "fs";
21837
21920
  import { Command as Command33, Option as Option31 } from "clipanion";
21838
21921
 
21839
21922
  // cli/util/browser-launch.ts
@@ -22459,7 +22542,7 @@ function registerFavoritesRoutes(app, deps) {
22459
22542
  );
22460
22543
  if (!result || !result.found) {
22461
22544
  throw new HTTPException4(404, {
22462
- message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
22545
+ message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
22463
22546
  });
22464
22547
  }
22465
22548
  return c.body(null, 204);
@@ -22546,7 +22629,7 @@ function contentTypeFor(format) {
22546
22629
  }
22547
22630
 
22548
22631
  // server/health.ts
22549
- import { existsSync as existsSync25 } from "fs";
22632
+ import { existsSync as existsSync23 } from "fs";
22550
22633
  var FALLBACK_SCHEMA_VERSION = "1";
22551
22634
  function buildHealth(deps) {
22552
22635
  return {
@@ -22554,7 +22637,7 @@ function buildHealth(deps) {
22554
22637
  schemaVersion: FALLBACK_SCHEMA_VERSION,
22555
22638
  specVersion: deps.specVersion,
22556
22639
  implVersion: VERSION,
22557
- db: existsSync25(deps.dbPath) ? "present" : "missing",
22640
+ db: existsSync23(deps.dbPath) ? "present" : "missing",
22558
22641
  cwd: deps.cwd,
22559
22642
  dbPath: deps.dbPath
22560
22643
  };
@@ -22586,47 +22669,60 @@ function registerHealthRoute(app, deps) {
22586
22669
  });
22587
22670
  }
22588
22671
 
22589
- // server/routes/issues.ts
22672
+ // server/limits.ts
22590
22673
  var DEFAULT_LIMIT = 100;
22591
22674
  var MAX_LIMIT = 1e3;
22675
+ var BFF_MAX_BULK_CONTRIBUTIONS = 200;
22676
+
22677
+ // server/routes/issues.ts
22592
22678
  function registerIssuesRoute(app, deps) {
22593
22679
  app.get("/api/issues", async (c) => {
22594
- const severityFilter = parseCsv(c.req.query("severity"));
22595
- const analyzerFilter = parseCsv(c.req.query("analyzerId"));
22596
- const nodePath = c.req.query("node") ?? null;
22597
- const { offset, limit } = parsePagination(c.req.query(), {
22598
- limit: DEFAULT_LIMIT,
22599
- max: MAX_LIMIT
22600
- });
22680
+ const inputs = parseIssuesQuery(c.req.query());
22601
22681
  const result = await tryWithSqlite(
22602
22682
  { databasePath: deps.options.dbPath, autoBackup: false },
22603
- (adapter) => adapter.issues.list({
22604
- severities: severityFilter,
22605
- analyzerIds: analyzerFilter,
22606
- nodePath,
22607
- offset,
22608
- limit
22609
- })
22683
+ (adapter) => adapter.issues.list(inputs.filter)
22610
22684
  );
22611
- const items = result?.items ?? [];
22612
- const total = result?.total ?? 0;
22613
22685
  return c.json(
22614
22686
  buildListEnvelope({
22615
22687
  kind: "issues",
22616
- items,
22617
- filters: {
22618
- severity: severityFilter.length > 0 ? severityFilter : null,
22619
- analyzerId: analyzerFilter.length > 0 ? analyzerFilter : null,
22620
- node: nodePath
22621
- },
22622
- total,
22623
- page: { offset, limit },
22688
+ items: result?.items ?? [],
22689
+ filters: inputs.echo,
22690
+ total: result?.total ?? 0,
22691
+ page: { offset: inputs.filter.offset, limit: inputs.filter.limit },
22624
22692
  kindRegistry: deps.kindRegistry,
22625
22693
  contributionsRegistry: deps.contributionsRegistry
22626
22694
  })
22627
22695
  );
22628
22696
  });
22629
22697
  }
22698
+ function parseIssuesQuery(query) {
22699
+ const severityFilter = parseCsv(query["severity"]);
22700
+ const analyzerFilter = parseCsv(query["analyzerId"]);
22701
+ const nodePath = query["node"] ?? null;
22702
+ const nodesRaw = parseCsv(query["nodes"]);
22703
+ const nodesFilter = nodesRaw.length > 0 ? nodesRaw : null;
22704
+ const { offset, limit } = parsePagination(query, {
22705
+ limit: DEFAULT_LIMIT,
22706
+ max: MAX_LIMIT
22707
+ });
22708
+ const filter = {
22709
+ severities: severityFilter,
22710
+ analyzerIds: analyzerFilter,
22711
+ nodePath,
22712
+ offset,
22713
+ limit
22714
+ };
22715
+ if (nodesFilter) filter.nodePaths = nodesFilter;
22716
+ return {
22717
+ filter,
22718
+ echo: {
22719
+ severity: severityFilter.length > 0 ? severityFilter : null,
22720
+ analyzerId: analyzerFilter.length > 0 ? analyzerFilter : null,
22721
+ node: nodePath,
22722
+ nodes: nodesFilter
22723
+ }
22724
+ };
22725
+ }
22630
22726
 
22631
22727
  // server/routes/links.ts
22632
22728
  function registerLinksRoute(app, deps) {
@@ -22666,22 +22762,30 @@ function registerLinksRoute(app, deps) {
22666
22762
  import { HTTPException as HTTPException6 } from "hono/http-exception";
22667
22763
 
22668
22764
  // server/node-body.ts
22669
- import { readFile as readFile4 } from "fs/promises";
22670
- import { isAbsolute as isAbsolute10, resolve as resolvePath2, relative as relativePath, sep as sep5 } from "path";
22765
+ import { constants as fsConstants2 } from "fs";
22766
+ import { open } from "fs/promises";
22767
+ import { isAbsolute as isAbsolute10, resolve as resolvePath2, relative as relativePath, sep as sep6 } from "path";
22671
22768
  async function readNodeBody(cwd, relPath) {
22672
22769
  if (isAbsolute10(relPath)) return null;
22673
22770
  const absRoot = resolvePath2(cwd);
22674
22771
  const absFile = resolvePath2(absRoot, relPath);
22675
22772
  const rel = relativePath(absRoot, absFile);
22676
- if (rel.startsWith("..") || rel.startsWith(sep5) || rel.length === 0) {
22773
+ if (rel.startsWith("..") || rel.startsWith(sep6) || rel.length === 0) {
22677
22774
  return null;
22678
22775
  }
22679
22776
  let raw;
22777
+ let handle = null;
22680
22778
  try {
22681
- raw = await readFile4(absFile, "utf-8");
22779
+ handle = await open(absFile, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
22780
+ raw = await handle.readFile("utf-8");
22682
22781
  } catch (err) {
22683
22782
  if (isExpectedFsError(err)) return null;
22684
22783
  throw err;
22784
+ } finally {
22785
+ if (handle !== null) {
22786
+ await handle.close().catch(() => {
22787
+ });
22788
+ }
22685
22789
  }
22686
22790
  return stripFrontmatter(raw);
22687
22791
  }
@@ -22691,7 +22795,16 @@ function stripFrontmatter(raw) {
22691
22795
  if (!match) return raw;
22692
22796
  return raw.slice(match[0].length);
22693
22797
  }
22694
- var EXPECTED_FS_ERROR_CODES = /* @__PURE__ */ new Set(["ENOENT", "EACCES", "EISDIR", "ENOTDIR"]);
22798
+ var EXPECTED_FS_ERROR_CODES = /* @__PURE__ */ new Set([
22799
+ "ENOENT",
22800
+ "EACCES",
22801
+ "EISDIR",
22802
+ "ENOTDIR",
22803
+ // `O_NOFOLLOW` opens fail with `ELOOP` on Linux / macOS when the
22804
+ // leaf is a symlink. Treat the same as "body unavailable" so the
22805
+ // BFF returns null, not a 500.
22806
+ "ELOOP"
22807
+ ]);
22695
22808
  function isExpectedFsError(err) {
22696
22809
  if (err === null || typeof err !== "object") return false;
22697
22810
  const code = err.code;
@@ -22749,9 +22862,6 @@ function splitCsv(raw) {
22749
22862
  }
22750
22863
 
22751
22864
  // server/routes/nodes.ts
22752
- var DEFAULT_LIMIT2 = 100;
22753
- var MAX_LIMIT2 = 1e3;
22754
- var BFF_MAX_BULK_CONTRIBUTIONS = 200;
22755
22865
  function registerNodesRoutes(app, deps) {
22756
22866
  app.get("/api/nodes/:pathB64", async (c) => {
22757
22867
  const pathB64 = c.req.param("pathB64");
@@ -22813,8 +22923,8 @@ function registerNodesRoutes(app, deps) {
22813
22923
  const params = new URL(c.req.url).searchParams;
22814
22924
  const { query, filters } = urlParamsToExportQuery(params);
22815
22925
  const { offset, limit } = parsePagination(c.req.query(), {
22816
- limit: DEFAULT_LIMIT2,
22817
- max: MAX_LIMIT2
22926
+ limit: DEFAULT_LIMIT,
22927
+ max: MAX_LIMIT
22818
22928
  });
22819
22929
  const opened = await tryWithSqlite(
22820
22930
  { databasePath: deps.options.dbPath, autoBackup: false },
@@ -23389,15 +23499,15 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
23389
23499
  import { HTTPException as HTTPException10 } from "hono/http-exception";
23390
23500
 
23391
23501
  // server/util/skillmapignore-io.ts
23392
- import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync4 } from "fs";
23502
+ import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
23393
23503
  import { resolve as resolve35 } from "path";
23394
23504
  var IGNORE_FILENAME2 = ".skillmapignore";
23395
23505
  function readPatterns(cwd) {
23396
23506
  const path = resolve35(cwd, IGNORE_FILENAME2);
23397
- if (!existsSync26(path)) return [];
23507
+ if (!existsSync24(path)) return [];
23398
23508
  let raw;
23399
23509
  try {
23400
- raw = readFileSync19(path, "utf8");
23510
+ raw = readFileSync17(path, "utf8");
23401
23511
  } catch {
23402
23512
  return [];
23403
23513
  }
@@ -23405,13 +23515,13 @@ function readPatterns(cwd) {
23405
23515
  }
23406
23516
  function writePatterns(cwd, nextPatterns) {
23407
23517
  const path = resolve35(cwd, IGNORE_FILENAME2);
23408
- const prior = existsSync26(path) ? safeRead(path) : "";
23518
+ const prior = existsSync24(path) ? safeRead(path) : "";
23409
23519
  const content = buildContent(prior, nextPatterns);
23410
- writeFileSync4(path, content, "utf8");
23520
+ writeFileSync2(path, content, "utf8");
23411
23521
  }
23412
23522
  function safeRead(path) {
23413
23523
  try {
23414
- return readFileSync19(path, "utf8");
23524
+ return readFileSync17(path, "utf8");
23415
23525
  } catch {
23416
23526
  return "";
23417
23527
  }
@@ -23566,7 +23676,7 @@ var parsePatchBody3 = makeBodyValidator(PATCH_BODY_SCHEMA2, {
23566
23676
  });
23567
23677
 
23568
23678
  // server/routes/project-preferences.ts
23569
- import { statSync as statSync10 } from "fs";
23679
+ import { statSync as statSync9 } from "fs";
23570
23680
  import { HTTPException as HTTPException11 } from "hono/http-exception";
23571
23681
  function registerProjectPreferencesRoute(app, deps) {
23572
23682
  app.get("/api/project-preferences", (c) => {
@@ -23715,7 +23825,7 @@ function formatPathDetail(path, cwd) {
23715
23825
  function isExistingDirectory(entry, cwd) {
23716
23826
  const abs = resolveScanPath(entry, cwd);
23717
23827
  try {
23718
- return statSync10(abs).isDirectory();
23828
+ return statSync9(abs).isDirectory();
23719
23829
  } catch {
23720
23830
  return false;
23721
23831
  }
@@ -23755,7 +23865,7 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
23755
23865
  });
23756
23866
 
23757
23867
  // server/routes/active-provider.ts
23758
- import { existsSync as existsSync27 } from "fs";
23868
+ import { existsSync as existsSync25 } from "fs";
23759
23869
  import { HTTPException as HTTPException12 } from "hono/http-exception";
23760
23870
  function registerActiveProviderRoute(app, deps) {
23761
23871
  app.get("/api/active-provider", (c) => {
@@ -23788,7 +23898,7 @@ function applyLensSwitch(deps, newValue) {
23788
23898
  });
23789
23899
  }
23790
23900
  const dbPath = resolveDbPath({ db: void 0, cwd });
23791
- if (!existsSync27(dbPath)) return { dropped: null };
23901
+ if (!existsSync25(dbPath)) return { dropped: null };
23792
23902
  const dropResult = dropScanZone(dbPath);
23793
23903
  return {
23794
23904
  dropped: {
@@ -24180,7 +24290,7 @@ function registerSidecarRoutes(app, deps) {
24180
24290
  assertContained(deps.runtimeContext.cwd, node.path);
24181
24291
  absPath = resolve36(deps.runtimeContext.cwd, node.path);
24182
24292
  } catch (err) {
24183
- throw new HTTPException14(500, { message: formatErrorMessage(err) });
24293
+ throw new HTTPException14(400, { message: formatErrorMessage(err) });
24184
24294
  }
24185
24295
  const result = invokeBump2(node, absPath, body);
24186
24296
  if (result.report.ok === false && result.report.reason === "fresh") {
@@ -24302,8 +24412,8 @@ function registerUpdateStatusRoute(app, deps) {
24302
24412
  }
24303
24413
 
24304
24414
  // server/static.ts
24305
- import { existsSync as existsSync28 } from "fs";
24306
- import { readFile as readFile5 } from "fs/promises";
24415
+ import { existsSync as existsSync26 } from "fs";
24416
+ import { readFile as readFile6 } from "fs/promises";
24307
24417
  import { extname, join as join19 } from "path";
24308
24418
  import { serveStatic } from "@hono/node-server/serve-static";
24309
24419
  var INDEX_HTML = "index.html";
@@ -24357,7 +24467,7 @@ function createSpaFallback(opts) {
24357
24467
  if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
24358
24468
  if (opts.uiDist === null) return htmlResponse(c, placeholder);
24359
24469
  const indexPath = join19(opts.uiDist, INDEX_HTML);
24360
- if (!existsSync28(indexPath)) return htmlResponse(c, placeholder);
24470
+ if (!existsSync26(indexPath)) return htmlResponse(c, placeholder);
24361
24471
  return fileResponse(c, indexPath);
24362
24472
  };
24363
24473
  }
@@ -24399,7 +24509,7 @@ function htmlResponse(c, html) {
24399
24509
  return c.body(html, 200, { "content-type": "text/html; charset=UTF-8" });
24400
24510
  }
24401
24511
  async function fileResponse(c, absPath) {
24402
- const buf = await readFile5(absPath);
24512
+ const buf = await readFile6(absPath);
24403
24513
  return c.body(buf, 200, { "content-type": mimeFor(absPath) });
24404
24514
  }
24405
24515
 
@@ -24929,7 +25039,7 @@ function validateNoUi(noUi, uiDist) {
24929
25039
  }
24930
25040
 
24931
25041
  // server/paths.ts
24932
- import { existsSync as existsSync29, statSync as statSync11 } from "fs";
25042
+ import { existsSync as existsSync27, statSync as statSync10 } from "fs";
24933
25043
  import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
24934
25044
  import { fileURLToPath as fileURLToPath5 } from "url";
24935
25045
  var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
@@ -24944,10 +25054,10 @@ function resolveExplicitUiDist(ctx, raw) {
24944
25054
  return isAbsolute11(raw) ? raw : resolve37(ctx.cwd, raw);
24945
25055
  }
24946
25056
  function isUiBundleDir(path) {
24947
- if (!existsSync29(path)) return false;
25057
+ if (!existsSync27(path)) return false;
24948
25058
  try {
24949
- if (!statSync11(path).isDirectory()) return false;
24950
- return existsSync29(join20(path, INDEX_HTML2));
25059
+ if (!statSync10(path).isDirectory()) return false;
25060
+ return existsSync27(join20(path, INDEX_HTML2));
24951
25061
  } catch {
24952
25062
  return false;
24953
25063
  }
@@ -25478,7 +25588,7 @@ var ServeCommand = class extends SmCommand {
25478
25588
  return ExitCode.Error;
25479
25589
  }
25480
25590
  const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
25481
- if (this.db !== void 0 && !existsSync30(dbPath)) {
25591
+ if (this.db !== void 0 && !existsSync28(dbPath)) {
25482
25592
  this.printer.info(
25483
25593
  tx(SERVE_TEXTS.dbNotFound, {
25484
25594
  glyph: errGlyph,
@@ -26028,7 +26138,7 @@ function rankConfidenceForGrouping(c) {
26028
26138
  }
26029
26139
 
26030
26140
  // cli/commands/sidecar.ts
26031
- import { existsSync as existsSync31, unlinkSync as unlinkSync2 } from "fs";
26141
+ import { unlink as unlink3 } from "fs/promises";
26032
26142
  import { resolve as resolve38 } from "path";
26033
26143
  import { Command as Command35, Option as Option33 } from "clipanion";
26034
26144
 
@@ -26320,7 +26430,7 @@ var SidecarPruneCommand = class extends SmCommand {
26320
26430
  continue;
26321
26431
  }
26322
26432
  try {
26323
- unlinkSync2(orphan.sidecarPath);
26433
+ await unlink3(orphan.sidecarPath);
26324
26434
  items.push({
26325
26435
  sidecarPath: orphan.sidecarPath,
26326
26436
  expectedMd: orphan.expectedMdPath,
@@ -26477,7 +26587,8 @@ var SidecarAnnotateCommand = class extends SmCommand {
26477
26587
  return ExitCode.Error;
26478
26588
  }
26479
26589
  const sidecarAbsPath = sidecarPathFor(absPath);
26480
- if (existsSync31(sidecarAbsPath) && this.force !== true) {
26590
+ const sidecarExists = await pathExists(sidecarAbsPath);
26591
+ if (sidecarExists && this.force !== true) {
26481
26592
  this.printer.error(
26482
26593
  tx(SIDECAR_TEXTS.annotateExists, {
26483
26594
  glyph: errGlyph,
@@ -26487,9 +26598,9 @@ var SidecarAnnotateCommand = class extends SmCommand {
26487
26598
  );
26488
26599
  return ExitCode.Error;
26489
26600
  }
26490
- if (existsSync31(sidecarAbsPath) && this.force === true) {
26601
+ if (sidecarExists && this.force === true) {
26491
26602
  try {
26492
- unlinkSync2(sidecarAbsPath);
26603
+ await unlink3(sidecarAbsPath);
26493
26604
  } catch (err) {
26494
26605
  this.printer.error(
26495
26606
  tx(SIDECAR_TEXTS.annotateFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
@@ -26717,7 +26828,7 @@ var STUB_COMMANDS = [
26717
26828
  ];
26718
26829
 
26719
26830
  // cli/commands/tutorial.ts
26720
- import { cpSync as cpSync2, existsSync as existsSync32, mkdirSync as mkdirSync7, rmSync as rmSync2, statSync as statSync12 } from "fs";
26831
+ import { cpSync as cpSync2, existsSync as existsSync29, mkdirSync as mkdirSync6, rmSync as rmSync2, statSync as statSync11 } from "fs";
26721
26832
  import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
26722
26833
  import { fileURLToPath as fileURLToPath6 } from "url";
26723
26834
  import { Command as Command37, Option as Option35 } from "clipanion";
@@ -26816,7 +26927,7 @@ var TutorialCommand = class extends SmCommand {
26816
26927
  const spec = VARIANT_SPECS[variant];
26817
26928
  const targetDir = join21(ctx.cwd, ".claude", "skills", spec.slug);
26818
26929
  const targetDisplay = `.claude/skills/${spec.slug}/`;
26819
- if (existsSync32(targetDir) && !this.force) {
26930
+ if (existsSync29(targetDir) && !this.force) {
26820
26931
  this.printer.error(
26821
26932
  tx(TUTORIAL_TEXTS.alreadyExists, {
26822
26933
  glyph: errGlyph,
@@ -26842,7 +26953,7 @@ var TutorialCommand = class extends SmCommand {
26842
26953
  }
26843
26954
  try {
26844
26955
  rmSync2(targetDir, { recursive: true, force: true });
26845
- mkdirSync7(dirname19(targetDir), { recursive: true });
26956
+ mkdirSync6(dirname19(targetDir), { recursive: true });
26846
26957
  cpSync2(sourceDir, targetDir, { recursive: true });
26847
26958
  } catch (err) {
26848
26959
  this.printer.error(
@@ -26899,7 +27010,7 @@ function resolveSkillSourceDir(variant) {
26899
27010
  resolve39(here, "../cli/tutorial", spec.slug)
26900
27011
  ];
26901
27012
  for (const candidate of candidates) {
26902
- if (existsSync32(candidate) && statSync12(candidate).isDirectory()) {
27013
+ if (existsSync29(candidate) && statSync11(candidate).isDirectory()) {
26903
27014
  cachedSourceDirs.set(variant, candidate);
26904
27015
  return candidate;
26905
27016
  }
@@ -27076,7 +27187,7 @@ await lifecycleDispatcher.dispatch(
27076
27187
  process.exit(exitCode);
27077
27188
  function resolveBareDefault() {
27078
27189
  const ctx = defaultRuntimeContext();
27079
- if (existsSync33(defaultProjectDbPath(ctx))) {
27190
+ if (existsSync30(defaultProjectDbPath(ctx))) {
27080
27191
  return ["serve"];
27081
27192
  }
27082
27193
  const stderr = process.stderr;