@skill-map/cli 0.42.0 → 0.44.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.
Files changed (48) hide show
  1. package/dist/cli/tutorial/sm-master/SKILL.md +7 -9
  2. package/dist/cli/tutorial/sm-master/references/fixture-templates.md +1 -1
  3. package/dist/cli/tutorial/sm-master/references/tour-plugins.md +10 -9
  4. package/dist/cli/tutorial/sm-master/references/tour-settings.md +4 -4
  5. package/dist/cli/tutorial/sm-tutorial/SKILL.md +216 -299
  6. package/dist/cli.js +1015 -376
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.js +24 -18
  9. package/dist/index.js.map +1 -1
  10. package/dist/kernel/index.d.ts +24 -30
  11. package/dist/kernel/index.js +24 -18
  12. package/dist/kernel/index.js.map +1 -1
  13. package/dist/migrations/001_initial.sql +6 -11
  14. package/dist/ui/chunk-27WQPOXP.js +1 -0
  15. package/dist/ui/{chunk-DZBSELHN.js → chunk-43S72FTV.js} +1 -1
  16. package/dist/ui/chunk-555ST76V.js +1 -0
  17. package/dist/ui/{chunk-JPYAASHN.js → chunk-5AD5ZV4I.js} +3 -3
  18. package/dist/ui/{chunk-CFJBTDAA.js → chunk-5ITZXW3A.js} +1 -1
  19. package/dist/ui/chunk-A7PRWMQD.js +1021 -0
  20. package/dist/ui/chunk-DL5EA245.js +123 -0
  21. package/dist/ui/chunk-F7I6KMHX.js +1 -0
  22. package/dist/ui/{chunk-5GD2GBPS.js → chunk-I5AX4U2N.js} +28 -28
  23. package/dist/ui/chunk-IUZRAD7S.js +1 -0
  24. package/dist/ui/{chunk-XOHD5XWA.js → chunk-IYM26L3O.js} +1 -1
  25. package/dist/ui/{chunk-SBCO7ZSP.js → chunk-LGFABCIA.js} +1 -1
  26. package/dist/ui/{chunk-IUDL3NDH.js → chunk-MFLFIA7C.js} +1 -1
  27. package/dist/ui/chunk-MS6B7344.js +315 -0
  28. package/dist/ui/chunk-P3SNMV4X.js +2 -0
  29. package/dist/ui/chunk-PZQHB7GS.js +4 -0
  30. package/dist/ui/chunk-QDUSFOBE.js +1 -0
  31. package/dist/ui/{chunk-HEJCH7BA.js → chunk-QNTAOR2L.js} +5 -5
  32. package/dist/ui/{chunk-CR3AANNX.js → chunk-S4S5ZMXJ.js} +1 -1
  33. package/dist/ui/{chunk-5WJRN3LD.js → chunk-T3IVIRRJ.js} +1 -1
  34. package/dist/ui/{chunk-HP375T2O.js → chunk-VGPYYAVI.js} +1 -1
  35. package/dist/ui/chunk-X227ITGS.js +499 -0
  36. package/dist/ui/index.html +1 -1
  37. package/dist/ui/main-O3CWFYKV.js +3 -0
  38. package/migrations/001_initial.sql +6 -11
  39. package/package.json +11 -10
  40. package/dist/ui/chunk-HFPA56IM.js +0 -1
  41. package/dist/ui/chunk-HHPSCDLM.js +0 -315
  42. package/dist/ui/chunk-HWP3HM55.js +0 -123
  43. package/dist/ui/chunk-PZ6Q5AOT.js +0 -1
  44. package/dist/ui/chunk-XJL4DZ4M.js +0 -1
  45. package/dist/ui/chunk-YL6SWAFJ.js +0 -1024
  46. package/dist/ui/main-7VYTTJP7.js +0 -3
  47. /package/dist/ui/{chunk-C2YUQODZ.js → chunk-4SG4352Z.js} +0 -0
  48. /package/dist/ui/{chunk-VB56BUGO.js → chunk-WCABR6TI.js} +0 -0
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // cli/entry.ts
2
- import { existsSync as existsSync30 } from "fs";
2
+ import { existsSync as existsSync32 } from "fs";
3
3
  import { Builtins, Cli as Cli2 } from "clipanion";
4
4
 
5
5
  // kernel/adapters/in-memory-progress.ts
@@ -244,11 +244,11 @@ function bucketByKind(kind, instance, bag) {
244
244
  // plugins/claude/providers/claude/schemas/skill.schema.json
245
245
  var skill_schema_default = {
246
246
  $schema: "https://json-schema.org/draft/2020-12/schema",
247
- $id: "https://skill-map.dev/providers/claude/v1/frontmatter/skill.schema.json",
247
+ $id: "https://skill-map.ai/providers/claude/v1/frontmatter/skill.schema.json",
248
248
  title: "FrontmatterSkill",
249
249
  description: "Frontmatter shape for nodes classified as `skill` by the Claude Provider. Today identical to `command` \u2014 both extend the shared `skill-base.schema.json` per Anthropic's documented merger (https://code.claude.com/docs/en/skills.md \u2014 \"Custom commands have been merged into skills\"). The two are kept SPLIT (not aliased) because skill-map's registry differentiates them in `IProviderKind.ui` (separate label / icon / color) and `defaultRefreshAction`. Splitting also reserves room for future divergence when one kind diverges from the other. No skill-only fields today; `additionalProperties: true` so the file is ready for them.",
250
250
  allOf: [
251
- { $ref: "https://skill-map.dev/providers/claude/v1/frontmatter/skill-base.schema.json" }
251
+ { $ref: "https://skill-map.ai/providers/claude/v1/frontmatter/skill-base.schema.json" }
252
252
  ],
253
253
  type: "object",
254
254
  additionalProperties: true,
@@ -258,11 +258,11 @@ var skill_schema_default = {
258
258
  // plugins/claude/providers/claude/schemas/skill-base.schema.json
259
259
  var skill_base_schema_default = {
260
260
  $schema: "https://json-schema.org/draft/2020-12/schema",
261
- $id: "https://skill-map.dev/providers/claude/v1/frontmatter/skill-base.schema.json",
261
+ $id: "https://skill-map.ai/providers/claude/v1/frontmatter/skill-base.schema.json",
262
262
  title: "FrontmatterSkillBase",
263
263
  description: 'Shared frontmatter base for `skill` and `command` nodes per Anthropic\'s documented merger (https://code.claude.com/docs/en/skills.md \u2014 "Custom commands have been merged into skills"). Both kinds carry the same vendor frontmatter today; skill-map keeps them as distinct `IProviderKind`s in the registry (different UI presentation, different `defaultRefreshAction`) but extends the same base via `allOf` + `$ref` so the field catalog is single-sourced. Field naming is reproduced verbatim from Anthropic \u2014 a deliberate mix of kebab-case (`argument-hint`, `disable-model-invocation`, `user-invocable`, `allowed-tools`), snake_case (`when_to_use`), and camelCase. `additionalProperties: true` so future Anthropic additions flow through unchanged until this schema catches up.',
264
264
  allOf: [
265
- { $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
265
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
266
266
  ],
267
267
  type: "object",
268
268
  additionalProperties: true,
@@ -297,6 +297,13 @@ var skill_base_schema_default = {
297
297
  ],
298
298
  description: "Tools pre-approved for this skill (no per-use permission prompt). Argument-scoped patterns supported (`Bash(git add *)`)."
299
299
  },
300
+ "disallowed-tools": {
301
+ oneOf: [
302
+ { type: "string" },
303
+ { type: "array", items: { type: "string" } }
304
+ ],
305
+ description: "Denylist sibling of `allowed-tools`: tools removed from the available pool while this skill / command is active (the restriction clears on the next user message). Accepts a space- or comma-separated string or a YAML list. Source: https://code.claude.com/docs/en/skills.md."
306
+ },
300
307
  model: {
301
308
  type: "string",
302
309
  description: "Model alias (`sonnet`, `opus`, `haiku`), full Claude id, or the literal `inherit` to defer to the parent session's model."
@@ -337,11 +344,11 @@ var skill_base_schema_default = {
337
344
  // plugins/claude/providers/claude/schemas/agent.schema.json
338
345
  var agent_schema_default = {
339
346
  $schema: "https://json-schema.org/draft/2020-12/schema",
340
- $id: "https://skill-map.dev/providers/claude/v1/frontmatter/agent.schema.json",
347
+ $id: "https://skill-map.ai/providers/claude/v1/frontmatter/agent.schema.json",
341
348
  title: "FrontmatterAgent",
342
- description: "Frontmatter shape for nodes classified as `agent` by the Claude Provider. Mirrors Anthropic's documented agent frontmatter verbatim (https://code.claude.com/docs/en/agents.md): `name` and `description` come from the spec base; this schema adds the 14 vendor-specific fields. skill-map AGGREGATES the vendor spec, it does not curate it \u2014 keys are reproduced exactly as Anthropic publishes them (mix of camelCase and snake_case). `additionalProperties: true` so future Anthropic additions flow through unchanged until this schema catches up.",
349
+ description: "Frontmatter shape for nodes classified as `agent` by the Claude Provider. Mirrors Anthropic's documented agent frontmatter verbatim (https://code.claude.com/docs/en/sub-agents.md): `name` and `description` come from the spec base; this schema adds the 14 vendor-specific fields. skill-map AGGREGATES the vendor spec, it does not curate it \u2014 keys are reproduced exactly as Anthropic publishes them (mix of camelCase and snake_case). `additionalProperties: true` so future Anthropic additions flow through unchanged until this schema catches up.",
343
350
  allOf: [
344
- { $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
351
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
345
352
  ],
346
353
  type: "object",
347
354
  additionalProperties: true,
@@ -363,7 +370,7 @@ var agent_schema_default = {
363
370
  permissionMode: {
364
371
  type: "string",
365
372
  enum: ["default", "acceptEdits", "auto", "dontAsk", "bypassPermissions", "plan"],
366
- description: "How the agent handles permission prompts. See https://code.claude.com/docs/en/agents.md."
373
+ description: "How the agent handles permission prompts. See https://code.claude.com/docs/en/sub-agents.md."
367
374
  },
368
375
  maxTurns: {
369
376
  type: "integer",
@@ -418,11 +425,11 @@ var agent_schema_default = {
418
425
  // plugins/claude/providers/claude/schemas/command.schema.json
419
426
  var command_schema_default = {
420
427
  $schema: "https://json-schema.org/draft/2020-12/schema",
421
- $id: "https://skill-map.dev/providers/claude/v1/frontmatter/command.schema.json",
428
+ $id: "https://skill-map.ai/providers/claude/v1/frontmatter/command.schema.json",
422
429
  title: "FrontmatterCommand",
423
430
  description: "Frontmatter shape for nodes classified as `command` by the Claude Provider. Today identical to `skill` per Anthropic's documented merger (https://code.claude.com/docs/en/skills.md \u2014 \"Custom commands have been merged into skills\"). Both extend the shared `skill-base.schema.json` via the same `allOf` pattern. The two are kept SPLIT (not aliased) because skill-map's registry differentiates them in `IProviderKind.ui` (separate label / icon / color) and `defaultRefreshAction`. Splitting also reserves room for future divergence. No command-only fields today; `additionalProperties: true` so the file is ready for them.",
424
431
  allOf: [
425
- { $ref: "https://skill-map.dev/providers/claude/v1/frontmatter/skill-base.schema.json" }
432
+ { $ref: "https://skill-map.ai/providers/claude/v1/frontmatter/skill-base.schema.json" }
426
433
  ],
427
434
  type: "object",
428
435
  additionalProperties: true,
@@ -498,7 +505,7 @@ var claudeProvider = {
498
505
  icon: { kind: "pi", id: "pi-user" }
499
506
  },
500
507
  // `frontmatter.name` is the documented canonical identifier
501
- // (https://code.claude.com/docs/en/agents.md); `filename-basename`
508
+ // (https://code.claude.com/docs/en/sub-agents.md); `filename-basename`
502
509
  // is a graceful fallback for agents authored without an explicit
503
510
  // `name:` field, the file at `.claude/agents/<id>.md` resolves
504
511
  // `@<id>` even when frontmatter is partial.
@@ -905,75 +912,77 @@ var antigravityProvider = {
905
912
  classify() {
906
913
  return null;
907
914
  },
908
- // Seed catalog, PROVISIONAL, derived from the Gemini CLI slash-command
909
- // surface. The Google Developers Blog post that announced the Antigravity
910
- // CLI rollout on 2026-05-19 states verbatim: "The Antigravity CLI fully
911
- // replaces the Gemini CLI ... preserves the most critical Gemini CLI
912
- // features: Agent Skills, Hooks, Subagents, and Extensions (now rebranded
913
- // as Antigravity plugins) ... shares the same agent harness as Antigravity
914
- // 2.0, the new Antigravity desktop application." Since the four feature
915
- // pillars carry over 1:1 and the agent harness is shared, the operator's
916
- // built-in slash-command vocabulary is overwhelmingly likely to be
917
- // Gemini CLI's. We mirror the full 38-verb Gemini CLI catalog (plus its
918
- // four documented aliases: `dir`, `?`, `exit`, `bashes`) so a user file
919
- // that names a skill / command `help`, `clear`, `mcp`, etc. is flagged
920
- // immediately by `core/name-reserved` once the lens activates the catalog.
915
+ // Built-in slash-command catalog, captured verbatim from `agy /help`
916
+ // (Antigravity CLI v1.0.3). This REPLACES the earlier provisional list
917
+ // that mirrored Gemini CLI's verbs: `agy` ships its own surface. It
918
+ // DROPPED Gemini-only verbs (`vim`, `theme`, `terminal-setup`,
919
+ // `setup-github`, `bashes`, `shells`, `policies`, `extensions`, `about`,
920
+ // `auth`, `bug`, `chat`, `compress`, `docs`, `editor`, `ide`, `init`,
921
+ // `memory`, `restore`, `stats`, `tools`, `upgrade`, `?`, `dir`) and
922
+ // ADDED agent-first ones (`goal`, `grill-me`, `schedule`, `fast`, `btw`,
923
+ // `artifact`, `context`, `diff`, `fork`, `tasks`, `add-dir`, `credits`,
924
+ // `feedback`, `logout`, `open`, `planning`, `rename`, `statusline`,
925
+ // `title`, `usage`). Both the 35 primary verbs and the 8 documented
926
+ // aliases (`new`, `settings`, `quit`, `branch`, `switch`, `conversation`,
927
+ // `undo`, `quota`) are reserved: a user skill named after either is
928
+ // silently shadowed by the built-in once the catalog activates.
921
929
  //
922
- // The catalog is INACTIVE today: the analyzer keys on `node.provider`
923
- // and this Provider's `classify()` returns `null` for every path, so
924
- // no node carries `provider: 'antigravity'`. The seed lives here so
925
- // the day Antigravity grows its own on-disk kind (e.g. a vendor-specific
926
- // `.antigravity/commands/` directory beyond the open-standard
927
- // `.agents/skills/`) the catalog is already in place with no migration.
930
+ // Declared under the `skill` kind (NOT `command`): Antigravity has no
931
+ // vendor-specific command directory, its user slash-commands are skills
932
+ // (`.agents/skills/<name>/SKILL.md`, owned by the universal `agent-skills`
933
+ // Provider). The catalog is ACTIVE via the LENS SCOPE in
934
+ // `buildReservedNodePaths` (spec/architecture.md §Provider ·
935
+ // reservedNames): when `activeProvider === 'antigravity'` the orchestrator
936
+ // lends this `skill` catalog to `agent-skills` skill nodes, so a user
937
+ // `.agents/skills/goal/SKILL.md` is flagged because `/goal` is built-in.
928
938
  //
929
- // **Reconciliation marker**: the day Google's docs at
930
- // antigravity.google/docs publishes the authoritative slash-command
931
- // reference, replace this comment + array with the official list (and
932
- // bump the file's leading docblock to cite the new source URL).
939
+ // **Reconciliation marker**: re-capture from `agy /help` on each major
940
+ // Antigravity CLI release and bump the cited version above.
933
941
  reservedNames: {
934
- command: [
935
- "?",
936
- "about",
942
+ skill: [
943
+ "add-dir",
937
944
  "agents",
938
- "auth",
939
- "bashes",
940
- "bug",
941
- "chat",
945
+ "artifact",
946
+ "branch",
947
+ "btw",
948
+ "changelog",
942
949
  "clear",
943
- "commands",
944
- "compress",
950
+ "config",
951
+ "context",
952
+ "conversation",
945
953
  "copy",
946
- "dir",
947
- "directory",
948
- "docs",
949
- "editor",
954
+ "credits",
955
+ "diff",
950
956
  "exit",
951
- "extensions",
957
+ "fast",
958
+ "feedback",
959
+ "fork",
960
+ "goal",
961
+ "grill-me",
952
962
  "help",
953
963
  "hooks",
954
- "ide",
955
- "init",
964
+ "keybindings",
965
+ "logout",
956
966
  "mcp",
957
- "memory",
958
967
  "model",
968
+ "new",
969
+ "open",
959
970
  "permissions",
960
- "plan",
961
- "policies",
962
- "privacy",
971
+ "planning",
963
972
  "quit",
964
- "restore",
973
+ "quota",
974
+ "rename",
965
975
  "resume",
966
976
  "rewind",
977
+ "schedule",
967
978
  "settings",
968
- "setup-github",
969
- "shells",
970
979
  "skills",
971
- "stats",
972
- "terminal-setup",
973
- "theme",
974
- "tools",
975
- "upgrade",
976
- "vim"
980
+ "statusline",
981
+ "switch",
982
+ "tasks",
983
+ "title",
984
+ "undo",
985
+ "usage"
977
986
  ]
978
987
  }
979
988
  };
@@ -981,11 +990,11 @@ var antigravityProvider = {
981
990
  // plugins/openai/providers/openai/schemas/agent.schema.json
982
991
  var agent_schema_default2 = {
983
992
  $schema: "https://json-schema.org/draft/2020-12/schema",
984
- $id: "https://skill-map.dev/providers/openai/v1/frontmatter/agent.schema.json",
993
+ $id: "https://skill-map.ai/providers/openai/v1/frontmatter/agent.schema.json",
985
994
  title: "FrontmatterCodexAgent",
986
995
  description: "Frontmatter shape for nodes classified as `agent` by the OpenAI Codex Provider. Codex sub-agents live as TOML files under `.codex/agents/<name>.toml`; the entire file IS the agent definition (no markdown body). The TOML parser feeds the parsed root object into `frontmatter`, so this schema validates the same shape skill-map's other providers carry on per-kind frontmatter. Mirrors Codex's documented sub-agent fields (https://github.com/openai/codex) with `additionalProperties: true` so future additions flow through unchanged.",
987
996
  allOf: [
988
- { $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
997
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
989
998
  ],
990
999
  type: "object",
991
1000
  additionalProperties: true,
@@ -1088,15 +1097,34 @@ var openaiProvider = {
1088
1097
  // plugins/agent-skills/providers/agent-skills/schemas/skill.schema.json
1089
1098
  var skill_schema_default2 = {
1090
1099
  $schema: "https://json-schema.org/draft/2020-12/schema",
1091
- $id: "https://skill-map.dev/providers/agent-skills/v1/frontmatter/skill.schema.json",
1100
+ $id: "https://skill-map.ai/providers/agent-skills/v1/frontmatter/skill.schema.json",
1092
1101
  title: "FrontmatterAgentSkillsSkill",
1093
- description: "Frontmatter shape for nodes classified as `skill` by the neutral `agent-skills` Provider \u2014 Agent Skills delivered as `SKILL.md` files at the open-standard path `.agents/skills/<name>/SKILL.md`. Jointly adopted by Anthropic, OpenAI (Codex), and Google (Gemini); the path is vendor-neutral so no single Provider should own it. Required fields are `name` and `description` (from spec base); per the open-standard contract no other fields are mandated.",
1102
+ description: "Frontmatter shape for nodes classified as `skill` by the neutral `agent-skills` Provider, Agent Skills delivered as `SKILL.md` files at the open-standard path `.agents/skills/<name>/SKILL.md`. Jointly adopted by Anthropic, OpenAI (Codex), and Google (Gemini); the path is vendor-neutral so no single Provider should own it. Required fields are `name` and `description` (from spec base). The standard's optional frontmatter fields are declared below, mirrored verbatim from https://agentskills.io/specification: `license`, `compatibility`, `metadata`, and the experimental `allowed-tools`. `additionalProperties: true` so any future standard field flows through unchanged until this schema catches up.",
1094
1103
  allOf: [
1095
- { $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
1104
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
1096
1105
  ],
1097
1106
  type: "object",
1098
1107
  additionalProperties: true,
1099
- properties: {}
1108
+ properties: {
1109
+ license: {
1110
+ type: "string",
1111
+ description: "License applied to the skill: a license name (e.g. `Apache-2.0`) or a reference to a bundled license file. Source: https://agentskills.io/specification."
1112
+ },
1113
+ compatibility: {
1114
+ type: "string",
1115
+ maxLength: 500,
1116
+ description: "Environment requirements (intended product, required system packages, network access, etc.). Most skills omit it. Max 500 characters. Source: https://agentskills.io/specification."
1117
+ },
1118
+ metadata: {
1119
+ type: "object",
1120
+ additionalProperties: { type: "string" },
1121
+ description: "Arbitrary string-keyed, string-valued map for client-defined metadata not covered by the standard. Source: https://agentskills.io/specification."
1122
+ },
1123
+ "allowed-tools": {
1124
+ type: "string",
1125
+ description: "Space-separated list of pre-approved tools the skill may run (e.g. `Bash(git:*) Read`). Experimental in the open standard; support varies between agent implementations. Source: https://agentskills.io/specification."
1126
+ }
1127
+ }
1100
1128
  };
1101
1129
 
1102
1130
  // plugins/agent-skills/providers/agent-skills/index.ts
@@ -1150,11 +1178,11 @@ var agentSkillsProvider = {
1150
1178
  // plugins/core/providers/core-markdown/schemas/markdown.schema.json
1151
1179
  var markdown_schema_default = {
1152
1180
  $schema: "https://json-schema.org/draft/2020-12/schema",
1153
- $id: "https://skill-map.dev/providers/core/v1/frontmatter/markdown.schema.json",
1181
+ $id: "https://skill-map.ai/providers/core/v1/frontmatter/markdown.schema.json",
1154
1182
  title: "FrontmatterMarkdown",
1155
1183
  description: "Frontmatter shape for nodes classified as `markdown` by the built-in `core/markdown` Provider, the universal fallback for any markdown file no vendor-specific Provider claims (Claude, OpenAI Codex, agent-skills, plus the metadata-only Antigravity bundle). The kind is named after the format because the file is a generic fallback; format-named kinds apply only as the generic fallback, a TOML file that IS a Codex agent still classifies as `agent`, not `toml`. Extends the spec's universal `frontmatter/base.schema.json` via $ref-by-$id with no additional fields. Ownership relocated from the Claude Provider in spec 0.18.0, markdown is provider-agnostic and lives under `core` so adding new vendor Providers does not require choosing which one owns the universal fallback.",
1156
1184
  allOf: [
1157
- { $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
1185
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
1158
1186
  ],
1159
1187
  type: "object",
1160
1188
  additionalProperties: true
@@ -2935,10 +2963,10 @@ function buildSchemaValidators() {
2935
2963
  hook: "extension-hook"
2936
2964
  };
2937
2965
  const pluginManifestValidator = ajv.compile({
2938
- $ref: "https://skill-map.dev/spec/v0/plugins-registry.schema.json#/$defs/PluginManifest"
2966
+ $ref: "https://skill-map.ai/spec/v0/plugins-registry.schema.json#/$defs/PluginManifest"
2939
2967
  });
2940
2968
  const contributionValidators = /* @__PURE__ */ new Map();
2941
- const VIEW_SLOTS_ID = "https://skill-map.dev/spec/v0/view-slots.schema.json";
2969
+ const VIEW_SLOTS_ID = "https://skill-map.ai/spec/v0/view-slots.schema.json";
2942
2970
  function getContributionValidator(slot) {
2943
2971
  if (!KNOWN_SLOT_NAMES.has(slot)) return null;
2944
2972
  const existing = contributionValidators.get(slot);
@@ -3920,11 +3948,11 @@ var UPDATE_CHECK_TEXTS = {
3920
3948
  // package.json
3921
3949
  var package_default = {
3922
3950
  name: "@skill-map/cli",
3923
- version: "0.42.0",
3951
+ version: "0.44.0",
3924
3952
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
3925
3953
  license: "MIT",
3926
3954
  type: "module",
3927
- homepage: "https://skill-map.dev",
3955
+ homepage: "https://skill-map.ai",
3928
3956
  repository: {
3929
3957
  type: "git",
3930
3958
  url: "git+https://github.com/crystian/skill-map.git",
@@ -3971,25 +3999,25 @@ var package_default = {
3971
3999
  typecheck: "tsc --noEmit",
3972
4000
  lint: "eslint .",
3973
4001
  "lint:fix": "eslint . --fix",
3974
- reference: "node scripts/build-reference.js",
3975
- "reference:check": "node scripts/build-reference.js --check",
3976
4002
  "build-built-ins": "node ../scripts/generate-built-ins.js",
3977
4003
  "built-ins:check": "node ../scripts/generate-built-ins.js --check",
3978
4004
  prebuild: "pnpm build-built-ins",
3979
4005
  validate: "pnpm validate:compile && pnpm validate:test",
3980
- "validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm reference:check && pnpm built-ins:check",
4006
+ "validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm built-ins:check",
3981
4007
  "validate:test": "pnpm test:ci",
3982
4008
  pretest: "tsup",
3983
4009
  "pretest:coverage": "tsup",
3984
4010
  "pretest:coverage:html": "tsup",
3985
- test: "tsc --noEmit && node --import tsx --test --test-reporter=spec '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
3986
- "test:ci": "node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
3987
- "test:coverage": "tsc --noEmit && SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
3988
- "test:coverage:html": "tsc --noEmit && SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4011
+ test: "tsc --noEmit && SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4012
+ "test:ci": "FORCE_COLOR=1 SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4013
+ "test:spec": "SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=spec '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4014
+ "test:coverage": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4015
+ "test:coverage:html": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
3989
4016
  clean: "rm -rf dist coverage"
3990
4017
  },
3991
4018
  dependencies: {
3992
4019
  "@hono/node-server": "2.0.1",
4020
+ "@sentry/node": "10.55.0",
3993
4021
  "@skill-map/spec": "workspace:*",
3994
4022
  ajv: "8.18.0",
3995
4023
  "ajv-formats": "3.0.1",
@@ -4000,6 +4028,7 @@ var package_default = {
4000
4028
  "js-tiktoken": "1.0.21",
4001
4029
  "js-yaml": "4.1.1",
4002
4030
  kysely: "0.28.17",
4031
+ "posthog-node": "5.35.6",
4003
4032
  semver: "7.7.4",
4004
4033
  "smol-toml": "1.6.1",
4005
4034
  typanion: "3.14.0",
@@ -4070,6 +4099,7 @@ function ansiFor(opts) {
4070
4099
  }
4071
4100
 
4072
4101
  // cli/util/user-settings-store.ts
4102
+ import { randomUUID } from "crypto";
4073
4103
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4074
4104
  import { homedir } from "os";
4075
4105
  import { join as join2 } from "path";
@@ -4178,13 +4208,13 @@ function userSettingsFilePath() {
4178
4208
  return join2(homedir(), SKILL_MAP_DIR, FILENAME);
4179
4209
  }
4180
4210
  function defaultSettings() {
4181
- return { schemaVersion: SCHEMA_VERSION, updateCheck: {} };
4211
+ return { schemaVersion: SCHEMA_VERSION, updateCheck: {}, telemetry: {} };
4182
4212
  }
4183
4213
  function readUserSettings() {
4184
4214
  const parsed = readParsedFile();
4185
4215
  if (parsed === null) return defaultSettings();
4186
4216
  const validated = validateOrDefault(parsed);
4187
- return backfillUpdateCheck(validated);
4217
+ return backfillSubObjects(validated);
4188
4218
  }
4189
4219
  function readParsedFile() {
4190
4220
  const path = userSettingsFilePath();
@@ -4206,11 +4236,12 @@ function validateOrDefault(parsed) {
4206
4236
  if (!result.ok) return defaultSettings();
4207
4237
  return result.data;
4208
4238
  }
4209
- function backfillUpdateCheck(settings) {
4210
- if (settings.updateCheck === void 0) {
4211
- return { ...settings, updateCheck: {} };
4212
- }
4213
- return settings;
4239
+ function backfillSubObjects(settings) {
4240
+ return {
4241
+ ...settings,
4242
+ updateCheck: settings.updateCheck ?? {},
4243
+ telemetry: settings.telemetry ?? {}
4244
+ };
4214
4245
  }
4215
4246
  function writeUserSettings(patch) {
4216
4247
  const dir = join2(homedir(), SKILL_MAP_DIR);
@@ -4232,14 +4263,49 @@ function isUpdateCheckEnabled() {
4232
4263
  const settings = readUserSettings();
4233
4264
  return settings.updateCheck?.enabled !== false;
4234
4265
  }
4266
+ function isErrorTelemetryEnabled() {
4267
+ const settings = readUserSettings();
4268
+ return settings.telemetry?.errorsEnabled === true;
4269
+ }
4270
+ function isUsageCliTelemetryEnabled() {
4271
+ const settings = readUserSettings();
4272
+ return settings.telemetry?.usageCliEnabled === true;
4273
+ }
4274
+ function isUsageUiTelemetryEnabled() {
4275
+ const settings = readUserSettings();
4276
+ return settings.telemetry?.usageUiEnabled === true;
4277
+ }
4278
+ function readAnonymousId() {
4279
+ const settings = readUserSettings();
4280
+ return settings.telemetry?.anonymousId ?? null;
4281
+ }
4282
+ function ensureAnonymousId(generate = () => randomUUID()) {
4283
+ const existing = readAnonymousId();
4284
+ if (existing !== null && existing !== "") return existing;
4285
+ const id = generate();
4286
+ writeUserSettings({ telemetry: { anonymousId: id } });
4287
+ return id;
4288
+ }
4289
+ function hasTelemetryPromptBeenShown() {
4290
+ const settings = readUserSettings();
4291
+ return typeof settings.telemetry?.promptedAt === "number";
4292
+ }
4293
+ function hasSeenFirstRun() {
4294
+ const settings = readUserSettings();
4295
+ return typeof settings.telemetry?.firstRunAt === "number";
4296
+ }
4235
4297
  function mergeSettings(current, patch) {
4236
4298
  const merged = {
4237
4299
  schemaVersion: SCHEMA_VERSION,
4238
- updateCheck: { ...current.updateCheck ?? {} }
4300
+ updateCheck: { ...current.updateCheck ?? {} },
4301
+ telemetry: { ...current.telemetry ?? {} }
4239
4302
  };
4240
4303
  if (patch.updateCheck) {
4241
4304
  merged.updateCheck = { ...merged.updateCheck, ...patch.updateCheck };
4242
4305
  }
4306
+ if (patch.telemetry) {
4307
+ merged.telemetry = { ...merged.telemetry, ...patch.telemetry };
4308
+ }
4243
4309
  return merged;
4244
4310
  }
4245
4311
  function tryLoadValidators() {
@@ -4352,40 +4418,40 @@ var updateCheckHook = {
4352
4418
  };
4353
4419
 
4354
4420
  // plugins/built-ins.ts
4355
- var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.42.0" };
4356
- var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.42.0" };
4357
- var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.42.0" };
4358
- var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.42.0" };
4359
- var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.42.0" };
4360
- var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.42.0" };
4361
- var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.42.0" };
4362
- var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.42.0" };
4363
- var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.42.0" };
4364
- var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.42.0" };
4365
- var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.42.0" };
4366
- var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.42.0" };
4367
- var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.42.0" };
4368
- var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.42.0" };
4369
- var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.42.0" };
4370
- var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.42.0" };
4371
- var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.42.0" };
4372
- var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.42.0" };
4373
- var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.42.0" };
4374
- var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.42.0" };
4375
- var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.42.0" };
4376
- var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.42.0" };
4377
- var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.42.0" };
4378
- var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.42.0" };
4379
- var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.42.0" };
4380
- var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.42.0" };
4381
- var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.42.0" };
4382
- var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.42.0" };
4383
- var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.42.0" };
4384
- var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.42.0" };
4385
- var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.42.0" };
4386
- var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.42.0" };
4387
- var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.42.0" };
4388
- var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.42.0" };
4421
+ var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.44.0" };
4422
+ var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.44.0" };
4423
+ var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.44.0" };
4424
+ var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.44.0" };
4425
+ var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.44.0" };
4426
+ var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.44.0" };
4427
+ var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.44.0" };
4428
+ var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.44.0" };
4429
+ var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.44.0" };
4430
+ var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.44.0" };
4431
+ var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.44.0" };
4432
+ var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.44.0" };
4433
+ var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.44.0" };
4434
+ var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.44.0" };
4435
+ var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.44.0" };
4436
+ var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.44.0" };
4437
+ var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.44.0" };
4438
+ var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.44.0" };
4439
+ var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.44.0" };
4440
+ var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.44.0" };
4441
+ var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.44.0" };
4442
+ var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.44.0" };
4443
+ var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.44.0" };
4444
+ var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.44.0" };
4445
+ var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.44.0" };
4446
+ var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.44.0" };
4447
+ var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.44.0" };
4448
+ var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.44.0" };
4449
+ var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.44.0" };
4450
+ var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.44.0" };
4451
+ var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.44.0" };
4452
+ var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.44.0" };
4453
+ var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.44.0" };
4454
+ var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.44.0" };
4389
4455
  var builtInBundles = [
4390
4456
  {
4391
4457
  id: "claude",
@@ -4917,6 +4983,341 @@ function defaultRuntimeContext() {
4917
4983
  return { cwd: process.cwd() };
4918
4984
  }
4919
4985
 
4986
+ // cli/telemetry/first-run-prompt.ts
4987
+ import { createInterface } from "readline/promises";
4988
+
4989
+ // cli/i18n/telemetry.texts.ts
4990
+ var TELEMETRY_PROMPT_TEXTS = {
4991
+ // Header + body of the one-time question (glyph `ℹ` added by the renderer).
4992
+ title: "Anonymous error and usage reporting",
4993
+ intro: [
4994
+ "skill-map can send anonymous reports to help fix bugs and decide what to",
4995
+ "build next. No personal information is ever sent: not your files or their",
4996
+ "contents, not your folder or home paths, not your settings."
4997
+ ],
4998
+ question: "Enable anonymous error and usage reporting?",
4999
+ answerYes: "[Y]es",
5000
+ answerNo: "[n]o",
5001
+ answerDetails: "[d]etails",
5002
+ // Disclosure shown on `[d]etails`, then the question is re-asked.
5003
+ detailsSentTitle: "Sent, only if you turn this on",
5004
+ detailsSent: [
5005
+ "crashes: error name, code, message, and a path-stripped stack trace",
5006
+ "usage: the command you ran and its flag names (never their values)",
5007
+ "usage: which built-in extractors ran, and which UI views you opened",
5008
+ "cli version, node major, os, arch, and a random anonymous id"
5009
+ ],
5010
+ detailsNeverTitle: "Never sent",
5011
+ detailsNever: [
5012
+ "your files, their contents, frontmatter, annotations",
5013
+ "absolute paths, hostname, your username, ip address",
5014
+ "your settings values or any flag values"
5015
+ ],
5016
+ detailsHint: "Turn error reports, CLI usage, and UI usage on or off independently in Settings, or force everything off with SKILL_MAP_TELEMETRY=0.",
5017
+ // Confirmation lines (glyph added by the renderer).
5018
+ enabled: "Telemetry on. Thanks. Turn error reports, CLI usage, and UI usage off independently in Settings.",
5019
+ disabled: "Telemetry off. You can turn any of it on later in Settings."
5020
+ };
5021
+
5022
+ // public-config.ts
5023
+ var SENTRY_DSN_NODE = "https://8b73dbb2563da4b77def12ce5ee46e75@o4511475590037504.ingest.de.sentry.io/4511475708002384";
5024
+ var POSTHOG_KEY_NODE = "phc_vMX3PcNeDsacWNg2hYEbKVXDijSWcjKFzabCkzU7RNEr";
5025
+ var POSTHOG_HOST = "https://eu.i.posthog.com";
5026
+
5027
+ // core/telemetry/scrub.ts
5028
+ var HOME_PLACEHOLDER = "<HOME>";
5029
+ var STRIPPED_ENVELOPE_KEYS = ["server_name", "user"];
5030
+ var HOME_PATTERNS = [
5031
+ // Windows: drive + Users + one user segment (back- or forward-slash).
5032
+ /[A-Za-z]:[\\/]Users[\\/][^\\/\s:*?"<>|]+/g,
5033
+ // POSIX user homes: /home/<user> or macOS /Users/<user>.
5034
+ /\/(?:home|Users)\/[^/\s:]+/g,
5035
+ // Root account home.
5036
+ /\/root(?=\/|\b)/g
5037
+ ];
5038
+ function scrubString(value) {
5039
+ let out = value;
5040
+ for (const pattern of HOME_PATTERNS) {
5041
+ out = out.replace(pattern, HOME_PLACEHOLDER);
5042
+ }
5043
+ return out;
5044
+ }
5045
+ function scrubEvent(event) {
5046
+ const walked = walk(event);
5047
+ if (walked !== null && typeof walked === "object" && !Array.isArray(walked)) {
5048
+ const record = walked;
5049
+ for (const key of STRIPPED_ENVELOPE_KEYS) {
5050
+ if (key in record) delete record[key];
5051
+ }
5052
+ }
5053
+ return walked;
5054
+ }
5055
+ function walk(value) {
5056
+ if (typeof value === "string") return scrubString(value);
5057
+ if (Array.isArray(value)) return value.map((item) => walk(item));
5058
+ if (value !== null && typeof value === "object") {
5059
+ const out = {};
5060
+ for (const [key, child] of Object.entries(value)) {
5061
+ out[key] = walk(child);
5062
+ }
5063
+ return out;
5064
+ }
5065
+ return value;
5066
+ }
5067
+
5068
+ // cli/telemetry/telemetry-env.ts
5069
+ var TELEMETRY_ENV_VAR = "SKILL_MAP_TELEMETRY_ENV";
5070
+ function resolveTelemetryEnv() {
5071
+ const raw = process.env[TELEMETRY_ENV_VAR];
5072
+ if (raw === void 0 || raw.trim() === "" || raw === "prod" || raw === "production") {
5073
+ return "prod";
5074
+ }
5075
+ return "dev";
5076
+ }
5077
+
5078
+ // cli/telemetry/sentry-init.ts
5079
+ var KILL_SWITCH_ENV = "SKILL_MAP_TELEMETRY";
5080
+ var sdk = null;
5081
+ function isCliDsnConfigured() {
5082
+ return SENTRY_DSN_NODE !== "";
5083
+ }
5084
+ function isTelemetryForcedOff() {
5085
+ return process.env[KILL_SWITCH_ENV] === "0";
5086
+ }
5087
+ function isTelemetryActive(dsn) {
5088
+ if (isTelemetryForcedOff()) return false;
5089
+ if (dsn === "") return false;
5090
+ return isErrorTelemetryEnabled();
5091
+ }
5092
+ async function initSentryCli(version, loadSdk = () => import("@sentry/node")) {
5093
+ if (sdk) return;
5094
+ if (!isTelemetryActive(SENTRY_DSN_NODE)) return;
5095
+ const Sentry = await loadSdk();
5096
+ Sentry.init({
5097
+ dsn: SENTRY_DSN_NODE,
5098
+ release: `@skill-map/cli@${version}`,
5099
+ environment: resolveTelemetryEnv(),
5100
+ // CLI and BFF share one Sentry project; the `surface` tag tells their
5101
+ // events apart in the shared issue stream.
5102
+ initialScope: { tags: { surface: "cli" } },
5103
+ // Errors only: do NOT register the OpenTelemetry ESM loader hooks. We
5104
+ // run no tracing / auto-instrumentation, and the hook calls the
5105
+ // deprecated `module.register()` (a `DEP0205` warning on Node >= 26 that
5106
+ // would print on every telemetry-on invocation). Disabling it keeps
5107
+ // stderr clean and skips the loader's startup cost.
5108
+ registerEsmLoaderHooks: false,
5109
+ defaultIntegrations: false,
5110
+ integrations: [
5111
+ Sentry.onUncaughtExceptionIntegration(),
5112
+ Sentry.onUnhandledRejectionIntegration()
5113
+ ],
5114
+ tracesSampleRate: 0,
5115
+ sendDefaultPii: false,
5116
+ beforeSend: (event) => scrubEvent(event)
5117
+ });
5118
+ sdk = Sentry;
5119
+ }
5120
+ function setTelemetryVerbTag(verb) {
5121
+ if (!sdk || verb === void 0 || verb === "") return;
5122
+ sdk.setTag("verb", verb);
5123
+ }
5124
+ async function closeSentryCli(timeoutMs = 2e3) {
5125
+ if (!sdk) return;
5126
+ try {
5127
+ await sdk.close(timeoutMs);
5128
+ } catch {
5129
+ }
5130
+ }
5131
+
5132
+ // cli/telemetry/usage-collector.ts
5133
+ var BUILT_IN_PLUGIN_IDS = /* @__PURE__ */ new Set([
5134
+ "claude",
5135
+ "antigravity",
5136
+ "openai",
5137
+ "agent-skills",
5138
+ "core"
5139
+ ]);
5140
+ var EXTERNAL_PLUGIN_PLACEHOLDER = "external_plugin";
5141
+ function qualifyExtensionForUsage(qualifiedId2) {
5142
+ const slash = qualifiedId2.indexOf("/");
5143
+ if (slash <= 0) return EXTERNAL_PLUGIN_PLACEHOLDER;
5144
+ const pluginId = qualifiedId2.slice(0, slash);
5145
+ return BUILT_IN_PLUGIN_IDS.has(pluginId) ? qualifiedId2 : EXTERNAL_PLUGIN_PLACEHOLDER;
5146
+ }
5147
+ function buildScanExtensionSet(executedExtensionIds) {
5148
+ const out = /* @__PURE__ */ new Set();
5149
+ for (const id of executedExtensionIds) {
5150
+ out.add(qualifyExtensionForUsage(id));
5151
+ }
5152
+ return [...out].sort();
5153
+ }
5154
+ function extractFlagNames(args2) {
5155
+ const out = /* @__PURE__ */ new Set();
5156
+ for (const arg of args2) {
5157
+ if (!arg.startsWith("-")) continue;
5158
+ const name = arg.replace(/^-+/, "").split("=")[0];
5159
+ if (name !== void 0 && name !== "") out.add(name);
5160
+ }
5161
+ return [...out].sort();
5162
+ }
5163
+ function cliVerbEventName(verb, knownVerbs) {
5164
+ return `cli.${knownVerbs.has(verb) ? verb : "unknown"}`;
5165
+ }
5166
+ function buildCliVerbProperties(flagNames, extensions) {
5167
+ const flags = [...new Set(flagNames)].sort();
5168
+ return extensions ? { flags, extensions } : { flags };
5169
+ }
5170
+ function envUsageProps(cliVersion) {
5171
+ return {
5172
+ cli_version: cliVersion,
5173
+ node_major: Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10),
5174
+ os: process.platform,
5175
+ arch: process.arch,
5176
+ environment: resolveTelemetryEnv()
5177
+ };
5178
+ }
5179
+
5180
+ // cli/telemetry/posthog-init.ts
5181
+ var client = null;
5182
+ function isUsageKeyConfigured() {
5183
+ return POSTHOG_KEY_NODE !== "";
5184
+ }
5185
+ function isUsageCliTelemetryActive(key) {
5186
+ if (isTelemetryForcedOff()) return false;
5187
+ if (key === "") return false;
5188
+ return isUsageCliTelemetryEnabled();
5189
+ }
5190
+ function scrubUsageEvent(event) {
5191
+ return event === null ? null : scrubEvent(event);
5192
+ }
5193
+ async function initUsageCli(loadSdk = () => import("posthog-node")) {
5194
+ if (client) return;
5195
+ if (!isUsageCliTelemetryActive(POSTHOG_KEY_NODE)) return;
5196
+ const { PostHog } = await loadSdk();
5197
+ client = new PostHog(POSTHOG_KEY_NODE, {
5198
+ host: POSTHOG_HOST,
5199
+ // Second line of defense behind the project-level IP drop: the client
5200
+ // never attaches an IP or geo, and never autocaptures.
5201
+ disableGeoip: true,
5202
+ before_send: scrubUsageEvent
5203
+ });
5204
+ }
5205
+ function captureUsage(event, properties) {
5206
+ if (client === null) return;
5207
+ const distinctId = readAnonymousId();
5208
+ if (distinctId === null) return;
5209
+ client.capture({
5210
+ distinctId,
5211
+ event,
5212
+ properties: { ...envUsageProps(VERSION), ...properties }
5213
+ });
5214
+ }
5215
+ var pendingScanExtensions = null;
5216
+ function setScanExtensions(extensions) {
5217
+ pendingScanExtensions = extensions;
5218
+ }
5219
+ function captureCliInvocation(verb, flagNames, knownVerbs) {
5220
+ const extensions = pendingScanExtensions;
5221
+ pendingScanExtensions = null;
5222
+ captureUsage(cliVerbEventName(verb, knownVerbs), buildCliVerbProperties(flagNames, extensions));
5223
+ }
5224
+ async function flushUsageCli(timeoutMs = 2e3) {
5225
+ if (client === null) return;
5226
+ try {
5227
+ await client.shutdown(timeoutMs);
5228
+ } catch {
5229
+ }
5230
+ }
5231
+
5232
+ // cli/telemetry/first-run-prompt.ts
5233
+ function interpretConsentAnswer(raw) {
5234
+ const value = raw.trim().toLowerCase();
5235
+ if (value === "n" || value === "no") return "no";
5236
+ if (value === "d" || value === "details") return "details";
5237
+ return "yes";
5238
+ }
5239
+ function isPromptEligible(opts) {
5240
+ return opts.dsnConfigured && opts.isTTY && !opts.isCI && !opts.forcedOff && !opts.alreadyPrompted;
5241
+ }
5242
+ function liveGateInputs(stdout) {
5243
+ return {
5244
+ dsnConfigured: isCliDsnConfigured() || isUsageKeyConfigured(),
5245
+ isTTY: stdout.isTTY === true,
5246
+ isCI: Boolean(process.env["CI"]),
5247
+ forcedOff: isTelemetryForcedOff(),
5248
+ alreadyPrompted: hasTelemetryPromptBeenShown()
5249
+ };
5250
+ }
5251
+ function renderConsent(ansi) {
5252
+ const t = TELEMETRY_PROMPT_TEXTS;
5253
+ const answerLine = ` ${t.question} ${ansi.bold(t.answerYes)} ${t.answerNo} ${ansi.dim(t.answerDetails)} `;
5254
+ return {
5255
+ question: [
5256
+ ` ${ansi.cyan("\u2139")} ${ansi.bold(t.title)}`,
5257
+ ...t.intro.map((line) => ` ${line}`),
5258
+ "",
5259
+ answerLine
5260
+ ].join("\n"),
5261
+ reprompt: answerLine,
5262
+ details: [
5263
+ "",
5264
+ ` ${t.detailsSentTitle}`,
5265
+ ...t.detailsSent.map((line) => ` ${ansi.dim("\u2192")} ${line}`),
5266
+ ` ${t.detailsNeverTitle}`,
5267
+ ...t.detailsNever.map((line) => ` ${ansi.red("\u2715")} ${line}`),
5268
+ "",
5269
+ ` ${ansi.dim(t.detailsHint)}`,
5270
+ ""
5271
+ ].join("\n"),
5272
+ enabled: ` ${ansi.green("\u2713")} ${t.enabled}
5273
+ `,
5274
+ disabled: ` ${ansi.cyan("\u2139")} ${t.disabled}
5275
+ `
5276
+ };
5277
+ }
5278
+ async function readConsentDecision(rl, stdout, rendered) {
5279
+ let answer = interpretConsentAnswer(await rl.question(rendered.question));
5280
+ while (answer === "details") {
5281
+ stdout.write(rendered.details);
5282
+ answer = interpretConsentAnswer(await rl.question(rendered.reprompt));
5283
+ }
5284
+ return answer === "yes";
5285
+ }
5286
+ async function runConsentPrompt(stdin, stdout, nowMs) {
5287
+ const rendered = renderConsent(
5288
+ ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: false })
5289
+ );
5290
+ const rl = createInterface({ input: stdin, output: stdout });
5291
+ try {
5292
+ const consented = await readConsentDecision(rl, stdout, rendered);
5293
+ writeUserSettings({
5294
+ telemetry: {
5295
+ errorsEnabled: consented,
5296
+ usageCliEnabled: consented,
5297
+ usageUiEnabled: consented,
5298
+ promptedAt: nowMs
5299
+ }
5300
+ });
5301
+ if (consented) ensureAnonymousId();
5302
+ stdout.write(consented ? rendered.enabled : rendered.disabled);
5303
+ } catch {
5304
+ } finally {
5305
+ rl.close();
5306
+ }
5307
+ }
5308
+ async function maybeRunFirstRunPrompt({
5309
+ stdin = process.stdin,
5310
+ stdout = process.stdout,
5311
+ nowMs = Date.now()
5312
+ } = {}) {
5313
+ if (!isPromptEligible(liveGateInputs(stdout))) return;
5314
+ if (!hasSeenFirstRun()) {
5315
+ writeUserSettings({ telemetry: { firstRunAt: nowMs } });
5316
+ return;
5317
+ }
5318
+ await runConsentPrompt(stdin, stdout, nowMs);
5319
+ }
5320
+
4920
5321
  // cli/commands/bump.ts
4921
5322
  import { Command as Command2, Option as Option2 } from "clipanion";
4922
5323
 
@@ -5599,13 +6000,13 @@ var CONSENT_TEXTS = {
5599
6000
  };
5600
6001
 
5601
6002
  // cli/util/confirm.ts
5602
- import { createInterface } from "readline";
6003
+ import { createInterface as createInterface2 } from "readline";
5603
6004
  var YES_PATTERN = new RegExp(UTIL_TEXTS.confirmYesPatternSource, "i");
5604
6005
  var NO_PATTERN = new RegExp(UTIL_TEXTS.confirmNoPatternSource, "i");
5605
6006
  async function confirm(question, streams, opts) {
5606
6007
  const defaultAnswer = opts?.defaultAnswer ?? "no";
5607
6008
  const suffix = defaultAnswer === "yes" ? UTIL_TEXTS.confirmPromptSuffixDefaultYes : UTIL_TEXTS.confirmPromptSuffix;
5608
- const rl = createInterface({ input: streams.stdin, output: streams.stderr });
6009
+ const rl = createInterface2({ input: streams.stdin, output: streams.stderr });
5609
6010
  try {
5610
6011
  const answer = await new Promise(
5611
6012
  (resolveP) => rl.question(`${question}${suffix}`, resolveP)
@@ -7444,8 +7845,7 @@ async function replaceAllScanTags(trx, records, livePaths = /* @__PURE__ */ new
7444
7845
  if (records.length === 0) return;
7445
7846
  const rows = records.map((r) => ({
7446
7847
  nodePath: r.nodePath,
7447
- tag: r.tag,
7448
- source: r.source
7848
+ tag: r.tag
7449
7849
  }));
7450
7850
  const BATCH = 300;
7451
7851
  for (let i = 0; i < rows.length; i += BATCH) {
@@ -7453,18 +7853,16 @@ async function replaceAllScanTags(trx, records, livePaths = /* @__PURE__ */ new
7453
7853
  }
7454
7854
  }
7455
7855
  async function loadTagsForNode(db, nodePath) {
7456
- const rows = await db.selectFrom("scan_node_tags").select(["nodePath", "tag", "source"]).where("nodePath", "=", nodePath).orderBy("source", "asc").orderBy("tag", "asc").execute();
7457
- return rows.map((r) => ({ nodePath: r.nodePath, tag: r.tag, source: r.source }));
7856
+ const rows = await db.selectFrom("scan_node_tags").select(["nodePath", "tag"]).where("nodePath", "=", nodePath).orderBy("tag", "asc").execute();
7857
+ return rows.map((r) => ({ nodePath: r.nodePath, tag: r.tag }));
7458
7858
  }
7459
7859
  async function loadTagsForPaths(db, nodePaths) {
7460
7860
  if (nodePaths.length === 0) return [];
7461
- const rows = await db.selectFrom("scan_node_tags").select(["nodePath", "tag", "source"]).where("nodePath", "in", [...nodePaths]).orderBy("source", "asc").orderBy("tag", "asc").execute();
7462
- return rows.map((r) => ({ nodePath: r.nodePath, tag: r.tag, source: r.source }));
7861
+ const rows = await db.selectFrom("scan_node_tags").select(["nodePath", "tag"]).where("nodePath", "in", [...nodePaths]).orderBy("tag", "asc").execute();
7862
+ return rows.map((r) => ({ nodePath: r.nodePath, tag: r.tag }));
7463
7863
  }
7464
- async function findNodesByTag(db, tag, source) {
7465
- let q = db.selectFrom("scan_node_tags").select("nodePath").where("tag", "=", tag);
7466
- if (source !== void 0) q = q.where("source", "=", source);
7467
- const rows = await q.distinct().orderBy("nodePath", "asc").execute();
7864
+ async function findNodesByTag(db, tag) {
7865
+ const rows = await db.selectFrom("scan_node_tags").select("nodePath").where("tag", "=", tag).distinct().orderBy("nodePath", "asc").execute();
7468
7866
  return rows.map((r) => r.nodePath);
7469
7867
  }
7470
7868
 
@@ -7662,19 +8060,18 @@ function pickIntegerVersion(v) {
7662
8060
  function nodesToTagRecords(nodes) {
7663
8061
  const records = [];
7664
8062
  for (const node of nodes) {
7665
- pushTagRecords(records, node.path, node.frontmatter?.["tags"], "author");
7666
- pushTagRecords(records, node.path, node.sidecar?.annotations?.["tags"], "user");
8063
+ pushTagRecords(records, node.path, node.sidecar?.annotations?.["tags"]);
7667
8064
  }
7668
8065
  return records;
7669
8066
  }
7670
- function pushTagRecords(out, nodePath, raw, source) {
8067
+ function pushTagRecords(out, nodePath, raw) {
7671
8068
  if (!Array.isArray(raw)) return;
7672
8069
  const seen = /* @__PURE__ */ new Set();
7673
8070
  for (const item of raw) {
7674
8071
  if (typeof item !== "string" || item.length === 0) continue;
7675
8072
  if (seen.has(item)) continue;
7676
8073
  seen.add(item);
7677
- out.push({ nodePath, tag: item, source });
8074
+ out.push({ nodePath, tag: item });
7678
8075
  }
7679
8076
  }
7680
8077
  function linkToRow(link) {
@@ -7882,7 +8279,7 @@ var SqliteStorageAdapter = class {
7882
8279
  /**
7883
8280
  * Access the underlying Kysely instance.
7884
8281
  *
7885
- * Test-only escape hatch (per AGENTS.md § Kernel boundaries, tests
8282
+ * Test-only escape hatch (per context/kernel.md §Kernel boundaries, tests
7886
8283
  * are the documented exception). CLI commands MUST consume the
7887
8284
  * adapter through the namespaced port surfaces (`port.<namespace>.*`
7888
8285
  * or `port.transaction(...)`); reaching for this getter from a
@@ -7915,7 +8312,7 @@ var SqliteStorageAdapter = class {
7915
8312
  this.tags = {
7916
8313
  listForNode: (nodePath) => loadTagsForNode(this.db, nodePath),
7917
8314
  listForPaths: (paths) => loadTagsForPaths(this.db, paths),
7918
- findNodes: (tag, source) => findNodesByTag(this.db, tag, source)
8315
+ findNodes: (tag) => findNodesByTag(this.db, tag)
7919
8316
  };
7920
8317
  this.issues = {
7921
8318
  listAll: () => listAllIssues(this.db),
@@ -10221,8 +10618,8 @@ var DEFAULT_READ_CONFIG = Object.freeze({
10221
10618
  });
10222
10619
  function resolveProviderWalk(provider) {
10223
10620
  if (provider.walk) {
10224
- const walk2 = provider.walk.bind(provider);
10225
- return walk2;
10621
+ const walk3 = provider.walk.bind(provider);
10622
+ return walk3;
10226
10623
  }
10227
10624
  const read = provider.read ?? DEFAULT_READ_CONFIG;
10228
10625
  return (roots, options) => {
@@ -12618,9 +13015,22 @@ var DbRestoreCommand = class extends SmCommand {
12618
13015
  };
12619
13016
 
12620
13017
  // cli/commands/db/reset.ts
12621
- import { rm as rm2 } from "fs/promises";
12622
13018
  import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
12623
13019
  import { Command as Command8, Option as Option8 } from "clipanion";
13020
+
13021
+ // core/sqlite/db-files.ts
13022
+ import { existsSync as existsSync19 } from "fs";
13023
+ import { rm as rm2 } from "fs/promises";
13024
+ var DB_FILE_SUFFIXES = ["", "-wal", "-shm"];
13025
+ async function removeDbFiles(dbPath) {
13026
+ if (dbPath === ":memory:") return;
13027
+ for (const suffix of DB_FILE_SUFFIXES) {
13028
+ const p = `${dbPath}${suffix}`;
13029
+ if (existsSync19(p)) await rm2(p);
13030
+ }
13031
+ }
13032
+
13033
+ // cli/commands/db/reset.ts
12624
13034
  var DbResetCommand = class extends SmCommand {
12625
13035
  static paths = [["db", "reset"]];
12626
13036
  static usage = Command8.Usage({
@@ -12680,10 +13090,7 @@ var DbResetCommand = class extends SmCommand {
12680
13090
  return ExitCode.Error;
12681
13091
  }
12682
13092
  }
12683
- for (const suffix of ["", "-wal", "-shm"]) {
12684
- const p = `${path}${suffix}`;
12685
- if (await pathExists(p)) await rm2(p);
12686
- }
13093
+ await removeDbFiles(path);
12687
13094
  const ansiHard = this.ansiFor("stdout");
12688
13095
  this.printer.data(
12689
13096
  tx(DB_TEXTS.resetHardDeleted, {
@@ -13838,8 +14245,7 @@ var HelpCommand = class extends Command15 {
13838
14245
 
13839
14246
  Formats:
13840
14247
  human (default): pretty terminal output.
13841
- md : canonical markdown. context/cli-reference.md is
13842
- regenerated from this and CI fails on drift.
14248
+ md : canonical markdown for documentation.
13843
14249
  json : structured surface dump per spec/cli-contract.md.
13844
14250
  `
13845
14251
  });
@@ -14424,7 +14830,7 @@ import { join as join17 } from "path";
14424
14830
  import { Command as Command17, Option as Option16 } from "clipanion";
14425
14831
 
14426
14832
  // kernel/orchestrator/index.ts
14427
- import { existsSync as existsSync21, statSync as statSync6 } from "fs";
14833
+ import { existsSync as existsSync22, statSync as statSync6 } from "fs";
14428
14834
  import { isAbsolute as isAbsolute7, resolve as resolve28 } from "path";
14429
14835
  import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
14430
14836
  import cl100k_base from "js-tiktoken/ranks/cl100k_base";
@@ -15471,16 +15877,16 @@ function computeDriftStatus(args2) {
15471
15877
  }
15472
15878
 
15473
15879
  // kernel/sidecar/discover-orphans.ts
15474
- import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
15880
+ import { existsSync as existsSync20, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
15475
15881
  import { join as join13, relative as relative4, sep as sep4 } from "path";
15476
15882
  function discoverOrphanSidecars(roots, shouldSkip) {
15477
15883
  const out = [];
15478
15884
  for (const root of roots) {
15479
- walk(root, root, shouldSkip ?? (() => false), out);
15885
+ walk2(root, root, shouldSkip ?? (() => false), out);
15480
15886
  }
15481
15887
  return out;
15482
15888
  }
15483
- function walk(root, current, shouldSkip, out) {
15889
+ function walk2(root, current, shouldSkip, out) {
15484
15890
  let entries;
15485
15891
  try {
15486
15892
  entries = readdirSync7(current, { withFileTypes: true, encoding: "utf8" });
@@ -15493,13 +15899,13 @@ function walk(root, current, shouldSkip, out) {
15493
15899
  if (shouldSkip(rel)) continue;
15494
15900
  if (entry.isSymbolicLink()) continue;
15495
15901
  if (entry.isDirectory()) {
15496
- walk(root, full, shouldSkip, out);
15902
+ walk2(root, full, shouldSkip, out);
15497
15903
  continue;
15498
15904
  }
15499
15905
  if (!entry.isFile()) continue;
15500
15906
  if (!entry.name.endsWith(".sm")) continue;
15501
15907
  const expectedMd = `${full.slice(0, -".sm".length)}.md`;
15502
- if (existsSync19(expectedMd) && safeIsFile(expectedMd)) continue;
15908
+ if (existsSync20(expectedMd) && safeIsFile(expectedMd)) continue;
15503
15909
  out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
15504
15910
  }
15505
15911
  }
@@ -15513,7 +15919,7 @@ function safeIsFile(path) {
15513
15919
 
15514
15920
  // kernel/orchestrator/node-build.ts
15515
15921
  import { createHash } from "crypto";
15516
- import { existsSync as existsSync20 } from "fs";
15922
+ import { existsSync as existsSync21 } from "fs";
15517
15923
  import { isAbsolute as isAbsolute6, resolve as resolvePath } from "path";
15518
15924
  import "js-tiktoken/lite";
15519
15925
  import yaml4 from "js-yaml";
@@ -15677,11 +16083,11 @@ function resolveSidecarOverlay(relativePath2, nodePathForIssue, roots, liveBodyH
15677
16083
  }
15678
16084
  function resolveAbsoluteMdPath(relativePath2, roots) {
15679
16085
  if (isAbsolute6(relativePath2)) {
15680
- return existsSync20(relativePath2) ? relativePath2 : null;
16086
+ return existsSync21(relativePath2) ? relativePath2 : null;
15681
16087
  }
15682
16088
  for (const root of roots) {
15683
16089
  const candidate = resolvePath(root, relativePath2);
15684
- if (existsSync20(candidate)) return candidate;
16090
+ if (existsSync21(candidate)) return candidate;
15685
16091
  }
15686
16092
  return null;
15687
16093
  }
@@ -16117,7 +16523,8 @@ function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
16117
16523
  const reservedNodePaths = buildReservedNodePaths(
16118
16524
  nodes,
16119
16525
  kindRegistry,
16120
- reservedNamesByProviderKind
16526
+ reservedNamesByProviderKind,
16527
+ activeProvider
16121
16528
  );
16122
16529
  return { kindRegistry, providerResolution, activeProvider, reservedNodePaths };
16123
16530
  }
@@ -16148,19 +16555,23 @@ function indexReservedNames(provider, out) {
16148
16555
  }
16149
16556
  }
16150
16557
  }
16151
- function buildReservedNodePaths(nodes, kindRegistry, reservedNamesByProviderKind) {
16558
+ function buildReservedNodePaths(nodes, kindRegistry, reservedNamesByProviderKind, activeProvider) {
16152
16559
  const out = /* @__PURE__ */ new Set();
16153
16560
  for (const node of nodes) {
16154
- const key = `${node.provider}/${node.kind}`;
16155
- const reservedSet = reservedNamesByProviderKind.get(key);
16156
- if (!reservedSet || reservedSet.size === 0) continue;
16157
- const ids = deriveNodeIdentifiers(node, kindRegistry.get(key));
16158
- if (ids.some((id) => reservedSet.has(id))) {
16561
+ const selfKey = `${node.provider}/${node.kind}`;
16562
+ const selfReserved = reservedNamesByProviderKind.get(selfKey);
16563
+ const lensReserved = activeProvider && activeProvider !== node.provider ? reservedNamesByProviderKind.get(`${activeProvider}/${node.kind}`) : void 0;
16564
+ if (!hasEntries(selfReserved) && !hasEntries(lensReserved)) continue;
16565
+ const ids = deriveNodeIdentifiers(node, kindRegistry.get(selfKey));
16566
+ if (ids.some((id) => selfReserved?.has(id) === true || lensReserved?.has(id) === true)) {
16159
16567
  out.add(node.path);
16160
16568
  }
16161
16569
  }
16162
16570
  return out;
16163
16571
  }
16572
+ function hasEntries(set) {
16573
+ return set !== void 0 && set.size > 0;
16574
+ }
16164
16575
  function buildScanSetup(options) {
16165
16576
  const start = Date.now();
16166
16577
  const emitter = options.emitter ?? new InMemoryProgressEmitter();
@@ -16246,7 +16657,7 @@ function validateRoots(roots) {
16246
16657
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
16247
16658
  }
16248
16659
  for (const root of roots) {
16249
- if (!existsSync21(root) || !statSync6(root).isDirectory()) {
16660
+ if (!existsSync22(root) || !statSync6(root).isDirectory()) {
16250
16661
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
16251
16662
  }
16252
16663
  }
@@ -16255,7 +16666,7 @@ function resolveActiveProviderOption(optionValue, roots, providers) {
16255
16666
  if (optionValue !== void 0) return optionValue;
16256
16667
  for (const root of roots) {
16257
16668
  const absRoot = isAbsolute7(root) ? root : resolve28(root);
16258
- if (!existsSync21(absRoot)) continue;
16669
+ if (!existsSync22(absRoot)) continue;
16259
16670
  const detected = resolveActiveProvider(absRoot, providers).resolved;
16260
16671
  if (detected !== null) return detected;
16261
16672
  }
@@ -16722,7 +17133,7 @@ function safeStat(path) {
16722
17133
  }
16723
17134
 
16724
17135
  // core/runtime/active-provider-bootstrap.ts
16725
- import { createInterface as createInterface2 } from "readline";
17136
+ import { createInterface as createInterface3 } from "readline";
16726
17137
  import { isAbsolute as isAbsolute9, join as join16 } from "path";
16727
17138
  async function bootstrapActiveProvider(opts) {
16728
17139
  const fromCwd = resolveActiveProvider(opts.cwd, opts.providers);
@@ -16878,7 +17289,7 @@ async function promptForLens(detected, stdin, stderr, warnGlyph) {
16878
17289
  );
16879
17290
  }
16880
17291
  stderr.write(lines.join("\n") + "\n");
16881
- const rl = createInterface2({ input: stdin, output: stderr });
17292
+ const rl = createInterface3({ input: stdin, output: stderr });
16882
17293
  try {
16883
17294
  const answer = await new Promise(
16884
17295
  (resolveP) => rl.question(SCAN_RUNNER_TEXTS.activeProviderPromptInput, resolveP)
@@ -16895,6 +17306,99 @@ async function promptForLens(detected, stdin, stderr, warnGlyph) {
16895
17306
  }
16896
17307
  }
16897
17308
 
17309
+ // core/sqlite/db-drift-reset.ts
17310
+ import { existsSync as existsSync23 } from "fs";
17311
+ import { createInterface as createInterface4 } from "readline";
17312
+ import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
17313
+
17314
+ // core/sqlite/i18n/db-drift.texts.ts
17315
+ var DB_DRIFT_TEXTS = {
17316
+ // Interactive confirm (TTY `sm scan`, no `--yes`). The block is
17317
+ // written to stderr, then the question line drives `readline`.
17318
+ driftPrompt: "{{glyph}} The local cache was built by skill-map {{dbVersion}} and you are on {{currentVersion}}.\n {{hint}}\n",
17319
+ driftPromptHint: "Pre-1.0 the DB is a derived cache (your .sm sidecars hold the real data); it cannot be carried across a version change and has to be rebuilt.",
17320
+ driftPromptQuestion: "Delete the local cache and rebuild it on this scan? [y/N] ",
17321
+ // Receipt after the rebuild (printed by the scan / refresh path).
17322
+ driftReset: "{{glyph}} Local cache rebuilt: it was written by skill-map {{dbVersion}}, you are on {{currentVersion}}.\n {{hint}}\n",
17323
+ driftResetHint: "The DB was deleted and is being regenerated by this scan; .sm sidecars were not touched.",
17324
+ // Abort headline when the operator declines (wrapped by the caller's
17325
+ // `sm scan: {message}` shell, so it carries no glyph / verb prefix).
17326
+ driftAborted: "cache rebuild declined: the {{dbVersion}} cache cannot be reused on {{currentVersion}}. {{hint}}",
17327
+ driftAbortedHint: "Re-run with --yes, or run `sm db reset --hard` then `sm scan`."
17328
+ };
17329
+
17330
+ // core/sqlite/db-drift-reset.ts
17331
+ async function maybeResetOnDrift(dbPath, policy) {
17332
+ const dbVersion = readScannedByVersion(dbPath);
17333
+ if (dbVersion === null) return { kind: "no-drift" };
17334
+ const skew = classifyVersionSkew(dbVersion, policy.currentVersion);
17335
+ if (skew.kind === "ok" || skew.kind === "no-meta") return { kind: "no-drift" };
17336
+ const confirmed = await confirmDriftReset(dbVersion, policy);
17337
+ if (!confirmed) {
17338
+ return { kind: "aborted", dbVersion, currentVersion: policy.currentVersion };
17339
+ }
17340
+ await removeDbFiles(dbPath);
17341
+ renderResetReceipt(dbVersion, policy);
17342
+ return { kind: "reset", dbVersion, currentVersion: policy.currentVersion };
17343
+ }
17344
+ function readScannedByVersion(dbPath) {
17345
+ if (dbPath === ":memory:" || !existsSync23(dbPath)) return null;
17346
+ let raw = null;
17347
+ try {
17348
+ raw = new DatabaseSync7(dbPath, { readOnly: true });
17349
+ const row = raw.prepare("SELECT scanned_by_version AS v FROM scan_meta LIMIT 1").get();
17350
+ const v = row?.v;
17351
+ return typeof v === "string" && v.length > 0 ? v : null;
17352
+ } catch {
17353
+ return null;
17354
+ } finally {
17355
+ raw?.close();
17356
+ }
17357
+ }
17358
+ async function confirmDriftReset(dbVersion, policy) {
17359
+ if (!shouldPromptForReset(policy)) return true;
17360
+ return askDriftReset(dbVersion, policy);
17361
+ }
17362
+ function shouldPromptForReset(policy) {
17363
+ if (policy.assumeYes) return false;
17364
+ if (!policy.stdin || !policy.stderr) return false;
17365
+ return policy.stdin.isTTY === true;
17366
+ }
17367
+ async function askDriftReset(dbVersion, policy) {
17368
+ const warnGlyph = policy.style?.warnGlyph ?? "\u26A0";
17369
+ const dim = policy.style?.dim ?? ((s) => s);
17370
+ policy.stderr.write(
17371
+ tx(DB_DRIFT_TEXTS.driftPrompt, {
17372
+ glyph: warnGlyph,
17373
+ dbVersion,
17374
+ currentVersion: policy.currentVersion,
17375
+ hint: dim(DB_DRIFT_TEXTS.driftPromptHint)
17376
+ })
17377
+ );
17378
+ const rl = createInterface4({ input: policy.stdin, output: policy.stderr });
17379
+ try {
17380
+ const answer = await new Promise(
17381
+ (resolveP) => rl.question(DB_DRIFT_TEXTS.driftPromptQuestion, resolveP)
17382
+ );
17383
+ return /^y(es)?$/i.test(answer.trim());
17384
+ } finally {
17385
+ rl.close();
17386
+ }
17387
+ }
17388
+ function renderResetReceipt(dbVersion, policy) {
17389
+ if (!policy.printer) return;
17390
+ const warnGlyph = policy.style?.warnGlyph ?? "\u26A0";
17391
+ const dim = policy.style?.dim ?? ((s) => s);
17392
+ policy.printer.warn(
17393
+ tx(DB_DRIFT_TEXTS.driftReset, {
17394
+ glyph: warnGlyph,
17395
+ dbVersion,
17396
+ currentVersion: policy.currentVersion,
17397
+ hint: dim(DB_DRIFT_TEXTS.driftResetHint)
17398
+ })
17399
+ );
17400
+ }
17401
+
16898
17402
  // core/runtime/scan-runner.ts
16899
17403
  async function runScanForCommand(opts) {
16900
17404
  const ctx = opts.ctx ?? defaultRuntimeContext();
@@ -16907,9 +17411,9 @@ async function runScanForCommand(opts) {
16907
17411
  const { cfg, ignoreFilter, strict, effectiveRoots } = scanInputs;
16908
17412
  let referenceablePaths;
16909
17413
  if (cfg.scan.referencePaths.length > 0) {
16910
- const walk2 = walkReferencePaths(cfg.scan.referencePaths, ctx.cwd);
16911
- referenceablePaths = walk2.paths;
16912
- emitReferenceWalkAdvisory(walk2, opts);
17414
+ const walk3 = walkReferencePaths(cfg.scan.referencePaths, ctx.cwd);
17415
+ referenceablePaths = walk3.paths;
17416
+ emitReferenceWalkAdvisory(walk3, opts);
16913
17417
  }
16914
17418
  const loadPrior = makePriorLoader(opts.noBuiltIns, strict);
16915
17419
  const jobsDir = defaultProjectJobsDir(ctx);
@@ -16971,11 +17475,11 @@ async function resolveActiveLens(opts, ctx, effectiveRoots, pluginRuntime, provi
16971
17475
  });
16972
17476
  return { kind: "ok", activeProvider: bootstrap.activeProvider };
16973
17477
  }
16974
- function emitReferenceWalkAdvisory(walk2, opts) {
16975
- if (walk2.truncated) {
17478
+ function emitReferenceWalkAdvisory(walk3, opts) {
17479
+ if (walk3.truncated) {
16976
17480
  opts.printer.warn(SCAN_RUNNER_TEXTS.referenceWalkTruncated);
16977
17481
  }
16978
- for (const missing of walk2.missingRoots) {
17482
+ for (const missing of walk3.missingRoots) {
16979
17483
  opts.printer.warn(
16980
17484
  tx(SCAN_RUNNER_TEXTS.referenceWalkMissingRoot, { path: missing })
16981
17485
  );
@@ -17092,7 +17596,29 @@ function buildRunScanEmitter(opts) {
17092
17596
  colorEnabled: opts.colorEnabled === true
17093
17597
  });
17094
17598
  }
17599
+ async function rebuildOnDrift(opts, dbPath) {
17600
+ const drift = await maybeResetOnDrift(dbPath, {
17601
+ currentVersion: VERSION,
17602
+ assumeYes: opts.yes ?? false,
17603
+ stdin: opts.stdin ?? process.stdin,
17604
+ stderr: opts.stderr,
17605
+ printer: opts.printer,
17606
+ ...opts.style ? { style: opts.style } : {}
17607
+ });
17608
+ if (drift.kind !== "aborted") return null;
17609
+ const dim = opts.style?.dim ?? ((s) => s);
17610
+ return {
17611
+ kind: "scan-error",
17612
+ message: tx(DB_DRIFT_TEXTS.driftAborted, {
17613
+ dbVersion: drift.dbVersion,
17614
+ currentVersion: drift.currentVersion,
17615
+ hint: dim(DB_DRIFT_TEXTS.driftAbortedHint)
17616
+ })
17617
+ };
17618
+ }
17095
17619
  async function runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanWith, extensions) {
17620
+ const driftError = await rebuildOnDrift(opts, dbPath);
17621
+ if (driftError) return driftError;
17096
17622
  let outcome;
17097
17623
  try {
17098
17624
  outcome = await withSqlite({ databasePath: dbPath }, async (adapter) => {
@@ -17132,7 +17658,8 @@ async function runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanW
17132
17658
  renameOps: outcome.renameOps,
17133
17659
  persistedTo: dbPath,
17134
17660
  dbPath,
17135
- strict
17661
+ strict,
17662
+ executedExtensionIds: outcome.extractorRuns.map((run) => run.extractorId)
17136
17663
  };
17137
17664
  }
17138
17665
  async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
@@ -17150,7 +17677,8 @@ async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
17150
17677
  renameOps: scanned.renameOps,
17151
17678
  persistedTo: null,
17152
17679
  dbPath,
17153
- strict
17680
+ strict,
17681
+ executedExtensionIds: scanned.extractorRuns.map((run) => run.extractorId)
17154
17682
  };
17155
17683
  } catch (err) {
17156
17684
  return { kind: "scan-error", message: formatErrorMessage(err) };
@@ -18182,19 +18710,6 @@ import { Command as Command20, Option as Option19 } from "clipanion";
18182
18710
  var LIST_TEXTS = {
18183
18711
  invalidSortBy: '{{glyph}} --sort-by: invalid sort field "{{value}}".\n {{hint}}\n',
18184
18712
  invalidSortByHint: "Allowed: {{allowed}}.",
18185
- /**
18186
- * §3.1b two-line block. Closed enum: hint enumerates the two valid
18187
- * values so the operator does not need to re-read `--help`.
18188
- */
18189
- invalidTagSource: '{{glyph}} --tag-source: expected "author" or "user", got "{{value}}".\n {{hint}}\n',
18190
- invalidTagSourceHint: "Allowed: author, user.",
18191
- /**
18192
- * §3.1b two-line block. `--tag-source` is a narrowing filter on
18193
- * `--tag`; the hint repeats the dependency in operator-actionable
18194
- * form.
18195
- */
18196
- tagSourceWithoutTag: "{{glyph}} --tag-source requires --tag <name>.\n {{hint}}\n",
18197
- tagSourceWithoutTagHint: "The source filter narrows tag matches, it does not stand alone. Pass --tag <name> alongside --tag-source.",
18198
18713
  noNodesFound: "No nodes found.\n",
18199
18714
  // --- renderTable column headers ----------------------------------------
18200
18715
  tableHeaderPath: "PATH",
@@ -18231,9 +18746,8 @@ var ListCommand = class extends SmCommand {
18231
18746
  Reads from the persisted scan snapshot (scan_nodes). Filters:
18232
18747
  --kind <k> restricts to one node kind; --issue keeps only nodes
18233
18748
  that touch at least one current issue; --tag <name> keeps only
18234
- nodes carrying that tag (matches the union of frontmatter.tags
18235
- and sidecar.annotations.tags by default; --tag-source author|user
18236
- narrows to one side).
18749
+ nodes carrying that tag in their \`.sm\` sidecar
18750
+ (\`annotations.tags\`).
18237
18751
 
18238
18752
  --sort-by accepts: path, kind, tokens_total, links_out_count,
18239
18753
  links_in_count, external_refs_count. Default: path. --limit N caps
@@ -18246,8 +18760,7 @@ var ListCommand = class extends SmCommand {
18246
18760
  ["List only agents", "$0 list --kind agent"],
18247
18761
  ["Top 5 by total tokens", "$0 list --sort-by tokens_total --limit 5"],
18248
18762
  ["Only nodes with issues, machine-readable", "$0 list --issue --json"],
18249
- ["Filter by tag (author or user surfaces)", "$0 list --tag urgent"],
18250
- ["Filter by user-only tag", "$0 list --tag wip --tag-source user"]
18763
+ ["Filter by tag", "$0 list --tag urgent"]
18251
18764
  ]
18252
18765
  });
18253
18766
  kind = Option19.String("--kind", { required: false });
@@ -18255,7 +18768,6 @@ var ListCommand = class extends SmCommand {
18255
18768
  sortBy = Option19.String("--sort-by", { required: false });
18256
18769
  limit = Option19.String("--limit", { required: false });
18257
18770
  tag = Option19.String("--tag", { required: false });
18258
- tagSource = Option19.String("--tag-source", { required: false });
18259
18771
  async run() {
18260
18772
  const stderrAnsi = this.ansiFor("stderr");
18261
18773
  const flags = this.#parseFlags(stderrAnsi);
@@ -18274,7 +18786,6 @@ var ListCommand = class extends SmCommand {
18274
18786
  * resolved values or a precomputed exit code (already printed
18275
18787
  * directed errors before returning).
18276
18788
  */
18277
- // eslint-disable-next-line complexity
18278
18789
  #parseFlags(stderrAnsi) {
18279
18790
  let sortColumn = "path";
18280
18791
  let sortDirection = "asc";
@@ -18301,30 +18812,7 @@ var ListCommand = class extends SmCommand {
18301
18812
  if (parsed === null) return { ok: false, exit: ExitCode.Error };
18302
18813
  limitValue = parsed;
18303
18814
  }
18304
- let tagSourceValue;
18305
- if (this.tagSource !== void 0) {
18306
- if (this.tag === void 0) {
18307
- this.printer.error(
18308
- tx(LIST_TEXTS.tagSourceWithoutTag, {
18309
- glyph: stderrAnsi.red("\u2715"),
18310
- hint: stderrAnsi.dim(LIST_TEXTS.tagSourceWithoutTagHint)
18311
- })
18312
- );
18313
- return { ok: false, exit: ExitCode.Error };
18314
- }
18315
- if (this.tagSource !== "author" && this.tagSource !== "user") {
18316
- this.printer.error(
18317
- tx(LIST_TEXTS.invalidTagSource, {
18318
- glyph: stderrAnsi.red("\u2715"),
18319
- value: this.tagSource,
18320
- hint: stderrAnsi.dim(LIST_TEXTS.invalidTagSourceHint)
18321
- })
18322
- );
18323
- return { ok: false, exit: ExitCode.Error };
18324
- }
18325
- tagSourceValue = this.tagSource;
18326
- }
18327
- return { ok: true, sortColumn, sortDirection, limitValue, tagSourceValue };
18815
+ return { ok: true, sortColumn, sortDirection, limitValue };
18328
18816
  }
18329
18817
  /**
18330
18818
  * Issue the DB queries: optional `--tag` allow-list narrowing, the
@@ -18332,7 +18820,7 @@ var ListCommand = class extends SmCommand {
18332
18820
  * out so `run()` reads as a thin orchestrator.
18333
18821
  */
18334
18822
  async #runQuery(adapter, flags) {
18335
- const tagAllowList = await this.#resolveTagAllowList(adapter, flags);
18823
+ const tagAllowList = await this.#resolveTagAllowList(adapter);
18336
18824
  if (tagAllowList === "no-match") return this.#renderEmpty();
18337
18825
  const filter = this.#buildFindNodesFilter(flags);
18338
18826
  const allMatchingNodes = await adapter.scans.findNodes(filter);
@@ -18348,16 +18836,15 @@ var ListCommand = class extends SmCommand {
18348
18836
  return ExitCode.Ok;
18349
18837
  }
18350
18838
  /**
18351
- * Resolve `--tag` (and the optional `--tag-source` filter) into a
18352
- * path allow-list. Returns:
18839
+ * Resolve `--tag` into a path allow-list. Returns:
18353
18840
  * - `null` when `--tag` was not supplied (no narrowing).
18354
18841
  * - `'no-match'` when the tag matched zero nodes (caller renders
18355
18842
  * the empty surface and exits).
18356
18843
  * - a Set of paths otherwise.
18357
18844
  */
18358
- async #resolveTagAllowList(adapter, flags) {
18845
+ async #resolveTagAllowList(adapter) {
18359
18846
  if (this.tag === void 0) return null;
18360
- const matchingPaths = await adapter.tags.findNodes(this.tag, flags.tagSourceValue);
18847
+ const matchingPaths = await adapter.tags.findNodes(this.tag);
18361
18848
  if (matchingPaths.length === 0) return "no-match";
18362
18849
  return new Set(matchingPaths);
18363
18850
  }
@@ -20115,6 +20602,8 @@ var TogglePluginsBase = class extends SmCommand {
20115
20602
  targets = lockError;
20116
20603
  const keys = expandToKeys(targets);
20117
20604
  await this.#persistKeys(keys, enabled);
20605
+ const set = buildScanExtensionSet(keys);
20606
+ captureUsage("plugin.apply", enabled ? { enabled: set, disabled: [] } : { enabled: [], disabled: set });
20118
20607
  this.#renderSuccess(keys, enabled);
20119
20608
  return ExitCode.Ok;
20120
20609
  }
@@ -20447,7 +20936,7 @@ function resolveBareToggle(id, catalogue) {
20447
20936
  }
20448
20937
 
20449
20938
  // cli/commands/plugins/create.ts
20450
- import { existsSync as existsSync22, mkdirSync as mkdirSync5, writeFileSync } from "fs";
20939
+ import { existsSync as existsSync24, mkdirSync as mkdirSync5, writeFileSync } from "fs";
20451
20940
  import { join as join18, resolve as resolve33 } from "path";
20452
20941
  import { Command as Command26, Option as Option25 } from "clipanion";
20453
20942
  var PluginsCreateCommand = class extends SmCommand {
@@ -20476,7 +20965,7 @@ var PluginsCreateCommand = class extends SmCommand {
20476
20965
  const ctx = defaultRuntimeContext();
20477
20966
  const baseDir = defaultProjectPluginsDir(ctx);
20478
20967
  const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
20479
- if (existsSync22(targetDir) && !this.force) {
20968
+ if (existsSync24(targetDir) && !this.force) {
20480
20969
  this.printer.error(
20481
20970
  tx(PLUGINS_TEXTS.createRefuseOverwrite, {
20482
20971
  glyph: errGlyph,
@@ -21077,6 +21566,29 @@ function stripFrontmatterFence(text) {
21077
21566
  }
21078
21567
  var REFRESH_COMMANDS = [RefreshCommand];
21079
21568
 
21569
+ // cli/i18n/intentional-fail.texts.ts
21570
+ var INTENTIONAL_FAIL_TEXTS = {
21571
+ triggering: "Triggering an intentional uncaught error to exercise Sentry error reporting...",
21572
+ errorMessage: "skill-map intentional failure (Sentry self-test)"
21573
+ };
21574
+
21575
+ // cli/commands/intentional-fail.ts
21576
+ var IntentionalFailCommand = class extends SmCommand {
21577
+ static paths = [["intentional-fail"]];
21578
+ // No `static usage` on purpose: that is what keeps the verb out of every
21579
+ // help / reference surface Clipanion drives from command definitions.
21580
+ // A self-test crash has no meaningful "done in <...>" line.
21581
+ emitElapsed = false;
21582
+ async run() {
21583
+ this.printer.warn(INTENTIONAL_FAIL_TEXTS.triggering);
21584
+ setTimeout(() => {
21585
+ throw new Error(INTENTIONAL_FAIL_TEXTS.errorMessage);
21586
+ }, 0);
21587
+ await new Promise((resolve40) => setTimeout(resolve40, 5e3));
21588
+ return 1;
21589
+ }
21590
+ };
21591
+
21080
21592
  // cli/commands/scan.ts
21081
21593
  import { Command as Command31, Option as Option29 } from "clipanion";
21082
21594
 
@@ -21202,6 +21714,12 @@ var RUNTIME_TEXTS = {
21202
21714
 
21203
21715
  // core/watcher/runtime.ts
21204
21716
  var DEFAULT_RUN_INITIAL_BATCH = true;
21717
+ async function rebuildWatcherDbOnDrift(dbPath, events) {
21718
+ const drift = await maybeResetOnDrift(dbPath, { currentVersion: VERSION, assumeYes: true });
21719
+ if (drift.kind === "reset") {
21720
+ events.onDriftReset?.({ dbVersion: drift.dbVersion, currentVersion: drift.currentVersion });
21721
+ }
21722
+ }
21205
21723
  function createWatcherRuntime(opts) {
21206
21724
  const events = opts.events ?? {};
21207
21725
  const cwd = opts.runtimeContext.cwd;
@@ -21245,6 +21763,7 @@ function createWatcherRuntime(opts) {
21245
21763
  };
21246
21764
  let handleBatch = null;
21247
21765
  const start = async () => {
21766
+ await rebuildWatcherDbOnDrift(opts.dbPath, events);
21248
21767
  cfg = loadEffectiveConfig();
21249
21768
  ignoreFilter = composeIgnoreFilter(cfg, readIgnoreFileText(cwd));
21250
21769
  applyConfigDerivedState(cfg);
@@ -21301,9 +21820,9 @@ function createWatcherRuntime(opts) {
21301
21820
  overrideMaxNodes: opts.maxNodesOverride ?? null
21302
21821
  };
21303
21822
  if (cfg.scan.referencePaths.length > 0) {
21304
- const walk2 = walkReferencePaths(cfg.scan.referencePaths, cwd);
21305
- if (walk2.paths.size > 0) {
21306
- runOptions.referenceablePaths = walk2.paths;
21823
+ const walk3 = walkReferencePaths(cfg.scan.referencePaths, cwd);
21824
+ if (walk3.paths.size > 0) {
21825
+ runOptions.referenceablePaths = walk3.paths;
21307
21826
  runOptions.cwd = cwd;
21308
21827
  }
21309
21828
  }
@@ -21600,6 +22119,16 @@ async function runWatchLoop(opts) {
21600
22119
  printer.warn(`${message}
21601
22120
  `);
21602
22121
  },
22122
+ onDriftReset: (info) => {
22123
+ context.stderr.write(
22124
+ tx(DB_DRIFT_TEXTS.driftReset, {
22125
+ glyph: stderrAnsi.yellow("\u26A0"),
22126
+ dbVersion: info.dbVersion,
22127
+ currentVersion: info.currentVersion,
22128
+ hint: stderrAnsi.dim(DB_DRIFT_TEXTS.driftResetHint)
22129
+ })
22130
+ );
22131
+ },
21603
22132
  onConfigLoaded: ({ debounceMs }) => {
21604
22133
  if (opts.json) return;
21605
22134
  context.stderr.write(
@@ -21821,7 +22350,7 @@ var ScanCommand = class extends SmCommand {
21821
22350
  description: "Long-running mode: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`."
21822
22351
  });
21823
22352
  yes = Option29.Boolean("--yes", false, {
21824
- description: "Non-interactive mode for ambiguous activeProvider auto-detect. With `--yes`, multiple provider markers (.claude/, .codex/, AGENTS.md, .cursor/) under the scan tree exit non-zero instead of prompting the operator. Set the lens manually via `sm config set activeProvider <id>` and re-run."
22353
+ description: "Non-interactive mode. For ambiguous activeProvider auto-detect, multiple provider markers (.claude/, .codex/, AGENTS.md, .cursor/) under the scan tree exit non-zero instead of prompting; set the lens manually via `sm config set activeProvider <id>` and re-run. Also auto-confirms the pre-1.0 schema-drift rebuild (when the DB was written by a different skill-map major.minor it is deleted and regenerated) instead of prompting."
21825
22354
  });
21826
22355
  maxNodes = Option29.String("--max-nodes", {
21827
22356
  required: false,
@@ -21871,7 +22400,11 @@ var ScanCommand = class extends SmCommand {
21871
22400
  style,
21872
22401
  ...parsedMaxNodes.value !== void 0 ? { maxNodes: parsedMaxNodes.value } : {}
21873
22402
  });
21874
- return outcome.kind === "ok" ? this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict) : this.renderFailure(outcome);
22403
+ if (outcome.kind === "ok") {
22404
+ setScanExtensions(buildScanExtensionSet(outcome.executedExtensionIds));
22405
+ return this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict);
22406
+ }
22407
+ return this.renderFailure(outcome);
21875
22408
  }
21876
22409
  /**
21877
22410
  * Parse `--max-nodes <N>`. Returns either the integer value (or
@@ -22355,7 +22888,7 @@ function renderDeltaIssues(issues) {
22355
22888
 
22356
22889
  // cli/commands/serve.ts
22357
22890
  import { spawn as spawn2 } from "child_process";
22358
- import { existsSync as existsSync28 } from "fs";
22891
+ import { existsSync as existsSync30 } from "fs";
22359
22892
  import { Command as Command33, Option as Option31 } from "clipanion";
22360
22893
 
22361
22894
  // kernel/util/dev-mode.ts
@@ -22389,7 +22922,7 @@ import { WebSocketServer } from "ws";
22389
22922
  // server/app.ts
22390
22923
  import { Hono } from "hono";
22391
22924
  import { bodyLimit } from "hono/body-limit";
22392
- import { HTTPException as HTTPException15 } from "hono/http-exception";
22925
+ import { HTTPException as HTTPException16 } from "hono/http-exception";
22393
22926
 
22394
22927
  // core/config/service.ts
22395
22928
  var ConfigService = class {
@@ -22510,6 +23043,11 @@ var SERVER_TEXTS = {
22510
23043
  // watcher loop continues, a transient FS error must not kill the
22511
23044
  // broadcaster.
22512
23045
  watcherBatchFailed: "skill-map server: watcher batch failed ({{message}}).\n",
23046
+ // Logged once when the pre-1.0 schema-drift check rebuilt the DB on
23047
+ // watcher boot (the on-disk cache was written by a different
23048
+ // major.minor). The scan that follows repopulates it; .sm sidecars
23049
+ // are untouched. See spec/db-schema.md §Schema drift (pre-1.0).
23050
+ watcherDriftReset: "skill-map server: local cache rebuilt (was {{dbVersion}}, now on {{currentVersion}}); .sm sidecars untouched.\n",
22513
23051
  // chokidar surfaced an error. The watcher stays open per IFsWatcher's
22514
23052
  // contract; the BFF also broadcasts a `watcher.error` advisory so the
22515
23053
  // SPA can surface it in the live event log.
@@ -22621,6 +23159,10 @@ var SERVER_TEXTS = {
22621
23159
  preferencesBodyEmpty: "Request body must contain at least one known preference (e.g. `updateCheck.enabled`).",
22622
23160
  preferencesUpdateCheckNotObject: '`updateCheck` must be an object (e.g. `{"updateCheck": {"enabled": false}}`).',
22623
23161
  preferencesUpdateCheckEnabledNotBoolean: "`updateCheck.enabled` must be a boolean.",
23162
+ preferencesTelemetryNotObject: '`telemetry` must be an object (e.g. `{"telemetry": {"errorsEnabled": true}}`).',
23163
+ preferencesTelemetryErrorsEnabledNotBoolean: "`telemetry.errorsEnabled` must be a boolean.",
23164
+ preferencesTelemetryUsageCliEnabledNotBoolean: "`telemetry.usageCliEnabled` must be a boolean.",
23165
+ preferencesTelemetryUsageUiEnabledNotBoolean: "`telemetry.usageUiEnabled` must be a boolean.",
22624
23166
  preferencesPersistFailed: "Could not persist preferences: {{message}}",
22625
23167
  // ---- project-preferences route (routes/project-preferences.ts) ----------
22626
23168
  //
@@ -22780,6 +23322,53 @@ function createSecurityHeaders() {
22780
23322
  };
22781
23323
  }
22782
23324
 
23325
+ // server/telemetry/sentry.ts
23326
+ import { HTTPException } from "hono/http-exception";
23327
+ var sdk2 = null;
23328
+ async function initSentryBff(version, loadSdk = () => import("@sentry/node")) {
23329
+ if (sdk2) return;
23330
+ if (!isTelemetryActive(SENTRY_DSN_NODE)) return;
23331
+ const Sentry = await loadSdk();
23332
+ Sentry.init({
23333
+ dsn: SENTRY_DSN_NODE,
23334
+ release: `@skill-map/cli@${version}`,
23335
+ environment: resolveTelemetryEnv(),
23336
+ // Shared project with the CLI; the `surface` tag separates the two.
23337
+ initialScope: { tags: { surface: "bff" } },
23338
+ // Errors only: skip the OpenTelemetry ESM loader hooks (we run no
23339
+ // tracing). They call the deprecated `module.register()` (a Node >= 26
23340
+ // DEP0205 warning) and add startup cost for nothing here.
23341
+ registerEsmLoaderHooks: false,
23342
+ defaultIntegrations: false,
23343
+ integrations: [],
23344
+ tracesSampleRate: 0,
23345
+ sendDefaultPii: false,
23346
+ beforeSend: (event) => scrubEvent(event)
23347
+ });
23348
+ sdk2 = Sentry;
23349
+ }
23350
+ function shouldCaptureError(err) {
23351
+ if (err instanceof HTTPException) return err.status >= 500;
23352
+ return true;
23353
+ }
23354
+ function createSentryRequestCapture() {
23355
+ return async function sentryRequestCapture(c, next) {
23356
+ try {
23357
+ await next();
23358
+ } catch (err) {
23359
+ const client2 = sdk2;
23360
+ if (client2 && shouldCaptureError(err)) {
23361
+ client2.withScope((scope) => {
23362
+ scope.setTag("route", c.req.routePath ?? c.req.path);
23363
+ scope.setTag("method", c.req.method);
23364
+ client2.captureException(err);
23365
+ });
23366
+ }
23367
+ throw err;
23368
+ }
23369
+ };
23370
+ }
23371
+
22783
23372
  // server/routes/annotations.ts
22784
23373
  var ENVELOPE_KIND = "annotations.registered";
22785
23374
  function registerAnnotationsRoute(app, deps) {
@@ -22796,10 +23385,10 @@ function registerAnnotationsRoute(app, deps) {
22796
23385
  }
22797
23386
 
22798
23387
  // server/routes/contributions.ts
22799
- import { HTTPException as HTTPException2 } from "hono/http-exception";
23388
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
22800
23389
 
22801
23390
  // server/util/parse-query.ts
22802
- import { HTTPException } from "hono/http-exception";
23391
+ import { HTTPException as HTTPException2 } from "hono/http-exception";
22803
23392
  function parseCsv(value) {
22804
23393
  if (value === void 0) return [];
22805
23394
  return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -22808,7 +23397,7 @@ function parsePagination(query, defaults) {
22808
23397
  const offset = parseNonNegativeInt(query.offset, "offset", 0);
22809
23398
  const limit = parseNonNegativeInt(query.limit, "limit", defaults.limit);
22810
23399
  if (limit > defaults.max) {
22811
- throw new HTTPException(400, {
23400
+ throw new HTTPException2(400, {
22812
23401
  message: tx(SERVER_TEXTS.paginationLimitTooLarge, {
22813
23402
  value: limit,
22814
23403
  max: defaults.max
@@ -22822,7 +23411,7 @@ function parseBooleanFlag(value) {
22822
23411
  }
22823
23412
  function parseRequiredString(value, name) {
22824
23413
  if (typeof value !== "string" || value.length === 0) {
22825
- throw new HTTPException(400, {
23414
+ throw new HTTPException2(400, {
22826
23415
  message: tx(SERVER_TEXTS.queryRequiredString, { name })
22827
23416
  });
22828
23417
  }
@@ -22833,7 +23422,7 @@ function parseNonNegativeInt(raw, name, fallback) {
22833
23422
  const trimmed = raw.trim();
22834
23423
  const parsed = Number.parseInt(trimmed, 10);
22835
23424
  if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== trimmed) {
22836
- throw new HTTPException(400, {
23425
+ throw new HTTPException2(400, {
22837
23426
  message: tx(SERVER_TEXTS.paginationInvalidInteger, { name, value: raw })
22838
23427
  });
22839
23428
  }
@@ -22844,7 +23433,7 @@ function parseNonNegativeInt(raw, name, fallback) {
22844
23433
  var QUALIFIED_ID_SEGMENT = /^[A-Za-z0-9._-]+$/;
22845
23434
  function parseQualifiedIdSegment(value, name) {
22846
23435
  if (!QUALIFIED_ID_SEGMENT.test(value)) {
22847
- throw new HTTPException2(400, {
23436
+ throw new HTTPException3(400, {
22848
23437
  message: tx(SERVER_TEXTS.qualifiedIdMalformed, {
22849
23438
  name,
22850
23439
  value: sanitizeForTerminal(value)
@@ -22874,7 +23463,7 @@ function registerContributionsRoutes(app, deps) {
22874
23463
  (e) => e.pluginId === pluginId && e.extensionId === extensionId && e.contributionId === contributionId
22875
23464
  );
22876
23465
  if (!catalogEntry) {
22877
- throw new HTTPException2(404, {
23466
+ throw new HTTPException3(404, {
22878
23467
  message: tx(SERVER_TEXTS.contributionUnknown, {
22879
23468
  pluginId: sanitizeForTerminal(pluginId),
22880
23469
  extensionId: sanitizeForTerminal(extensionId),
@@ -22905,7 +23494,7 @@ function registerContributionsRoutes(app, deps) {
22905
23494
  }
22906
23495
 
22907
23496
  // server/routes/config.ts
22908
- import { HTTPException as HTTPException3 } from "hono/http-exception";
23497
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
22909
23498
 
22910
23499
  // server/envelope.ts
22911
23500
  var REST_ENVELOPE_SCHEMA_VERSION = "1";
@@ -22944,7 +23533,7 @@ function registerConfigRoute(app, deps) {
22944
23533
  try {
22945
23534
  loaded = deps.configService.get();
22946
23535
  } catch (err) {
22947
- throw new HTTPException3(500, { message: formatErrorMessage(err) });
23536
+ throw new HTTPException4(500, { message: formatErrorMessage(err) });
22948
23537
  }
22949
23538
  for (const warn of loaded.warnings) {
22950
23539
  log.warn(sanitizeForTerminal(warn));
@@ -22962,7 +23551,7 @@ function registerConfigRoute(app, deps) {
22962
23551
  }
22963
23552
 
22964
23553
  // server/routes/favorites.ts
22965
- import { HTTPException as HTTPException4 } from "hono/http-exception";
23554
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
22966
23555
 
22967
23556
  // server/path-codec.ts
22968
23557
  var PathCodecError = class extends Error {
@@ -23002,7 +23591,7 @@ function registerFavoritesRoutes(app, deps) {
23002
23591
  }
23003
23592
  );
23004
23593
  if (!result || !result.found) {
23005
- throw new HTTPException4(404, {
23594
+ throw new HTTPException5(404, {
23006
23595
  message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
23007
23596
  });
23008
23597
  }
@@ -23022,14 +23611,14 @@ function decodePath(pathB64) {
23022
23611
  return decodeNodePath(pathB64);
23023
23612
  } catch (err) {
23024
23613
  if (err instanceof PathCodecError) {
23025
- throw new HTTPException4(404, { message: SERVER_TEXTS.pathB64Malformed });
23614
+ throw new HTTPException5(404, { message: SERVER_TEXTS.pathB64Malformed });
23026
23615
  }
23027
23616
  throw err;
23028
23617
  }
23029
23618
  }
23030
23619
 
23031
23620
  // server/routes/graph.ts
23032
- import { HTTPException as HTTPException5 } from "hono/http-exception";
23621
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
23033
23622
  var DEFAULT_FORMAT2 = "ascii";
23034
23623
  var FORMAT_ID_PATTERN = /^[a-z0-9-]+$/;
23035
23624
  var FORMAT_ID_MAX = 32;
@@ -23037,7 +23626,7 @@ function registerGraphRoute(app, deps) {
23037
23626
  app.get("/api/graph", async (c) => {
23038
23627
  const format = c.req.query("format") ?? DEFAULT_FORMAT2;
23039
23628
  if (format.length > FORMAT_ID_MAX || !FORMAT_ID_PATTERN.test(format)) {
23040
- throw new HTTPException5(400, {
23629
+ throw new HTTPException6(400, {
23041
23630
  // Sanitize defensively, the regex above already rejects ANSI
23042
23631
  // and control bytes, but the message interpolates user input
23043
23632
  // and the BFF mirrors error envelopes into the server log.
@@ -23053,7 +23642,7 @@ function registerGraphRoute(app, deps) {
23053
23642
  const formatter = formatters.find((f) => f.formatId === format);
23054
23643
  if (!formatter) {
23055
23644
  const available = formatters.map((f) => f.formatId).sort().join(", ");
23056
- throw new HTTPException5(400, {
23645
+ throw new HTTPException6(400, {
23057
23646
  message: tx(SERVER_TEXTS.graphUnknownFormat, {
23058
23647
  format,
23059
23648
  available: available || "(none)"
@@ -23090,7 +23679,7 @@ function contentTypeFor(format) {
23090
23679
  }
23091
23680
 
23092
23681
  // server/health.ts
23093
- import { existsSync as existsSync23 } from "fs";
23682
+ import { existsSync as existsSync25 } from "fs";
23094
23683
  var FALLBACK_SCHEMA_VERSION = "1";
23095
23684
  function buildHealth(deps) {
23096
23685
  const dev = isDevBuild();
@@ -23099,7 +23688,7 @@ function buildHealth(deps) {
23099
23688
  schemaVersion: FALLBACK_SCHEMA_VERSION,
23100
23689
  specVersion: deps.specVersion,
23101
23690
  implVersion: VERSION,
23102
- db: existsSync23(deps.dbPath) ? "present" : "missing",
23691
+ db: existsSync25(deps.dbPath) ? "present" : "missing",
23103
23692
  cwd: deps.cwd,
23104
23693
  dbPath: deps.dbPath,
23105
23694
  // Only emit when truthy so a published install keeps the wire
@@ -23226,7 +23815,7 @@ function registerLinksRoute(app, deps) {
23226
23815
  }
23227
23816
 
23228
23817
  // server/routes/nodes.ts
23229
- import { HTTPException as HTTPException6 } from "hono/http-exception";
23818
+ import { HTTPException as HTTPException7 } from "hono/http-exception";
23230
23819
 
23231
23820
  // server/node-body.ts
23232
23821
  import { constants as fsConstants2 } from "fs";
@@ -23337,7 +23926,7 @@ function registerNodesRoutes(app, deps) {
23337
23926
  nodePath = decodeNodePath(pathB64);
23338
23927
  } catch (err) {
23339
23928
  if (err instanceof PathCodecError) {
23340
- throw new HTTPException6(404, { message: SERVER_TEXTS.pathB64Malformed });
23929
+ throw new HTTPException7(404, { message: SERVER_TEXTS.pathB64Malformed });
23341
23930
  }
23342
23931
  throw err;
23343
23932
  }
@@ -23350,7 +23939,7 @@ function registerNodesRoutes(app, deps) {
23350
23939
  bundle: null,
23351
23940
  isFavorite: false,
23352
23941
  contributions: [],
23353
- tags: { byAuthor: [], byUser: [] }
23942
+ tags: []
23354
23943
  };
23355
23944
  }
23356
23945
  const favSet = await adapter.favorites.listPaths();
@@ -23360,16 +23949,16 @@ function registerNodesRoutes(app, deps) {
23360
23949
  bundle: b,
23361
23950
  isFavorite: favSet.has(b.node.path),
23362
23951
  contributions: contributions2,
23363
- tags: groupTagsBySource(tagRows)
23952
+ tags: tagRows.map((r) => r.tag)
23364
23953
  };
23365
23954
  }
23366
23955
  );
23367
23956
  const bundle = result?.bundle ?? null;
23368
23957
  const isFavorite = result?.isFavorite ?? false;
23369
23958
  const contributions = result?.contributions ?? [];
23370
- const tags = result?.tags ?? { byAuthor: [], byUser: [] };
23959
+ const tags = result?.tags ?? [];
23371
23960
  if (!bundle) {
23372
- throw new HTTPException6(404, {
23961
+ throw new HTTPException7(404, {
23373
23962
  message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
23374
23963
  });
23375
23964
  }
@@ -23421,7 +24010,7 @@ function registerNodesRoutes(app, deps) {
23421
24010
  const contribByPath = contributionsOmitted ? /* @__PURE__ */ new Map() : await groupContributionsByPath(
23422
24011
  await adapter.contributions.listForPaths(pagePaths)
23423
24012
  );
23424
- const tagByPath = await groupTagsByPath(
24013
+ const tagByPath = groupTagsByPath(
23425
24014
  await adapter.tags.listForPaths(pagePaths)
23426
24015
  );
23427
24016
  return { contributionsByPath: contribByPath, tagsByPath: tagByPath };
@@ -23434,7 +24023,7 @@ function registerNodesRoutes(app, deps) {
23434
24023
  ...n,
23435
24024
  isFavorite: favSet.has(n.path),
23436
24025
  contributions: contributionsByPath.get(n.path) ?? [],
23437
- tags: tagsByPath.get(n.path) ?? { byAuthor: [], byUser: [] }
24026
+ tags: tagsByPath.get(n.path) ?? []
23438
24027
  }));
23439
24028
  return c.json(
23440
24029
  buildListEnvelope({
@@ -23457,24 +24046,15 @@ function registerNodesRoutes(app, deps) {
23457
24046
  function parseIncludes(raw) {
23458
24047
  return new Set(parseCsv(raw));
23459
24048
  }
23460
- function groupTagsBySource(rows) {
23461
- const byAuthor = /* @__PURE__ */ new Set();
23462
- const byUser = /* @__PURE__ */ new Set();
23463
- for (const r of rows) (r.source === "author" ? byAuthor : byUser).add(r.tag);
23464
- return {
23465
- byAuthor: [...byAuthor].sort(),
23466
- byUser: [...byUser].sort()
23467
- };
23468
- }
23469
- async function groupTagsByPath(rows) {
24049
+ function groupTagsByPath(rows) {
23470
24050
  const buckets = /* @__PURE__ */ new Map();
23471
24051
  for (const r of rows) {
23472
- const list = buckets.get(r.nodePath);
23473
- if (list) list.push({ tag: r.tag, source: r.source });
23474
- else buckets.set(r.nodePath, [{ tag: r.tag, source: r.source }]);
24052
+ const set = buckets.get(r.nodePath);
24053
+ if (set) set.add(r.tag);
24054
+ else buckets.set(r.nodePath, /* @__PURE__ */ new Set([r.tag]));
23475
24055
  }
23476
24056
  const out = /* @__PURE__ */ new Map();
23477
- for (const [path, entries] of buckets) out.set(path, groupTagsBySource(entries));
24057
+ for (const [path, set] of buckets) out.set(path, [...set].sort());
23478
24058
  return out;
23479
24059
  }
23480
24060
  async function groupContributionsByPath(rows) {
@@ -23488,11 +24068,11 @@ async function groupContributionsByPath(rows) {
23488
24068
  }
23489
24069
 
23490
24070
  // server/routes/plugins.ts
23491
- import { HTTPException as HTTPException8 } from "hono/http-exception";
24071
+ import { HTTPException as HTTPException9 } from "hono/http-exception";
23492
24072
 
23493
24073
  // server/util/parse-body.ts
23494
24074
  import { Ajv2020 as Ajv20207 } from "ajv/dist/2020.js";
23495
- import { HTTPException as HTTPException7 } from "hono/http-exception";
24075
+ import { HTTPException as HTTPException8 } from "hono/http-exception";
23496
24076
  function makeBodyValidator(schema, messages) {
23497
24077
  const ajv = new Ajv20207({ strict: false, allErrors: false });
23498
24078
  const validate = ajv.compile(schema);
@@ -23501,16 +24081,16 @@ function makeBodyValidator(schema, messages) {
23501
24081
  try {
23502
24082
  raw = await req.json();
23503
24083
  } catch {
23504
- throw new HTTPException7(400, { message: messages.notJson });
24084
+ throw new HTTPException8(400, { message: messages.notJson });
23505
24085
  }
23506
24086
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
23507
- throw new HTTPException7(400, { message: messages.notObject });
24087
+ throw new HTTPException8(400, { message: messages.notObject });
23508
24088
  }
23509
24089
  if (validate(raw)) {
23510
24090
  return raw;
23511
24091
  }
23512
24092
  const message = resolveErrorMessage(validate.errors, messages);
23513
- throw new HTTPException7(400, { message });
24093
+ throw new HTTPException8(400, { message });
23514
24094
  };
23515
24095
  }
23516
24096
  function resolveErrorMessage(errors, messages) {
@@ -23611,18 +24191,18 @@ function registerPluginsRoute(app, deps) {
23611
24191
  app.patch("/api/plugins/:id", async (c) => {
23612
24192
  const id = c.req.param("id");
23613
24193
  if (id.includes("/")) {
23614
- throw new HTTPException8(400, {
24194
+ throw new HTTPException9(400, {
23615
24195
  message: tx(SERVER_TEXTS.pluginsCascadeRouteQualifiedRejected, { id })
23616
24196
  });
23617
24197
  }
23618
24198
  const handle = findHandle(id, deps);
23619
24199
  if (!handle) {
23620
- throw new HTTPException8(404, {
24200
+ throw new HTTPException9(404, {
23621
24201
  message: tx(SERVER_TEXTS.pluginsUnknown, { id })
23622
24202
  });
23623
24203
  }
23624
24204
  if (isPluginLocked(id)) {
23625
- throw new HTTPException8(403, {
24205
+ throw new HTTPException9(403, {
23626
24206
  message: tx(SERVER_TEXTS.pluginsLocked, { id })
23627
24207
  });
23628
24208
  }
@@ -23636,18 +24216,18 @@ function registerPluginsRoute(app, deps) {
23636
24216
  const extensionId = c.req.param("extensionId");
23637
24217
  const handle = findHandle(bundleId, deps);
23638
24218
  if (!handle) {
23639
- throw new HTTPException8(404, {
24219
+ throw new HTTPException9(404, {
23640
24220
  message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
23641
24221
  });
23642
24222
  }
23643
24223
  if (!hasExtension(handle, extensionId)) {
23644
- throw new HTTPException8(404, {
24224
+ throw new HTTPException9(404, {
23645
24225
  message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { bundleId, extensionId })
23646
24226
  });
23647
24227
  }
23648
24228
  const qualified = qualifiedExtensionId(bundleId, extensionId);
23649
24229
  if (isPluginLocked(qualified) || isPluginLocked(bundleId)) {
23650
- throw new HTTPException8(403, {
24230
+ throw new HTTPException9(403, {
23651
24231
  message: tx(SERVER_TEXTS.pluginsExtensionLocked, { bundleId, extensionId })
23652
24232
  });
23653
24233
  }
@@ -23923,7 +24503,7 @@ function hasExtension(handle, extensionId) {
23923
24503
  }
23924
24504
 
23925
24505
  // server/routes/preferences.ts
23926
- import { HTTPException as HTTPException9 } from "hono/http-exception";
24506
+ import { HTTPException as HTTPException10 } from "hono/http-exception";
23927
24507
  function registerPreferencesRoute(app, _deps) {
23928
24508
  app.get("/api/preferences", (c) => {
23929
24509
  return c.json(buildEnvelope());
@@ -23936,20 +24516,43 @@ function registerPreferencesRoute(app, _deps) {
23936
24516
  }
23937
24517
  function buildEnvelope() {
23938
24518
  return {
23939
- updateCheck: { enabled: isUpdateCheckEnabled() }
24519
+ updateCheck: { enabled: isUpdateCheckEnabled() },
24520
+ telemetry: {
24521
+ errorsEnabled: isErrorTelemetryEnabled(),
24522
+ usageCliEnabled: isUsageCliTelemetryEnabled(),
24523
+ usageUiEnabled: isUsageUiTelemetryEnabled(),
24524
+ anonymousId: readAnonymousId(),
24525
+ environment: resolveTelemetryEnv()
24526
+ }
23940
24527
  };
23941
24528
  }
23942
24529
  function applyPatch(body) {
23943
- if (body.updateCheck && typeof body.updateCheck.enabled === "boolean") {
23944
- try {
24530
+ try {
24531
+ if (body.updateCheck && typeof body.updateCheck.enabled === "boolean") {
23945
24532
  writeUserSettings({ updateCheck: { enabled: body.updateCheck.enabled } });
23946
- } catch (err) {
23947
- throw new HTTPException9(400, {
23948
- message: tx(SERVER_TEXTS.preferencesPersistFailed, {
23949
- message: formatErrorMessage(err)
23950
- })
23951
- });
23952
24533
  }
24534
+ if (body.telemetry) {
24535
+ applyTelemetryPatch(body.telemetry);
24536
+ }
24537
+ } catch (err) {
24538
+ throw new HTTPException10(400, {
24539
+ message: tx(SERVER_TEXTS.preferencesPersistFailed, {
24540
+ message: formatErrorMessage(err)
24541
+ })
24542
+ });
24543
+ }
24544
+ }
24545
+ function applyTelemetryPatch(t) {
24546
+ if (typeof t.errorsEnabled === "boolean") {
24547
+ writeUserSettings({ telemetry: { errorsEnabled: t.errorsEnabled } });
24548
+ }
24549
+ if (typeof t.usageCliEnabled === "boolean") {
24550
+ writeUserSettings({ telemetry: { usageCliEnabled: t.usageCliEnabled } });
24551
+ if (t.usageCliEnabled) ensureAnonymousId();
24552
+ }
24553
+ if (typeof t.usageUiEnabled === "boolean") {
24554
+ writeUserSettings({ telemetry: { usageUiEnabled: t.usageUiEnabled } });
24555
+ if (t.usageUiEnabled) ensureAnonymousId();
23953
24556
  }
23954
24557
  }
23955
24558
  var PATCH_BODY_SCHEMA = {
@@ -23963,6 +24566,15 @@ var PATCH_BODY_SCHEMA = {
23963
24566
  properties: {
23964
24567
  enabled: { type: "boolean" }
23965
24568
  }
24569
+ },
24570
+ telemetry: {
24571
+ type: "object",
24572
+ additionalProperties: false,
24573
+ properties: {
24574
+ errorsEnabled: { type: "boolean" },
24575
+ usageCliEnabled: { type: "boolean" },
24576
+ usageUiEnabled: { type: "boolean" }
24577
+ }
23966
24578
  }
23967
24579
  }
23968
24580
  };
@@ -23973,20 +24585,24 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
23973
24585
  mapping: {
23974
24586
  ":minProperties": SERVER_TEXTS.preferencesBodyEmpty,
23975
24587
  "/updateCheck:type:object": SERVER_TEXTS.preferencesUpdateCheckNotObject,
23976
- "/updateCheck/enabled:type:boolean": SERVER_TEXTS.preferencesUpdateCheckEnabledNotBoolean
24588
+ "/updateCheck/enabled:type:boolean": SERVER_TEXTS.preferencesUpdateCheckEnabledNotBoolean,
24589
+ "/telemetry:type:object": SERVER_TEXTS.preferencesTelemetryNotObject,
24590
+ "/telemetry/errorsEnabled:type:boolean": SERVER_TEXTS.preferencesTelemetryErrorsEnabledNotBoolean,
24591
+ "/telemetry/usageCliEnabled:type:boolean": SERVER_TEXTS.preferencesTelemetryUsageCliEnabledNotBoolean,
24592
+ "/telemetry/usageUiEnabled:type:boolean": SERVER_TEXTS.preferencesTelemetryUsageUiEnabledNotBoolean
23977
24593
  }
23978
24594
  });
23979
24595
 
23980
24596
  // server/routes/project-ignore.ts
23981
- import { HTTPException as HTTPException10 } from "hono/http-exception";
24597
+ import { HTTPException as HTTPException11 } from "hono/http-exception";
23982
24598
 
23983
24599
  // server/util/skillmapignore-io.ts
23984
- import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
24600
+ import { existsSync as existsSync26, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
23985
24601
  import { resolve as resolve35 } from "path";
23986
24602
  var IGNORE_FILENAME2 = ".skillmapignore";
23987
24603
  function readPatterns(cwd) {
23988
24604
  const path = resolve35(cwd, IGNORE_FILENAME2);
23989
- if (!existsSync24(path)) return [];
24605
+ if (!existsSync26(path)) return [];
23990
24606
  let raw;
23991
24607
  try {
23992
24608
  raw = readFileSync17(path, "utf8");
@@ -23997,7 +24613,7 @@ function readPatterns(cwd) {
23997
24613
  }
23998
24614
  function writePatterns(cwd, nextPatterns) {
23999
24615
  const path = resolve35(cwd, IGNORE_FILENAME2);
24000
- const prior = existsSync24(path) ? safeRead(path) : "";
24616
+ const prior = existsSync26(path) ? safeRead(path) : "";
24001
24617
  const content = buildContent(prior, nextPatterns);
24002
24618
  writeFileSync2(path, content, "utf8");
24003
24619
  }
@@ -24065,12 +24681,12 @@ async function applyPatch2(deps, body) {
24065
24681
  for (const raw of body.patterns) {
24066
24682
  const t = raw.trim();
24067
24683
  if (t.length === 0) {
24068
- throw new HTTPException10(400, {
24684
+ throw new HTTPException11(400, {
24069
24685
  message: SERVER_TEXTS.projectIgnorePatternEmpty
24070
24686
  });
24071
24687
  }
24072
24688
  if (seen.has(t)) {
24073
- throw new HTTPException10(400, {
24689
+ throw new HTTPException11(400, {
24074
24690
  message: tx(SERVER_TEXTS.projectIgnorePatternDuplicate, { pattern: t })
24075
24691
  });
24076
24692
  }
@@ -24081,7 +24697,7 @@ async function applyPatch2(deps, body) {
24081
24697
  try {
24082
24698
  writePatterns(cwd, trimmed);
24083
24699
  } catch (err) {
24084
- throw new HTTPException10(400, {
24700
+ throw new HTTPException11(400, {
24085
24701
  message: tx(SERVER_TEXTS.projectIgnorePersistFailed, {
24086
24702
  message: formatErrorMessage(err)
24087
24703
  })
@@ -24159,7 +24775,7 @@ var parsePatchBody3 = makeBodyValidator(PATCH_BODY_SCHEMA2, {
24159
24775
 
24160
24776
  // server/routes/project-preferences.ts
24161
24777
  import { statSync as statSync9 } from "fs";
24162
- import { HTTPException as HTTPException11 } from "hono/http-exception";
24778
+ import { HTTPException as HTTPException12 } from "hono/http-exception";
24163
24779
  function registerProjectPreferencesRoute(app, deps) {
24164
24780
  app.get("/api/project-preferences", (c) => {
24165
24781
  return c.json(buildEnvelope3(deps));
@@ -24187,7 +24803,7 @@ async function applyPatch3(deps, body) {
24187
24803
  const cwd = deps.runtimeContext.cwd;
24188
24804
  const missingPaths = collectMissingPaths(writes, cwd);
24189
24805
  if (missingPaths.length > 0) {
24190
- throw new HTTPException11(400, {
24806
+ throw new HTTPException12(400, {
24191
24807
  message: tx(SERVER_TEXTS.projectPrefsPathNotFound, {
24192
24808
  paths: missingPaths.join(", ")
24193
24809
  })
@@ -24196,7 +24812,7 @@ async function applyPatch3(deps, body) {
24196
24812
  const exposures = writes.map((w) => projectPathExposure({ key: w.key, value: w.value, cwd })).filter((e) => e.expandsSurface);
24197
24813
  if (exposures.length > 0 && body.confirm !== true) {
24198
24814
  const exposed = exposures.flatMap((e) => e.exposedPaths);
24199
- throw new HTTPException11(412, {
24815
+ throw new HTTPException12(412, {
24200
24816
  message: tx(SERVER_TEXTS.projectPrefsConfirmRequired, {
24201
24817
  paths: exposed.join(", ")
24202
24818
  })
@@ -24248,7 +24864,7 @@ function runWrite(w, cwd) {
24248
24864
  try {
24249
24865
  writeConfigValue(w.key, w.value, { target: "project-local", cwd });
24250
24866
  } catch (err) {
24251
- throw new HTTPException11(400, {
24867
+ throw new HTTPException12(400, {
24252
24868
  message: tx(SERVER_TEXTS.projectPrefsPersistFailed, {
24253
24869
  key: w.key,
24254
24870
  message: formatErrorMessage(err)
@@ -24347,8 +24963,8 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
24347
24963
  });
24348
24964
 
24349
24965
  // server/routes/active-provider.ts
24350
- import { existsSync as existsSync25 } from "fs";
24351
- import { HTTPException as HTTPException12 } from "hono/http-exception";
24966
+ import { existsSync as existsSync27 } from "fs";
24967
+ import { HTTPException as HTTPException13 } from "hono/http-exception";
24352
24968
  function registerActiveProviderRoute(app, deps) {
24353
24969
  app.get("/api/active-provider", (c) => {
24354
24970
  return c.json(buildEnvelope4(deps));
@@ -24373,14 +24989,14 @@ function applyLensSwitch(deps, newValue) {
24373
24989
  try {
24374
24990
  writeConfigValue("activeProvider", newValue, { target: "project", cwd });
24375
24991
  } catch (err) {
24376
- throw new HTTPException12(400, {
24992
+ throw new HTTPException13(400, {
24377
24993
  message: tx(SERVER_TEXTS.activeProviderPersistFailed, {
24378
24994
  message: formatErrorMessage(err)
24379
24995
  })
24380
24996
  });
24381
24997
  }
24382
24998
  const dbPath = resolveDbPath({ db: void 0, cwd });
24383
- if (!existsSync25(dbPath)) return { dropped: null };
24999
+ if (!existsSync27(dbPath)) return { dropped: null };
24384
25000
  const dropResult = dropScanZone(dbPath);
24385
25001
  return {
24386
25002
  dropped: {
@@ -24409,7 +25025,7 @@ var parsePatchBody5 = makeBodyValidator(PATCH_BODY_SCHEMA4, {
24409
25025
  });
24410
25026
 
24411
25027
  // server/routes/scan.ts
24412
- import { HTTPException as HTTPException13 } from "hono/http-exception";
25028
+ import { HTTPException as HTTPException14 } from "hono/http-exception";
24413
25029
 
24414
25030
  // server/scan-mutex.ts
24415
25031
  var inFlight = null;
@@ -24501,6 +25117,14 @@ function createWatcherService(opts) {
24501
25117
  onPluginWarning: (message) => {
24502
25118
  log.warn(sanitizeForTerminal(message));
24503
25119
  },
25120
+ onDriftReset: (info) => {
25121
+ log.warn(
25122
+ tx(SERVER_TEXTS.watcherDriftReset, {
25123
+ dbVersion: info.dbVersion,
25124
+ currentVersion: info.currentVersion
25125
+ })
25126
+ );
25127
+ },
24504
25128
  onReady: (info) => {
24505
25129
  opts.broadcaster.broadcast(
24506
25130
  buildWatcherStartedEvent({ roots: info.roots, debounceMs: info.debounceMs })
@@ -24567,7 +25191,7 @@ function registerScanRoute(app, deps) {
24567
25191
  }
24568
25192
  async function runPersistedScan(c, deps) {
24569
25193
  if (deps.options.noBuiltIns || deps.options.noPlugins) {
24570
- throw new HTTPException13(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
25194
+ throw new HTTPException14(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
24571
25195
  }
24572
25196
  const dbExists = await tryWithSqlite(
24573
25197
  { databasePath: deps.options.dbPath, autoBackup: false },
@@ -24604,7 +25228,7 @@ async function runPersistedScan(c, deps) {
24604
25228
  ...deps.options.maxNodes !== void 0 ? { maxNodes: deps.options.maxNodes } : {}
24605
25229
  });
24606
25230
  if (outcome.kind !== "ok") {
24607
- throw new HTTPException13(500, {
25231
+ throw new HTTPException14(500, {
24608
25232
  message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.scanGuardTrip, { existing: outcome.existing }) : outcome.message
24609
25233
  });
24610
25234
  }
@@ -24612,7 +25236,7 @@ async function runPersistedScan(c, deps) {
24612
25236
  });
24613
25237
  } catch (err) {
24614
25238
  if (err instanceof ScanBusyError) {
24615
- throw new HTTPException13(409, { message: SERVER_TEXTS.scanPostBusy });
25239
+ throw new HTTPException14(409, { message: SERVER_TEXTS.scanPostBusy });
24616
25240
  }
24617
25241
  throw err;
24618
25242
  }
@@ -24643,13 +25267,8 @@ async function loadPersistedScan(deps) {
24643
25267
  if (list) list.push(r);
24644
25268
  else byPath3.set(r.nodePath, [r]);
24645
25269
  }
24646
- const tagBuckets = /* @__PURE__ */ new Map();
24647
- for (const r of tagRows) {
24648
- const list = tagBuckets.get(r.nodePath);
24649
- if (list) list.push({ tag: r.tag, source: r.source });
24650
- else tagBuckets.set(r.nodePath, [{ tag: r.tag, source: r.source }]);
24651
- }
24652
- return { loaded, favSet, contribByPath: byPath3, tagBuckets };
25270
+ const tagsByPath = groupTagsByPath2(tagRows);
25271
+ return { loaded, favSet, contribByPath: byPath3, tagsByPath };
24653
25272
  }
24654
25273
  );
24655
25274
  if (opened === null) {
@@ -24661,22 +25280,24 @@ async function loadPersistedScan(deps) {
24661
25280
  ...n,
24662
25281
  isFavorite: opened.favSet.has(n.path),
24663
25282
  contributions: opened.contribByPath.get(n.path) ?? [],
24664
- tags: groupTagsBySource2(opened.tagBuckets.get(n.path) ?? [])
25283
+ tags: opened.tagsByPath.get(n.path) ?? []
24665
25284
  }))
24666
25285
  };
24667
25286
  }
24668
- function groupTagsBySource2(rows) {
24669
- const byAuthor = /* @__PURE__ */ new Set();
24670
- const byUser = /* @__PURE__ */ new Set();
24671
- for (const r of rows) (r.source === "author" ? byAuthor : byUser).add(r.tag);
24672
- return {
24673
- byAuthor: [...byAuthor].sort(),
24674
- byUser: [...byUser].sort()
24675
- };
25287
+ function groupTagsByPath2(rows) {
25288
+ const buckets = /* @__PURE__ */ new Map();
25289
+ for (const r of rows) {
25290
+ const set = buckets.get(r.nodePath);
25291
+ if (set) set.add(r.tag);
25292
+ else buckets.set(r.nodePath, /* @__PURE__ */ new Set([r.tag]));
25293
+ }
25294
+ const out = /* @__PURE__ */ new Map();
25295
+ for (const [path, set] of buckets) out.set(path, [...set].sort());
25296
+ return out;
24676
25297
  }
24677
25298
  async function runFreshScan(deps) {
24678
25299
  if (deps.options.noBuiltIns || deps.options.noPlugins) {
24679
- throw new HTTPException13(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
25300
+ throw new HTTPException14(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
24680
25301
  }
24681
25302
  const resolveEnabledOverride = await buildBffResolverOverride(deps);
24682
25303
  const outcome = await runScanForCommand({
@@ -24711,7 +25332,7 @@ async function runFreshScan(deps) {
24711
25332
  ...deps.options.maxNodes !== void 0 ? { maxNodes: deps.options.maxNodes } : {}
24712
25333
  });
24713
25334
  if (outcome.kind !== "ok") {
24714
- throw new HTTPException13(500, {
25335
+ throw new HTTPException14(500, {
24715
25336
  message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.freshScanGuardTrip, { existing: outcome.existing }) : outcome.message
24716
25337
  });
24717
25338
  }
@@ -24751,7 +25372,7 @@ function emptyScanResult() {
24751
25372
  }
24752
25373
 
24753
25374
  // server/routes/sidecar.ts
24754
- import { HTTPException as HTTPException14 } from "hono/http-exception";
25375
+ import { HTTPException as HTTPException15 } from "hono/http-exception";
24755
25376
  import { resolve as resolve36 } from "path";
24756
25377
  var STATUS_FRESH = "fresh";
24757
25378
  var ENVELOPE_KIND2 = "sidecar.bumped";
@@ -24788,11 +25409,11 @@ function registerSidecarRoutes(app, deps) {
24788
25409
  assertContained(deps.runtimeContext.cwd, node.path);
24789
25410
  absPath = resolve36(deps.runtimeContext.cwd, node.path);
24790
25411
  } catch (err) {
24791
- throw new HTTPException14(400, { message: formatErrorMessage(err) });
25412
+ throw new HTTPException15(400, { message: formatErrorMessage(err) });
24792
25413
  }
24793
25414
  const result = invokeBump2(node, absPath, body);
24794
25415
  if (result.report.ok === false && result.report.reason === "fresh") {
24795
- throw new HTTPException14(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
25416
+ throw new HTTPException15(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
24796
25417
  }
24797
25418
  if (result.report.ok === true && result.report.noop === true) {
24798
25419
  const envelope2 = {
@@ -24819,7 +25440,7 @@ function registerSidecarRoutes(app, deps) {
24819
25440
  }
24820
25441
  } catch (err) {
24821
25442
  if (err instanceof EConsentRequiredError) throw err;
24822
- throw new HTTPException14(500, { message: formatErrorMessage(err) });
25443
+ throw new HTTPException15(500, { message: formatErrorMessage(err) });
24823
25444
  }
24824
25445
  if (body.confirm === true) {
24825
25446
  deps.configService.reload();
@@ -24856,7 +25477,7 @@ async function loadNode(deps, nodePath) {
24856
25477
  );
24857
25478
  const node = persisted?.nodes.find((n) => n.path === nodePath);
24858
25479
  if (!node) {
24859
- throw new HTTPException14(404, {
25480
+ throw new HTTPException15(404, {
24860
25481
  message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
24861
25482
  });
24862
25483
  }
@@ -24864,7 +25485,7 @@ async function loadNode(deps, nodePath) {
24864
25485
  }
24865
25486
  function invokeBump2(node, absPath, body) {
24866
25487
  if (!nodeBumpAction.invoke) {
24867
- throw new HTTPException14(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
25488
+ throw new HTTPException15(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
24868
25489
  }
24869
25490
  const input = {};
24870
25491
  if (body.force === true) input.force = true;
@@ -24910,7 +25531,7 @@ function registerUpdateStatusRoute(app, deps) {
24910
25531
  }
24911
25532
 
24912
25533
  // server/static.ts
24913
- import { existsSync as existsSync26 } from "fs";
25534
+ import { existsSync as existsSync28 } from "fs";
24914
25535
  import { readFile as readFile6 } from "fs/promises";
24915
25536
  import { extname, join as join19 } from "path";
24916
25537
  import { serveStatic } from "@hono/node-server/serve-static";
@@ -24965,7 +25586,7 @@ function createSpaFallback(opts) {
24965
25586
  if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
24966
25587
  if (opts.uiDist === null) return htmlResponse(c, placeholder);
24967
25588
  const indexPath = join19(opts.uiDist, INDEX_HTML);
24968
- if (!existsSync26(indexPath)) return htmlResponse(c, placeholder);
25589
+ if (!existsSync28(indexPath)) return htmlResponse(c, placeholder);
24969
25590
  return fileResponse(c, indexPath);
24970
25591
  };
24971
25592
  }
@@ -25051,13 +25672,13 @@ function attachBroadcasterRoute(app, broadcaster) {
25051
25672
 
25052
25673
  // server/app.ts
25053
25674
  var BODY_LIMIT_BYTES = 1024 * 1024;
25054
- var DbMissingError = class extends HTTPException15 {
25675
+ var DbMissingError = class extends HTTPException16 {
25055
25676
  constructor(message) {
25056
25677
  super(500, { message });
25057
25678
  this.name = "DbMissingError";
25058
25679
  }
25059
25680
  };
25060
- var BulkValidationError = class extends HTTPException15 {
25681
+ var BulkValidationError = class extends HTTPException16 {
25061
25682
  id;
25062
25683
  code;
25063
25684
  constructor(init) {
@@ -25067,7 +25688,7 @@ var BulkValidationError = class extends HTTPException15 {
25067
25688
  this.code = init.code;
25068
25689
  }
25069
25690
  };
25070
- var LoopbackGateError = class extends HTTPException15 {
25691
+ var LoopbackGateError = class extends HTTPException16 {
25071
25692
  code;
25072
25693
  constructor(init) {
25073
25694
  super(403, { message: init.message });
@@ -25080,6 +25701,7 @@ function createApp(deps) {
25080
25701
  const configService = new ConfigService({
25081
25702
  cwd: deps.runtimeContext.cwd
25082
25703
  });
25704
+ app.use("*", createSentryRequestCapture());
25083
25705
  app.use("*", createLoopbackGate({ port: deps.options.port }));
25084
25706
  app.use("*", createSecurityHeaders());
25085
25707
  app.use(
@@ -25087,7 +25709,7 @@ function createApp(deps) {
25087
25709
  bodyLimit({
25088
25710
  maxSize: BODY_LIMIT_BYTES,
25089
25711
  onError: () => {
25090
- throw new HTTPException15(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
25712
+ throw new HTTPException16(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
25091
25713
  }
25092
25714
  })
25093
25715
  );
@@ -25133,7 +25755,7 @@ function createApp(deps) {
25133
25755
  registerActiveProviderRoute(app, routeDeps);
25134
25756
  registerProjectIgnoreRoute(app, routeDeps);
25135
25757
  app.all("/api/*", (c) => {
25136
- throw new HTTPException15(404, {
25758
+ throw new HTTPException16(404, {
25137
25759
  message: tx(SERVER_TEXTS.unknownApiEndpoint, { path: sanitizeForTerminal(c.req.path) })
25138
25760
  });
25139
25761
  });
@@ -25141,7 +25763,7 @@ function createApp(deps) {
25141
25763
  app.use("*", createStaticHandler({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
25142
25764
  app.get("*", createSpaFallback({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
25143
25765
  app.notFound((c) => {
25144
- throw new HTTPException15(404, {
25766
+ throw new HTTPException16(404, {
25145
25767
  message: tx(SERVER_TEXTS.unknownPath, { path: sanitizeForTerminal(c.req.path) })
25146
25768
  });
25147
25769
  });
@@ -25196,7 +25818,7 @@ function formatError2(err, c) {
25196
25818
  };
25197
25819
  return c.json(envelope, 403);
25198
25820
  }
25199
- if (err instanceof HTTPException15) {
25821
+ if (err instanceof HTTPException16) {
25200
25822
  const status = err.status;
25201
25823
  const envelope = {
25202
25824
  ok: false,
@@ -25295,7 +25917,7 @@ var WsBroadcaster = class {
25295
25917
  * stop the rest from receiving the event. A failing socket is closed
25296
25918
  * + unregistered so the next broadcast doesn't waste cycles on it.
25297
25919
  *
25298
- * Backpressure check (per AGENTS.md §Watcher integration): if a
25920
+ * Backpressure check (per context/kernel.md §Kernel boundaries): if a
25299
25921
  * client's `bufferedAmount` exceeds `MAX_BUFFERED_BYTES`, it's evicted
25300
25922
  * with close code 1009. The check runs BEFORE `send` so the threshold
25301
25923
  * acts as an admission gate, not a post-mortem.
@@ -25315,8 +25937,8 @@ var WsBroadcaster = class {
25315
25937
  return;
25316
25938
  }
25317
25939
  const snapshot = Array.from(this.#clients);
25318
- for (const client of snapshot) {
25319
- this.#deliver(client, payload);
25940
+ for (const client2 of snapshot) {
25941
+ this.#deliver(client2, payload);
25320
25942
  }
25321
25943
  }
25322
25944
  /**
@@ -25330,9 +25952,9 @@ var WsBroadcaster = class {
25330
25952
  this.#shutDown = true;
25331
25953
  const snapshot = Array.from(this.#clients);
25332
25954
  this.#clients.clear();
25333
- for (const client of snapshot) {
25955
+ for (const client2 of snapshot) {
25334
25956
  try {
25335
- client.close(CLOSE_CODE_GOING_AWAY, "server shutdown");
25957
+ client2.close(CLOSE_CODE_GOING_AWAY, "server shutdown");
25336
25958
  } catch {
25337
25959
  }
25338
25960
  }
@@ -25341,31 +25963,31 @@ var WsBroadcaster = class {
25341
25963
  * Per-client delivery: backpressure check, then `send()`. Eviction +
25342
25964
  * unregistration on either failure mode.
25343
25965
  */
25344
- #deliver(client, payload) {
25345
- if (client.readyState !== READY_STATE_OPEN) {
25346
- this.#clients.delete(client);
25966
+ #deliver(client2, payload) {
25967
+ if (client2.readyState !== READY_STATE_OPEN) {
25968
+ this.#clients.delete(client2);
25347
25969
  return;
25348
25970
  }
25349
- if (client.bufferedAmount > MAX_BUFFERED_BYTES) {
25350
- this.#clients.delete(client);
25971
+ if (client2.bufferedAmount > MAX_BUFFERED_BYTES) {
25972
+ this.#clients.delete(client2);
25351
25973
  try {
25352
- client.close(CLOSE_CODE_MESSAGE_TOO_BIG, "backpressure exceeded");
25974
+ client2.close(CLOSE_CODE_MESSAGE_TOO_BIG, "backpressure exceeded");
25353
25975
  } catch {
25354
25976
  }
25355
25977
  log.warn(
25356
25978
  tx(SERVER_TEXTS.wsBackpressureEvicted, {
25357
- buffered: String(client.bufferedAmount),
25979
+ buffered: String(client2.bufferedAmount),
25358
25980
  threshold: String(MAX_BUFFERED_BYTES)
25359
25981
  })
25360
25982
  );
25361
25983
  return;
25362
25984
  }
25363
25985
  try {
25364
- client.send(payload);
25986
+ client2.send(payload);
25365
25987
  } catch (err) {
25366
- this.#clients.delete(client);
25988
+ this.#clients.delete(client2);
25367
25989
  try {
25368
- client.close();
25990
+ client2.close();
25369
25991
  } catch {
25370
25992
  }
25371
25993
  const message = formatErrorMessage(err);
@@ -25574,7 +26196,7 @@ function validateNoUi(noUi, uiDist) {
25574
26196
  }
25575
26197
 
25576
26198
  // server/paths.ts
25577
- import { existsSync as existsSync27, statSync as statSync10 } from "fs";
26199
+ import { existsSync as existsSync29, statSync as statSync10 } from "fs";
25578
26200
  import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
25579
26201
  import { fileURLToPath as fileURLToPath6 } from "url";
25580
26202
  var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
@@ -25589,10 +26211,10 @@ function resolveExplicitUiDist(ctx, raw) {
25589
26211
  return isAbsolute11(raw) ? raw : resolve37(ctx.cwd, raw);
25590
26212
  }
25591
26213
  function isUiBundleDir(path) {
25592
- if (!existsSync27(path)) return false;
26214
+ if (!existsSync29(path)) return false;
25593
26215
  try {
25594
26216
  if (!statSync10(path).isDirectory()) return false;
25595
- return existsSync27(join20(path, INDEX_HTML2));
26217
+ return existsSync29(join20(path, INDEX_HTML2));
25596
26218
  } catch {
25597
26219
  return false;
25598
26220
  }
@@ -26126,7 +26748,7 @@ var ServeCommand = class extends SmCommand {
26126
26748
  emitElapsed = false;
26127
26749
  // CLI orchestrator with multi-flag handling, each `if (this.flag)`
26128
26750
  // branch is one cyclomatic point. Splitting per branch scatters the
26129
- // validation away from the flag it gates. Per AGENTS.md §Linting
26751
+ // validation away from the flag it gates. Per context/lint.md
26130
26752
  // category 1 ("CLI orchestrators with multi-flag handling").
26131
26753
  // eslint-disable-next-line complexity
26132
26754
  async run() {
@@ -26147,7 +26769,7 @@ var ServeCommand = class extends SmCommand {
26147
26769
  return ExitCode.Error;
26148
26770
  }
26149
26771
  const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
26150
- if (this.db !== void 0 && !existsSync28(dbPath)) {
26772
+ if (this.db !== void 0 && !existsSync30(dbPath)) {
26151
26773
  this.printer.info(
26152
26774
  tx(SERVE_TEXTS.dbNotFound, {
26153
26775
  glyph: errGlyph,
@@ -26229,6 +26851,7 @@ var ServeCommand = class extends SmCommand {
26229
26851
  this.printer.info(formatValidationError(validation.error, stderrAnsi));
26230
26852
  return ExitCode.Error;
26231
26853
  }
26854
+ await initSentryBff(VERSION);
26232
26855
  let handle;
26233
26856
  try {
26234
26857
  handle = await createServer(validation.options);
@@ -27412,7 +28035,7 @@ var STUB_COMMANDS = [
27412
28035
  ];
27413
28036
 
27414
28037
  // cli/commands/tutorial.ts
27415
- import { cpSync as cpSync2, existsSync as existsSync29, mkdirSync as mkdirSync6, rmSync as rmSync2, statSync as statSync11 } from "fs";
28038
+ import { cpSync as cpSync2, existsSync as existsSync31, mkdirSync as mkdirSync6, rmSync as rmSync2, statSync as statSync11 } from "fs";
27416
28039
  import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
27417
28040
  import { fileURLToPath as fileURLToPath7 } from "url";
27418
28041
  import { Command as Command37, Option as Option35 } from "clipanion";
@@ -27511,7 +28134,7 @@ var TutorialCommand = class extends SmCommand {
27511
28134
  const spec = VARIANT_SPECS[variant];
27512
28135
  const targetDir = join21(ctx.cwd, ".claude", "skills", spec.slug);
27513
28136
  const targetDisplay = `.claude/skills/${spec.slug}/`;
27514
- if (existsSync29(targetDir) && !this.force) {
28137
+ if (existsSync31(targetDir) && !this.force) {
27515
28138
  this.printer.error(
27516
28139
  tx(TUTORIAL_TEXTS.alreadyExists, {
27517
28140
  glyph: errGlyph,
@@ -27594,7 +28217,7 @@ function resolveSkillSourceDir(variant) {
27594
28217
  resolve39(here, "../cli/tutorial", spec.slug)
27595
28218
  ];
27596
28219
  for (const candidate of candidates) {
27597
- if (existsSync29(candidate) && statSync11(candidate).isDirectory()) {
28220
+ if (existsSync31(candidate) && statSync11(candidate).isDirectory()) {
27598
28221
  cachedSourceDirs.set(variant, candidate);
27599
28222
  return candidate;
27600
28223
  }
@@ -27698,6 +28321,7 @@ cli.register(RootHelpCommand);
27698
28321
  cli.register(HelpCommand);
27699
28322
  cli.register(InitCommand);
27700
28323
  cli.register(TutorialCommand);
28324
+ cli.register(IntentionalFailCommand);
27701
28325
  cli.register(ScanCommand);
27702
28326
  cli.register(ScanCompareCommand);
27703
28327
  cli.register(ServeCommand);
@@ -27731,6 +28355,13 @@ var logLevel = resolveLogLevel({
27731
28355
  configureLogger(new Logger({ level: logLevel, stream: process.stderr }));
27732
28356
  var bareArgs = resolveBareInvocation(args);
27733
28357
  var routedArgs = routeHelpArgs(bareArgs ?? args, cli);
28358
+ var telemetryVerb = routedArgs[0];
28359
+ await maybeRunFirstRunPrompt();
28360
+ if (telemetryVerb !== "serve") {
28361
+ await initSentryCli(VERSION);
28362
+ setTelemetryVerbTag(telemetryVerb);
28363
+ await initUsageCli();
28364
+ }
27734
28365
  var lifecycleDispatcher = makeHookDispatcher(
27735
28366
  builtIns().hooks ?? [],
27736
28367
  new InMemoryProgressEmitter()
@@ -27767,10 +28398,18 @@ var exitCode = await cli.run(routedArgs, {
27767
28398
  stdout: process.stdout,
27768
28399
  stderr: process.stderr
27769
28400
  });
28401
+ if (telemetryVerb !== void 0 && telemetryVerb !== "") {
28402
+ const knownVerbs = new Set(
28403
+ registeredVerbPaths(cli).map((path) => path[0]).filter((token) => token !== void 0)
28404
+ );
28405
+ captureCliInvocation(telemetryVerb, extractFlagNames(routedArgs.slice(1)), knownVerbs);
28406
+ }
27770
28407
  await lifecycleDispatcher.dispatch(
27771
28408
  "shutdown",
27772
28409
  makeEvent("shutdown", { exitCode })
27773
28410
  );
28411
+ await closeSentryCli();
28412
+ await flushUsageCli();
27774
28413
  process.exit(exitCode);
27775
28414
  function resolveBareInvocation(rawArgs) {
27776
28415
  if (rawArgs.length === 0) return resolveBareDefault();
@@ -27779,7 +28418,7 @@ function resolveBareInvocation(rawArgs) {
27779
28418
  if (first !== void 0 && first.startsWith("-") && !passthrough.has(first)) {
27780
28419
  const isSingleDashLong = !first.startsWith("--") && first.length > 2;
27781
28420
  if (isSingleDashLong) return null;
27782
- if (existsSync30(defaultProjectDbPath(defaultRuntimeContext()))) {
28421
+ if (existsSync32(defaultProjectDbPath(defaultRuntimeContext()))) {
27783
28422
  return ["serve", ...rawArgs];
27784
28423
  }
27785
28424
  return resolveBareDefault();
@@ -27788,7 +28427,7 @@ function resolveBareInvocation(rawArgs) {
27788
28427
  }
27789
28428
  function resolveBareDefault() {
27790
28429
  const ctx = defaultRuntimeContext();
27791
- if (existsSync30(defaultProjectDbPath(ctx))) {
28430
+ if (existsSync32(defaultProjectDbPath(ctx))) {
27792
28431
  return ["serve"];
27793
28432
  }
27794
28433
  const stderr = process.stderr;