@skill-map/cli 0.69.0 → 0.71.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 (45) hide show
  1. package/dist/cli/tutorial/sm-tutorial/SKILL.md +8 -3
  2. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/agents-hub/providers/codex/en/agents-hub.md +2 -0
  3. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/agents-hub/providers/codex/es/agents-hub.md +2 -0
  4. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/providers/codex/en/todo-bullet-agent.md +1 -0
  5. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/providers/codex/en/todo-bullet-command.md +1 -0
  6. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/providers/codex/en/todo-bullet-skill.md +1 -0
  7. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/providers/codex/es/todo-bullet-agent.md +1 -0
  8. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/providers/codex/es/todo-bullet-command.md +1 -0
  9. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/providers/codex/es/todo-bullet-skill.md +1 -0
  10. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/providers/codex/en/__PROVIDER__/skills/publish/SKILL.md +2 -2
  11. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/providers/codex/es/__PROVIDER__/skills/publish/SKILL.md +2 -2
  12. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/providers/codex/en/.codex/agents/master-agent.toml +1 -1
  13. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/providers/codex/es/.codex/agents/master-agent.toml +1 -1
  14. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/providers/codex/en/.codex/agents/content-editor.toml +1 -1
  15. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/providers/codex/es/.codex/agents/content-editor.toml +1 -1
  16. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/providers/codex/en/.codex/agents/demo-agent.toml +1 -1
  17. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/providers/codex/es/.codex/agents/demo-agent.toml +1 -1
  18. package/dist/cli/tutorial/sm-tutorial/references/_core.md +73 -47
  19. package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +2 -2
  20. package/dist/cli/tutorial/sm-tutorial/references/part-authoring.md +0 -7
  21. package/dist/cli/tutorial/sm-tutorial/references/part-basic-daily.md +10 -26
  22. package/dist/cli/tutorial/sm-tutorial/references/part-basic-fundamentals.md +10 -18
  23. package/dist/cli/tutorial/sm-tutorial/references/part-basic-kickoff.md +12 -31
  24. package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +0 -4
  25. package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +97 -43
  26. package/dist/cli/tutorial/sm-tutorial/references/part-fundamentals.md +11 -14
  27. package/dist/cli/tutorial/sm-tutorial/references/part-mcp.md +4 -14
  28. package/dist/cli/tutorial/sm-tutorial/references/part-plugins.md +0 -8
  29. package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +18 -36
  30. package/dist/cli/tutorial/sm-tutorial/references/part-settings.md +2 -10
  31. package/dist/cli/tutorial/sm-tutorial/scripts/lib/paths.js +6 -5
  32. package/dist/cli.js +441 -269
  33. package/dist/index.js +115 -18
  34. package/dist/kernel/index.d.ts +27 -0
  35. package/dist/kernel/index.js +115 -18
  36. package/dist/ui/chunk-CK4C2IIP.js +3 -0
  37. package/dist/ui/{chunk-RRRXQNG6.js → chunk-EVNCL7FV.js} +21 -21
  38. package/dist/ui/{chunk-E7GLGHVY.js → chunk-GUGB4JY5.js} +1 -1
  39. package/dist/ui/{chunk-SXSNTF26.js → chunk-RSPEJBPT.js} +1 -1
  40. package/dist/ui/chunk-SQCXHF3J.js +2 -0
  41. package/dist/ui/index.html +1 -1
  42. package/dist/ui/{main-23NGLEUB.js → main-GY4PAVQW.js} +3 -3
  43. package/package.json +2 -2
  44. package/dist/ui/chunk-RLRSNHYG.js +0 -3
  45. package/dist/ui/chunk-SI4MGFOW.js +0 -2
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // cli/entry.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="75d0c2c2-d08f-5d7c-aa62-3e1d53dd4ef1")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="81f53ca6-a8bf-5fc1-909d-ce926a83f056")}catch(e){}}();
4
4
  import { existsSync as existsSync34 } from "fs";
5
5
  import { Builtins, Cli as Cli2 } from "clipanion";
6
6
 
@@ -250,7 +250,7 @@ function bucketByKind(kind, instance, bag) {
250
250
  // package.json
251
251
  var package_default = {
252
252
  name: "@skill-map/cli",
253
- version: "0.69.0",
253
+ version: "0.71.0",
254
254
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
255
255
  license: "MIT",
256
256
  type: "module",
@@ -581,7 +581,7 @@ var command_schema_default = {
581
581
  // plugins/ids.ts
582
582
  var CORE_PLUGIN_ID = "core";
583
583
  var CLAUDE_PLUGIN_ID = "claude";
584
- var OPENAI_PLUGIN_ID = "codex";
584
+ var CODEX_PLUGIN_ID = "codex";
585
585
  var ANTIGRAVITY_PLUGIN_ID = "antigravity";
586
586
  var AGENT_SKILLS_PLUGIN_ID = "agent-skills";
587
587
 
@@ -633,7 +633,10 @@ var claudeProvider = {
633
633
  presentation: {
634
634
  label: "Anthropic's Claude",
635
635
  color: "#cc785c",
636
- colorDark: "#e89270"
636
+ colorDark: "#e89270",
637
+ // Claude Code invokes both commands and skills with `/`; the palette
638
+ // paints this as the `invokes` edge glyph under the claude lens.
639
+ invocationSigil: "/"
637
640
  },
638
641
  // Auto-detect marker: a `.claude/` directory under the scope root marks
639
642
  // a Claude Code project. Provider-owned (replaces the old central
@@ -805,9 +808,6 @@ var claudeProvider = {
805
808
  }
806
809
  };
807
810
 
808
- // plugins/claude/extractors/at-directive/index.ts
809
- import { posix as pathPosix } from "path";
810
-
811
811
  // kernel/util/strip-code-blocks.ts
812
812
  var FENCE_RE = /^(?<indent> {0,3})(?<fence>`{3,}|~{3,})/;
813
813
  function stripCodeBlocks(input) {
@@ -815,6 +815,28 @@ function stripCodeBlocks(input) {
815
815
  const fenceless = stripFences(input);
816
816
  return stripInline(fenceless);
817
817
  }
818
+ function findBacktickImbalance(body) {
819
+ if (!body) return null;
820
+ const { stripped, openFenceLine } = scanFences(body);
821
+ if (openFenceLine > 0) {
822
+ return { kind: "fence", line: openFenceLine, sourceLine: sourceLineAt(body, openFenceLine) };
823
+ }
824
+ const survivor = stripInline(maskEscapes(stripped)).indexOf("`");
825
+ if (survivor < 0) return null;
826
+ const line = lineOfIndex(stripped, survivor);
827
+ return { kind: "inline", line, sourceLine: sourceLineAt(body, line) };
828
+ }
829
+ function maskEscapes(text) {
830
+ return text.replace(/\\[^\n]/g, " ");
831
+ }
832
+ function lineOfIndex(text, idx) {
833
+ let line = 1;
834
+ for (let i = 0; i < idx; i++) if (text[i] === "\n") line++;
835
+ return line;
836
+ }
837
+ function sourceLineAt(text, line) {
838
+ return text.split("\n")[line - 1]?.trim() ?? "";
839
+ }
818
840
  function extractCodeRegions(input) {
819
841
  if (!input) return input;
820
842
  const stripped = stripCodeBlocks(input);
@@ -834,30 +856,31 @@ function stripHtml(input) {
834
856
  function stripCodeAndHtml(input) {
835
857
  return stripHtml(stripCodeBlocks(input));
836
858
  }
837
- function stripFences(input) {
859
+ function scanFences(input) {
838
860
  const out = [];
839
861
  const lines = input.split("\n");
840
862
  let openFence = null;
841
- for (const line of lines) {
863
+ let openFenceLine = 0;
864
+ for (let i = 0; i < lines.length; i++) {
865
+ const line = lines[i];
842
866
  if (openFence) {
843
- const closer = matchClosingFence(line, openFence);
844
- if (closer) {
845
- out.push(blank(line));
846
- openFence = null;
847
- } else {
848
- out.push(blank(line));
849
- }
867
+ if (matchClosingFence(line, openFence)) openFence = null;
868
+ out.push(blank(line));
850
869
  continue;
851
870
  }
852
871
  const open3 = FENCE_RE.exec(line);
853
872
  if (open3?.groups) {
854
873
  openFence = open3.groups["fence"];
874
+ openFenceLine = i + 1;
855
875
  out.push(blank(line));
856
876
  continue;
857
877
  }
858
878
  out.push(line);
859
879
  }
860
- return out.join("\n");
880
+ return { stripped: out.join("\n"), openFenceLine: openFence ? openFenceLine : 0 };
881
+ }
882
+ function stripFences(input) {
883
+ return scanFences(input).stripped;
861
884
  }
862
885
  function matchClosingFence(line, openFence) {
863
886
  const m = FENCE_RE.exec(line);
@@ -903,84 +926,61 @@ function normalizeTrigger(source) {
903
926
  return out.trim();
904
927
  }
905
928
 
929
+ // kernel/util/at-token.ts
930
+ import { posix as pathPosix } from "path";
931
+ var AT_TOKEN_RE = /(?:^|[^A-Za-z0-9_@])(@(?:(?:\.{1,2}\/)+|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-z0-9_])?(?::[a-z0-9][a-z0-9_-]*)?)/gi;
932
+ var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
933
+ function classifyAtFileToken(bare) {
934
+ if (bare.startsWith("/")) return null;
935
+ if (bare.startsWith("./") || bare.startsWith("../")) return "relative path prefix";
936
+ if (FILE_EXT_RE.test(bare)) return "known file extension";
937
+ return null;
938
+ }
939
+ function resolveAtFileTarget(sourceDir, bare) {
940
+ const joined = sourceDir === "." ? bare : `${sourceDir}/${bare}`;
941
+ return pathPosix.normalize(joined);
942
+ }
943
+
906
944
  // plugins/claude/extractors/at-directive/index.ts
907
945
  var ID = "at-directive";
908
- var AT_RE = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-z0-9_])?(?::[a-z0-9][a-z0-9_-]*)?)/gi;
909
- var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
910
946
  var atDirectiveExtractor = {
911
947
  id: ID,
912
948
  pluginId: CLAUDE_PLUGIN_ID,
913
949
  kind: "extractor",
914
- description: "Detects `@<token>` directives in a node's body using Claude Code rules, choosing the link kind by token shape. Example: a bare handle `@team` becomes a `mentions` link, while a file-flavoured token `@docs/api.md` becomes a `references` link.",
950
+ description: "Detects bare `@<handle>` mentions in a node's body using Claude Code rules and emits a `mentions` link to the named entity. Example: `@team` becomes a `mentions` link. File-shaped tokens like `@docs/api.md` are handled by `core/at-file` instead.",
915
951
  scope: "body",
916
- // Authorised under the claude AND codex lenses: OpenAI Codex sub-agents
917
- // reference each other with the same `@<name>` mention grammar, and their
918
- // prompt (the TOML `developer_instructions` body, fed in via the codex
919
- // provider's `read.bodyField`) carries the same `@` tokens. The gate is the active
920
- // lens, not the node's provider, so under `codex` this parses `@` across
921
- // the project's markdown surface just as it does under `claude`.
922
- precondition: { provider: ["claude", "codex"] },
923
- // eslint-disable-next-line complexity
952
+ // Claude-only. This is Claude's bare `@<handle>` MENTION grammar. The
953
+ // file-reference half of `@` (file-shaped tokens) is the shared
954
+ // `core/at-file` extractor, which also serves codex / antigravity; this
955
+ // extractor defers file-shaped tokens to it.
956
+ precondition: { provider: ["claude"] },
924
957
  extract(ctx) {
925
958
  const seenMentions = /* @__PURE__ */ new Set();
926
- const seenReferences = /* @__PURE__ */ new Set();
927
959
  const body = stripCodeAndHtml(ctx.body);
928
960
  const lineStarts = computeLineStarts(body);
929
- const sourceDir = pathPosix.dirname(ctx.node.path);
930
- for (const match of body.matchAll(AT_RE)) {
961
+ for (const match of body.matchAll(AT_TOKEN_RE)) {
931
962
  const original = match[1];
932
963
  const bare = original.slice(1);
933
- const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
934
- const line = lineFor(lineStarts, captureOffset);
935
- const range = { start: captureOffset, end: captureOffset + original.length, line };
936
964
  if (bare.startsWith("/")) continue;
937
- const isReference = bare.startsWith("./") || bare.startsWith("../") || FILE_EXT_RE.test(bare);
938
- if (isReference) {
939
- const target = resolveSourceRelative(sourceDir, bare);
940
- const dedupKey = target.toLowerCase();
941
- if (seenReferences.has(dedupKey)) continue;
942
- seenReferences.add(dedupKey);
943
- ctx.emitSignal({
944
- source: ctx.node.path,
945
- scope: "body",
946
- range,
947
- raw: original,
948
- candidates: [
949
- {
950
- extractorId: ID,
951
- kind: "references",
952
- target,
953
- // 0.85: strong file signal (path prefix `./` / `../` OR
954
- // a known file extension on the tail). One degree of
955
- // inference (the runtime still resolves the path).
956
- confidence: 0.85,
957
- rationale: bare.startsWith("./") || bare.startsWith("../") ? "relative path prefix" : "known file extension",
958
- trigger: {
959
- originalTrigger: original,
960
- normalizedTrigger: target
961
- }
962
- }
963
- ]
964
- });
965
- continue;
966
- }
965
+ if (classifyAtFileToken(bare) !== null) continue;
967
966
  const normalized = normalizeTrigger(original);
968
967
  if (seenMentions.has(normalized)) continue;
969
968
  seenMentions.add(normalized);
969
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
970
+ const line = lineFor(lineStarts, captureOffset);
970
971
  ctx.emitSignal({
971
972
  source: ctx.node.path,
972
973
  scope: "body",
973
- range,
974
+ range: { start: captureOffset, end: captureOffset + original.length, line },
974
975
  raw: original,
975
976
  candidates: [
976
977
  {
977
978
  extractorId: ID,
978
979
  kind: "mentions",
979
980
  target: original,
980
- // 0.5: genuine ambiguity. A bare `@handle` (no extension, no
981
- // path prefix) could be an agent, a handle, or generic prose.
982
- // The runtime decides at invocation time; the extractor leaves
983
- // the question open.
981
+ // 0.5: genuine ambiguity. A bare `@handle` (no extension, no path
982
+ // prefix) could be an agent, a handle, or generic prose. The
983
+ // runtime decides at invocation time; the extractor leaves it open.
984
984
  confidence: 0.5,
985
985
  rationale: "no extension, no path prefix",
986
986
  trigger: {
@@ -993,73 +993,9 @@ var atDirectiveExtractor = {
993
993
  }
994
994
  }
995
995
  };
996
- function resolveSourceRelative(sourceDir, bare) {
997
- const joined = sourceDir === "." ? bare : `${sourceDir}/${bare}`;
998
- return pathPosix.normalize(joined);
999
- }
1000
-
1001
- // plugins/claude/extractors/slash-command/index.ts
1002
- var ID2 = "slash-command";
1003
- var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
1004
- var slashCommandExtractor = {
1005
- id: ID2,
1006
- pluginId: CLAUDE_PLUGIN_ID,
1007
- kind: "extractor",
1008
- description: "Turns `/command` invocations in a node's body into arrows that point at the resolved slash command or skill, using Claude Code routing rules. Example: `/deploy` in the body draws an arrow to the `deploy` command.",
1009
- scope: "body",
1010
- // Also authorised under the codex and antigravity lenses, which share the
1011
- // `/command` grammar. Under codex a sub-agent's prompt body (the TOML
1012
- // `developer_instructions` field) has its `/command` tokens parsed for
1013
- // pipeline parity; codex resolves them to its open-standard skills
1014
- // (`invokes: ['skill']`). Under antigravity a workflow / skill / AGENTS.md
1015
- // body's `/name` tokens resolve to BOTH skills and workflows
1016
- // (`invokes: ['skill', 'workflow']`), since Antigravity invokes either by
1017
- // the same slash. A lens that declares no `invokes` resolution leaves the
1018
- // signals unresolved (no spurious edges).
1019
- precondition: { provider: ["claude", "codex", "antigravity"] },
1020
- extract(ctx) {
1021
- const seen = /* @__PURE__ */ new Set();
1022
- const body = stripCodeAndHtml(ctx.body);
1023
- const lineStarts = computeLineStarts(body);
1024
- for (const match of body.matchAll(SLASH_RE)) {
1025
- const original = match[1];
1026
- const endIdx = (match.index ?? 0) + match[0].length;
1027
- const nextChar = body[endIdx];
1028
- if (nextChar && /[A-Za-z0-9_/-]/.test(nextChar)) continue;
1029
- const normalized = normalizeTrigger(original);
1030
- if (seen.has(normalized)) continue;
1031
- seen.add(normalized);
1032
- const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
1033
- const line = lineFor(lineStarts, captureOffset);
1034
- ctx.emitSignal({
1035
- source: ctx.node.path,
1036
- scope: "body",
1037
- range: { start: captureOffset, end: captureOffset + original.length, line },
1038
- raw: original,
1039
- candidates: [
1040
- {
1041
- extractorId: ID2,
1042
- kind: "invokes",
1043
- target: original,
1044
- // 0.8: clean `/command` match after code-block strip. The
1045
- // post-match path guard above filters URL / file-path noise,
1046
- // so a hit is unambiguous syntax. Resolution against the
1047
- // live skill / command catalog happens downstream.
1048
- confidence: 0.8,
1049
- rationale: "unambiguous slash syntax post code-block strip",
1050
- trigger: {
1051
- originalTrigger: original,
1052
- normalizedTrigger: normalized
1053
- }
1054
- }
1055
- ]
1056
- });
1057
- }
1058
- }
1059
- };
1060
996
 
1061
997
  // plugins/claude/extractors/tools-counter/index.ts
1062
- var ID3 = "tools-counter";
998
+ var ID2 = "tools-counter";
1063
999
  var count = {
1064
1000
  slot: "card.footer.left",
1065
1001
  icon: "pi-wrench",
@@ -1069,7 +1005,7 @@ var count = {
1069
1005
  };
1070
1006
  var TOOLTIP_MAX = 255;
1071
1007
  var toolsCounterExtractor = {
1072
- id: ID3,
1008
+ id: ID2,
1073
1009
  pluginId: CLAUDE_PLUGIN_ID,
1074
1010
  kind: "extractor",
1075
1011
  description: "Counts the tools an agent declares in its frontmatter and shows the count on the agent card. Example: an agent with `tools: [Bash, Read, Grep]` shows a count of 3.",
@@ -1241,26 +1177,32 @@ var agentSkillsProvider = {
1241
1177
  stability: "stable",
1242
1178
  // Auto-detect marker: a `.agents/` directory marks an open-standard
1243
1179
  // project. This is also the marker a Google/Antigravity project carries
1244
- // (Antigravity adopted the open standard). The marker only produces an
1245
- // auto-detect candidate once this experimental provider is enabled.
1246
- // Provider-owned.
1247
- detect: { markers: [".agents"] },
1180
+ // (Antigravity adopted the open standard) and the shared skill home a
1181
+ // Codex project populates under `.agents/skills/`. `fallback: true` makes
1182
+ // this candidate yield to any vendor marker present alongside `.agents/`:
1183
+ // a `.codex/` + `.agents/` project resolves `codex` outright, never an
1184
+ // ambiguous `codex` vs `agent-skills` prompt. The `.agents/` marker only
1185
+ // wins when no vendor marker is present. Provider-owned.
1186
+ detect: { markers: [".agents"], fallback: true },
1248
1187
  // Authoring target for `sm tutorial`: the open standard discovers skills
1249
1188
  // under `.agents/skills/<name>/SKILL.md`. `aka` lists Antigravity, which
1250
1189
  // shares this territory AND the BASIC tutorial track (skill + markdown,
1251
1190
  // references), so a tester on Antigravity scaffolds here. OpenAI Codex
1252
1191
  // also reads `.agents/skills/`, but Codex is a RICH-track lens (it has the
1253
- // `agent` kind, slash and `@`), so advertising it under this basic row
1192
+ // `agent` kind, plus `$`-skill invocation and `@`-file references), so
1193
+ // advertising it under this basic row
1254
1194
  // would hand it the wrong book; Codex is surfaced once a Codex rich
1255
1195
  // scaffold target lands. `aka` is display-only, `--for` matches the id.
1256
1196
  scaffold: { skillDir: ".agents/skills", aka: ["Google's Antigravity"] },
1257
1197
  read: COMMONS_READ,
1258
1198
  kinds: COMMONS_KINDS,
1259
1199
  resolution: COMMONS_RESOLUTION,
1260
- // Base reserved-name catalog (self-scope under the `agent-skills` lens). The
1261
- // shared export is inherited by every Provider that adopts the open
1262
- // standard (see `COMMONS_RESERVED_NAMES` above).
1263
- reservedNames: COMMONS_RESERVED_NAMES,
1200
+ // NO `reservedNames`: the neutral open standard has no `/`-invocation, a
1201
+ // skill activates by its `description` and connects via markdown links, so
1202
+ // a skill name cannot shadow a built-in `/` command. The shared
1203
+ // `COMMONS_RESERVED_NAMES` export above is for `/`-invoking vendors
1204
+ // (Antigravity) to spread, NOT applied here. See spec/architecture.md
1205
+ // §Provider · reservedNames.
1264
1206
  classify: classifyCommonsPath
1265
1207
  };
1266
1208
 
@@ -1308,7 +1250,10 @@ var antigravityProvider = {
1308
1250
  presentation: {
1309
1251
  label: "Google's Antigravity",
1310
1252
  color: "#7c3aed",
1311
- colorDark: "#a78bfa"
1253
+ colorDark: "#a78bfa",
1254
+ // Antigravity bolts `/`-invocation onto the open standard (skills and
1255
+ // workflows are `/<name>`), so the `invokes` edge glyph is the slash.
1256
+ invocationSigil: "/"
1312
1257
  },
1313
1258
  // Auto-detect marker: Antigravity's workflows live under `.agent/workflows/`
1314
1259
  // (SINGULAR `.agent`), its one vendor-specific on-disk territory. Skills
@@ -1346,10 +1291,15 @@ var antigravityProvider = {
1346
1291
  schemaJson: workflow_schema_default,
1347
1292
  ui: {
1348
1293
  label: "Workflows",
1349
- // Antigravity violet, so a workflow node reads as Antigravity's own
1350
- // (skills keep the normalised cross-provider green of COMMONS_KINDS).
1351
- color: "#7c3aed",
1352
- colorDark: "#a78bfa",
1294
+ // A workflow is Antigravity's command-equivalent (its own slash-
1295
+ // invocable procedural kind, the slot Claude fills with `command`),
1296
+ // so it adopts the SAME color as Claude's `command` kind (amber) for a
1297
+ // uniform cross-provider vocabulary, the same way skills normalise to
1298
+ // the cross-provider green of COMMONS_KINDS. The Antigravity violet
1299
+ // stays on the provider-level `presentation` (the lens identity), not
1300
+ // on this kind.
1301
+ color: "#f59e0b",
1302
+ colorDark: "#fbbf24",
1353
1303
  icon: { kind: "pi", id: "pi-sitemap" }
1354
1304
  },
1355
1305
  // The handle is ALWAYS the filename stem (`/<name>`): Antigravity
@@ -1361,7 +1311,12 @@ var antigravityProvider = {
1361
1311
  // `/<name>` slash invocations resolve to BOTH skills and workflows: under
1362
1312
  // the antigravity lens a `/deploy` links to either `.agents/skills/deploy`
1363
1313
  // or `.agent/workflows/deploy.md`. Overrides the open-standard default
1364
- // (`invokes: ['skill']`) to add the own `workflow` kind.
1314
+ // (`invokes: ['skill']`) to add the own `workflow` kind. Antigravity's OTHER
1315
+ // connector, `@filename` file references (the documented rules / skill /
1316
+ // workflow file pointer, a file-picker grammar like Codex's, distinct from
1317
+ // Claude's `@`-agent-mention), needs no entry here: the shared `core/at-file`
1318
+ // extractor (gated to claude / codex / antigravity) emits them as
1319
+ // `references` resolved by PATH, lens-independent.
1365
1320
  resolution: { invokes: ["skill", "workflow"] },
1366
1321
  classify(path) {
1367
1322
  if (/^\.agent\/workflows\/[^/]+\.md$/.test(path.toLowerCase())) return "workflow";
@@ -1452,7 +1407,7 @@ var agent_schema_default2 = {
1452
1407
  // plugins/codex/providers/codex/index.ts
1453
1408
  var codexProvider = {
1454
1409
  id: "codex",
1455
- pluginId: OPENAI_PLUGIN_ID,
1410
+ pluginId: CODEX_PLUGIN_ID,
1456
1411
  kind: "provider",
1457
1412
  description: "Classifies `.codex/agents/*.toml` as OpenAI Codex CLI sub-agents and `.agents/skills/*/SKILL.md` as Codex skills (open standard).",
1458
1413
  // Provider identity for the active-lens dropdown, the topbar lens chip,
@@ -1461,7 +1416,11 @@ var codexProvider = {
1461
1416
  presentation: {
1462
1417
  label: "OpenAI's Codex",
1463
1418
  color: "#22c55e",
1464
- colorDark: "#4ade80"
1419
+ colorDark: "#4ade80",
1420
+ // Codex invokes skills with `$` (`$publish`); `/` is reserved for its
1421
+ // own built-in commands (`/model`, `/init`), so the `invokes` edge glyph
1422
+ // under the codex lens is the dollar, not the slash.
1423
+ invocationSigil: "$"
1465
1424
  },
1466
1425
  // Auto-detect marker: a `.codex/` directory marks a Codex CLI project.
1467
1426
  // `AGENTS.md` is intentionally NOT a marker: it is the open agents.md
@@ -1527,19 +1486,21 @@ var codexProvider = {
1527
1486
  // `frontmatter.name`.
1528
1487
  ...COMMONS_KINDS
1529
1488
  },
1530
- // Mentions resolve to agents (`@<name>`, the Codex sub-agent handle).
1531
- // Slash invocations resolve to skills (`invokes: ['skill']`, inherited
1532
- // from the open standard), so a `/skill-name` in an agent's prompt links
1533
- // to its `.agents/skills/` skill.
1489
+ // Skill invocations resolve to skills (`invokes: ['skill']`, inherited
1490
+ // from the open standard): a `$skill-name` in an agent's prompt (parsed by
1491
+ // the codex `dollar-skill` extractor) links to its `.agents/skills/` skill.
1492
+ // NO `mentions` entry: Codex's `@` is a file picker, parsed by the
1493
+ // codex-owned `at-file` extractor as a path-resolved `references` link, not
1494
+ // an agent-mention grammar.
1534
1495
  resolution: {
1535
- mentions: ["agent"],
1536
1496
  ...COMMONS_RESOLUTION
1537
1497
  },
1538
- // Open-standard reserved-name base (the universal cross-agent slash
1539
- // verbs an agent CLI ships built-in), inherited from `agent-skills` and
1540
- // applied under the codex lens via SELF scope: a user skill that shadows
1541
- // one is flagged by `core/name-reserved`.
1542
- reservedNames: COMMONS_RESERVED_NAMES,
1498
+ // NO `reservedNames`: Codex invokes skills via `$` (the `dollar-skill`
1499
+ // extractor), a namespace disjoint from its built-in `/` commands, so a
1500
+ // `$`-skill named `model` does NOT collide with `/model`. Reserved-skill
1501
+ // names only apply to lenses that invoke skills through the `/` command
1502
+ // channel (claude, antigravity). See spec/architecture.md §Provider ·
1503
+ // reservedNames.
1543
1504
  classify(path) {
1544
1505
  const lower = path.toLowerCase();
1545
1506
  if (lower.startsWith(".codex/agents/") && lower.endsWith(".toml")) return "agent";
@@ -1547,6 +1508,57 @@ var codexProvider = {
1547
1508
  }
1548
1509
  };
1549
1510
 
1511
+ // plugins/codex/extractors/dollar-skill/index.ts
1512
+ var ID3 = "dollar-skill";
1513
+ var DOLLAR_RE = /(?<![A-Za-z0-9_$])(\$[a-z][a-z0-9_-]*)/g;
1514
+ var dollarSkillExtractor = {
1515
+ id: ID3,
1516
+ pluginId: CODEX_PLUGIN_ID,
1517
+ kind: "extractor",
1518
+ description: "Turns `$skill` invocations in a node's body into arrows that point at the resolved Codex skill, using OpenAI Codex routing rules. Example: `$check-links` in the body draws an arrow to the `check-links` skill.",
1519
+ scope: "body",
1520
+ // Codex-only: `$skill` is OpenAI Codex's explicit skill-invocation
1521
+ // grammar. The codex provider resolves it to its open-standard skills
1522
+ // (`invokes: ['skill']`). Other lenses do not parse `$`.
1523
+ precondition: { provider: ["codex"] },
1524
+ extract(ctx) {
1525
+ const seen = /* @__PURE__ */ new Set();
1526
+ const body = stripCodeAndHtml(ctx.body);
1527
+ const lineStarts = computeLineStarts(body);
1528
+ for (const match of body.matchAll(DOLLAR_RE)) {
1529
+ const original = match[1];
1530
+ const normalized = normalizeTrigger(original);
1531
+ if (seen.has(normalized)) continue;
1532
+ seen.add(normalized);
1533
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
1534
+ const line = lineFor(lineStarts, captureOffset);
1535
+ ctx.emitSignal({
1536
+ source: ctx.node.path,
1537
+ scope: "body",
1538
+ range: { start: captureOffset, end: captureOffset + original.length, line },
1539
+ raw: original,
1540
+ candidates: [
1541
+ {
1542
+ extractorId: ID3,
1543
+ kind: "invokes",
1544
+ target: original,
1545
+ // 0.8: clean `$skill` match after code-block strip. The
1546
+ // lowercase-letter guard filters currency / env-var noise, so a
1547
+ // hit is unambiguous syntax. Resolution against the live skill
1548
+ // catalog happens downstream.
1549
+ confidence: 0.8,
1550
+ rationale: "unambiguous $skill syntax post code-block strip",
1551
+ trigger: {
1552
+ originalTrigger: original,
1553
+ normalizedTrigger: normalized
1554
+ }
1555
+ }
1556
+ ]
1557
+ });
1558
+ }
1559
+ }
1560
+ };
1561
+
1550
1562
  // plugins/core/providers/core-markdown/schemas/markdown.schema.json
1551
1563
  var markdown_schema_default = {
1552
1564
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -1633,12 +1645,68 @@ var coreMarkdownProvider = {
1633
1645
  }
1634
1646
  };
1635
1647
 
1636
- // plugins/core/extractors/backtick-path/index.ts
1648
+ // plugins/core/extractors/at-file/index.ts
1637
1649
  import { posix as pathPosix2 } from "path";
1638
- var ID4 = "backtick-path";
1650
+ var ID4 = "at-file";
1651
+ var atFileExtractor = {
1652
+ id: ID4,
1653
+ pluginId: CORE_PLUGIN_ID,
1654
+ kind: "extractor",
1655
+ description: "Detects `@<file>` references in a node's body for the `@`-file-picker lenses (Codex, Antigravity, Claude), where `@` points at a workspace file. A path- or extension-shaped token becomes a `references` link to that file; a bare `@handle` forms no edge. Example: `@builder.toml` in a body draws an arrow to the `builder` file.",
1656
+ scope: "body",
1657
+ // The lenses whose runtime reads `@` as a file-path picker. Codex (`@` is a
1658
+ // file picker, `$` invokes skills), Antigravity (documented `@filename` file
1659
+ // refs), and Claude (`@file.md` file ref; its bare-handle MENTION half lives
1660
+ // in `claude/at-directive`). NOT gated under `agent-skills` / `markdown`,
1661
+ // whose runtimes read `@` as nothing (no phantom edges).
1662
+ precondition: { provider: ["claude", "codex", "antigravity"] },
1663
+ extract(ctx) {
1664
+ const seenReferences = /* @__PURE__ */ new Set();
1665
+ const body = stripCodeAndHtml(ctx.body);
1666
+ const lineStarts = computeLineStarts(body);
1667
+ const sourceDir = pathPosix2.dirname(ctx.node.path);
1668
+ for (const match of body.matchAll(AT_TOKEN_RE)) {
1669
+ const original = match[1];
1670
+ const bare = original.slice(1);
1671
+ const rationale = classifyAtFileToken(bare);
1672
+ if (rationale === null) continue;
1673
+ const target = resolveAtFileTarget(sourceDir, bare);
1674
+ const dedupKey = target.toLowerCase();
1675
+ if (seenReferences.has(dedupKey)) continue;
1676
+ seenReferences.add(dedupKey);
1677
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
1678
+ const line = lineFor(lineStarts, captureOffset);
1679
+ ctx.emitSignal({
1680
+ source: ctx.node.path,
1681
+ scope: "body",
1682
+ range: { start: captureOffset, end: captureOffset + original.length, line },
1683
+ raw: original,
1684
+ candidates: [
1685
+ {
1686
+ extractorId: ID4,
1687
+ kind: "references",
1688
+ target,
1689
+ // 0.85: strong file signal (path prefix or known extension),
1690
+ // one degree of inference (the runtime resolves the path).
1691
+ confidence: 0.85,
1692
+ rationale,
1693
+ trigger: {
1694
+ originalTrigger: original,
1695
+ normalizedTrigger: target
1696
+ }
1697
+ }
1698
+ ]
1699
+ });
1700
+ }
1701
+ }
1702
+ };
1703
+
1704
+ // plugins/core/extractors/backtick-path/index.ts
1705
+ import { posix as pathPosix3 } from "path";
1706
+ var ID5 = "backtick-path";
1639
1707
  var PATH_RE = /(?<![\w/:.-])(?:\.{1,2}\/)?[\w][\w.-]*(?:\/[\w.-]+)*\.md\b(?![\w/])/g;
1640
1708
  var backtickPathExtractor = {
1641
- id: ID4,
1709
+ id: ID5,
1642
1710
  pluginId: CORE_PLUGIN_ID,
1643
1711
  kind: "extractor",
1644
1712
  description: "Turns relative .md paths written inside code spans and fenced blocks into arrows between nodes in the graph. Example: a backticked `references/rules.md` path draws an arrow to that file.",
@@ -1647,7 +1715,7 @@ var backtickPathExtractor = {
1647
1715
  const seen = /* @__PURE__ */ new Set();
1648
1716
  const body = extractCodeRegions(ctx.body);
1649
1717
  const lineStarts = computeLineStarts(body);
1650
- const sourceDir = pathPosix2.dirname(ctx.node.path);
1718
+ const sourceDir = pathPosix3.dirname(ctx.node.path);
1651
1719
  for (const match of body.matchAll(PATH_RE)) {
1652
1720
  const original = match[0];
1653
1721
  const resolved = resolveTarget(sourceDir, original);
@@ -1663,7 +1731,7 @@ var backtickPathExtractor = {
1663
1731
  raw: original,
1664
1732
  candidates: [
1665
1733
  {
1666
- extractorId: ID4,
1734
+ extractorId: ID5,
1667
1735
  kind: "points",
1668
1736
  target: resolved,
1669
1737
  // 0.85: a strong file signal with one degree of inference,
@@ -1688,11 +1756,11 @@ function resolveTarget(sourceDir, raw) {
1688
1756
  if (trimmed.length === 0) return null;
1689
1757
  if (trimmed.startsWith("/")) return null;
1690
1758
  const joined = sourceDir === "." ? trimmed : `${sourceDir}/${trimmed}`;
1691
- return pathPosix2.normalize(joined);
1759
+ return pathPosix3.normalize(joined);
1692
1760
  }
1693
1761
 
1694
1762
  // plugins/core/extractors/external-url-counter/index.ts
1695
- var ID5 = "external-url-counter";
1763
+ var ID6 = "external-url-counter";
1696
1764
  var count2 = {
1697
1765
  slot: "card.footer.left",
1698
1766
  icon: "pi-link",
@@ -1712,7 +1780,7 @@ var settings = {
1712
1780
  var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
1713
1781
  var TRAILING_PUNCT = /[.,;:!?]+$/;
1714
1782
  var externalUrlCounterExtractor = {
1715
- id: ID5,
1783
+ id: ID6,
1716
1784
  pluginId: CORE_PLUGIN_ID,
1717
1785
  kind: "extractor",
1718
1786
  description: "Counts the distinct external URLs in a node's body and shows the count on the card. Example: a body linking `https://example.com` and `https://docs.rs` shows a count of 2.",
@@ -1764,7 +1832,7 @@ var externalUrlCounterExtractor = {
1764
1832
  raw: original,
1765
1833
  candidates: [
1766
1834
  {
1767
- extractorId: ID5,
1835
+ extractorId: ID6,
1768
1836
  kind: "references",
1769
1837
  target: normalized.href,
1770
1838
  confidence: 0.3,
@@ -1805,12 +1873,12 @@ function normalizeUrl(raw) {
1805
1873
  }
1806
1874
 
1807
1875
  // plugins/core/extractors/markdown-link/index.ts
1808
- import { posix as pathPosix3 } from "path";
1809
- var ID6 = "markdown-link";
1876
+ import { posix as pathPosix4 } from "path";
1877
+ var ID7 = "markdown-link";
1810
1878
  var LINK_RE = /(?<!!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
1811
1879
  var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
1812
1880
  var markdownLinkExtractor = {
1813
- id: ID6,
1881
+ id: ID7,
1814
1882
  pluginId: CORE_PLUGIN_ID,
1815
1883
  kind: "extractor",
1816
1884
  description: "Turns markdown links (`[text](path)`) in a node's body into arrows between nodes in the graph. Example: `[the guide](docs/guide.md)` draws an arrow to `docs/guide.md`.",
@@ -1819,7 +1887,7 @@ var markdownLinkExtractor = {
1819
1887
  const seen = /* @__PURE__ */ new Set();
1820
1888
  const body = stripCodeAndHtml(ctx.body);
1821
1889
  const lineStarts = computeLineStarts(body);
1822
- const sourceDir = pathPosix3.dirname(ctx.node.path);
1890
+ const sourceDir = pathPosix4.dirname(ctx.node.path);
1823
1891
  for (const match of body.matchAll(LINK_RE)) {
1824
1892
  const original = match[2];
1825
1893
  const resolved = resolveTarget2(sourceDir, original);
@@ -1835,7 +1903,7 @@ var markdownLinkExtractor = {
1835
1903
  raw: match[0],
1836
1904
  candidates: [
1837
1905
  {
1838
- extractorId: ID6,
1906
+ extractorId: ID7,
1839
1907
  kind: "references",
1840
1908
  target: resolved,
1841
1909
  // 0.95: the `[text](path)` syntax is unambiguous (the spec's
@@ -1866,14 +1934,14 @@ function resolveTarget2(sourceDir, raw) {
1866
1934
  if (URL_SCHEME_RE.test(trimmed)) return null;
1867
1935
  if (trimmed.startsWith("/")) return null;
1868
1936
  const joined = sourceDir === "." ? trimmed : `${sourceDir}/${trimmed}`;
1869
- return pathPosix3.normalize(joined);
1937
+ return pathPosix4.normalize(joined);
1870
1938
  }
1871
1939
 
1872
1940
  // plugins/core/extractors/mcp-tools/index.ts
1873
- var ID7 = "mcp-tools";
1941
+ var ID8 = "mcp-tools";
1874
1942
  var MCP_PATTERN = /^mcp__([a-z0-9][a-z0-9_-]*)__[a-z0-9_-]+$/i;
1875
1943
  var mcpToolsExtractor = {
1876
- id: ID7,
1944
+ id: ID8,
1877
1945
  pluginId: CORE_PLUGIN_ID,
1878
1946
  kind: "extractor",
1879
1947
  description: "Turns `tools: [mcp__<server>__<tool>]` entries in a node's frontmatter into an MCP node per unique server and an arrow from the source to each one. Example: `tools: [mcp__github__create_pr]` adds an `mcp://github` node and an arrow to it.",
@@ -1904,7 +1972,7 @@ var mcpToolsExtractor = {
1904
1972
  raw: `mcp__${server}__*`,
1905
1973
  candidates: [
1906
1974
  {
1907
- extractorId: ID7,
1975
+ extractorId: ID8,
1908
1976
  kind: "references",
1909
1977
  target: mcpPath,
1910
1978
  confidence: 0.85,
@@ -1934,6 +2002,65 @@ function collectMcpServers(tools) {
1934
2002
  return out;
1935
2003
  }
1936
2004
 
2005
+ // plugins/core/extractors/slash-command/index.ts
2006
+ var ID9 = "slash-command";
2007
+ var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
2008
+ var slashCommandExtractor = {
2009
+ id: ID9,
2010
+ pluginId: CORE_PLUGIN_ID,
2011
+ kind: "extractor",
2012
+ description: "Turns `/command` invocations in a node's body into arrows that point at the resolved slash command, skill, or workflow, using the `/`-grammar shared by Claude and Antigravity. Example: `/deploy` in the body draws an arrow to the `deploy` command.",
2013
+ scope: "body",
2014
+ // Also authorised under the antigravity lens, which shares the `/command`
2015
+ // grammar: a workflow / skill / AGENTS.md body's `/name` tokens resolve to
2016
+ // BOTH skills and workflows (`invokes: ['skill', 'workflow']`), since
2017
+ // Antigravity invokes either by the same slash. NOT gated under codex:
2018
+ // OpenAI Codex reserves `/` for its OWN built-in commands (`/model`,
2019
+ // `/init`, ...) and invokes user skills with `$` instead (parsed by the
2020
+ // codex `dollar-skill` extractor). A lens that declares no `invokes`
2021
+ // resolution leaves the signals unresolved (no spurious edges).
2022
+ precondition: { provider: ["claude", "antigravity"] },
2023
+ extract(ctx) {
2024
+ const seen = /* @__PURE__ */ new Set();
2025
+ const body = stripCodeAndHtml(ctx.body);
2026
+ const lineStarts = computeLineStarts(body);
2027
+ for (const match of body.matchAll(SLASH_RE)) {
2028
+ const original = match[1];
2029
+ const endIdx = (match.index ?? 0) + match[0].length;
2030
+ const nextChar = body[endIdx];
2031
+ if (nextChar && /[A-Za-z0-9_/-]/.test(nextChar)) continue;
2032
+ const normalized = normalizeTrigger(original);
2033
+ if (seen.has(normalized)) continue;
2034
+ seen.add(normalized);
2035
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
2036
+ const line = lineFor(lineStarts, captureOffset);
2037
+ ctx.emitSignal({
2038
+ source: ctx.node.path,
2039
+ scope: "body",
2040
+ range: { start: captureOffset, end: captureOffset + original.length, line },
2041
+ raw: original,
2042
+ candidates: [
2043
+ {
2044
+ extractorId: ID9,
2045
+ kind: "invokes",
2046
+ target: original,
2047
+ // 0.8: clean `/command` match after code-block strip. The
2048
+ // post-match path guard above filters URL / file-path noise,
2049
+ // so a hit is unambiguous syntax. Resolution against the
2050
+ // live skill / command catalog happens downstream.
2051
+ confidence: 0.8,
2052
+ rationale: "unambiguous slash syntax post code-block strip",
2053
+ trigger: {
2054
+ originalTrigger: original,
2055
+ normalizedTrigger: normalized
2056
+ }
2057
+ }
2058
+ ]
2059
+ });
2060
+ }
2061
+ }
2062
+ };
2063
+
1937
2064
  // plugins/core/analyzers/annotation-field-unknown/index.ts
1938
2065
  import { readFileSync } from "fs";
1939
2066
  import { dirname, resolve } from "path";
@@ -1973,10 +2100,10 @@ var ANNOTATION_FIELD_UNKNOWN_TEXTS = {
1973
2100
  };
1974
2101
 
1975
2102
  // plugins/core/analyzers/annotation-field-unknown/index.ts
1976
- var ID8 = "annotation-field-unknown";
2103
+ var ID10 = "annotation-field-unknown";
1977
2104
  var RESERVED_ROOT_BLOCKS = /* @__PURE__ */ new Set(["identity", "annotations", "settings", "audit"]);
1978
2105
  var annotationFieldUnknownAnalyzer = {
1979
- id: ID8,
2106
+ id: ID10,
1980
2107
  pluginId: CORE_PLUGIN_ID,
1981
2108
  kind: "analyzer",
1982
2109
  description: "Flags typos or unrecognized keys in sidecars (`.sm`).",
@@ -2011,7 +2138,7 @@ var annotationFieldUnknownAnalyzer = {
2011
2138
  for (const key of Object.keys(annotations)) {
2012
2139
  if (!knownAnnotationKeys.has(key)) {
2013
2140
  issues.push({
2014
- analyzerId: ID8,
2141
+ analyzerId: ID10,
2015
2142
  severity: "warn",
2016
2143
  nodeIds: [node.path],
2017
2144
  message: formatFinding({
@@ -2037,7 +2164,7 @@ var annotationFieldUnknownAnalyzer = {
2037
2164
  if (validator(value)) continue;
2038
2165
  const errors = (validator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
2039
2166
  issues.push({
2040
- analyzerId: ID8,
2167
+ analyzerId: ID10,
2041
2168
  severity: "warn",
2042
2169
  nodeIds: [node.path],
2043
2170
  message: formatFinding({
@@ -2052,7 +2179,7 @@ var annotationFieldUnknownAnalyzer = {
2052
2179
  continue;
2053
2180
  }
2054
2181
  issues.push({
2055
- analyzerId: ID8,
2182
+ analyzerId: ID10,
2056
2183
  severity: "warn",
2057
2184
  nodeIds: [node.path],
2058
2185
  message: formatFinding({
@@ -2124,9 +2251,9 @@ var ANNOTATION_ORPHAN_TEXTS = {
2124
2251
  };
2125
2252
 
2126
2253
  // plugins/core/analyzers/annotation-orphan/index.ts
2127
- var ID9 = "annotation-orphan";
2254
+ var ID11 = "annotation-orphan";
2128
2255
  var annotationOrphanAnalyzer = {
2129
- id: ID9,
2256
+ id: ID11,
2130
2257
  pluginId: CORE_PLUGIN_ID,
2131
2258
  kind: "analyzer",
2132
2259
  description: "Flags sidecars (`.sm`) whose `.md` file no longer exists.",
@@ -2138,7 +2265,7 @@ var annotationOrphanAnalyzer = {
2138
2265
  for (const orphan of orphans) {
2139
2266
  const expectedMdRelative = orphan.relativePath.endsWith(".sm") ? `${orphan.relativePath.slice(0, -".sm".length)}.md` : `${orphan.relativePath}.md`;
2140
2267
  issues.push({
2141
- analyzerId: ID9,
2268
+ analyzerId: ID11,
2142
2269
  severity: "warn",
2143
2270
  nodeIds: [expectedMdRelative],
2144
2271
  message: formatFinding({
@@ -2181,7 +2308,7 @@ var ANNOTATION_STALE_TEXTS = {
2181
2308
  };
2182
2309
 
2183
2310
  // plugins/core/analyzers/annotation-stale/index.ts
2184
- var ID10 = "annotation-stale";
2311
+ var ID12 = "annotation-stale";
2185
2312
  var staleIcon = {
2186
2313
  slot: "card.footer.right",
2187
2314
  icon: "pi-clock",
@@ -2194,7 +2321,7 @@ var staleBadge = {
2194
2321
  priority: 20
2195
2322
  };
2196
2323
  var annotationStaleAnalyzer = {
2197
- id: ID10,
2324
+ id: ID12,
2198
2325
  pluginId: CORE_PLUGIN_ID,
2199
2326
  kind: "analyzer",
2200
2327
  description: "Marks sidecars (`.sm`) that are out of date with their `.md`.",
@@ -2214,7 +2341,7 @@ var annotationStaleAnalyzer = {
2214
2341
  const status = staleStatus(node.sidecar);
2215
2342
  if (status === null) continue;
2216
2343
  issues.push({
2217
- analyzerId: ID10,
2344
+ analyzerId: ID12,
2218
2345
  severity: "info",
2219
2346
  nodeIds: [node.path],
2220
2347
  message: formatFinding({ body: messageFor(status) }),
@@ -2260,9 +2387,9 @@ function tooltipFor(status) {
2260
2387
  }
2261
2388
 
2262
2389
  // plugins/core/analyzers/contribution-orphan/index.ts
2263
- var ID11 = "contribution-orphan";
2390
+ var ID13 = "contribution-orphan";
2264
2391
  var contributionOrphanAnalyzer = {
2265
- id: ID11,
2392
+ id: ID13,
2266
2393
  pluginId: CORE_PLUGIN_ID,
2267
2394
  kind: "analyzer",
2268
2395
  description: "Warns about plugin data referencing nodes renamed or deleted in the latest scan.",
@@ -2294,12 +2421,12 @@ var EXTRACTOR_COLLISION_TEXTS = {
2294
2421
  };
2295
2422
 
2296
2423
  // plugins/core/analyzers/extractor-collision/index.ts
2297
- var ID12 = "extractor-collision";
2424
+ var ID14 = "extractor-collision";
2298
2425
  function signalLines(signal) {
2299
2426
  return signal.range && typeof signal.range.line === "number" ? [signal.range.line] : void 0;
2300
2427
  }
2301
2428
  var extractorCollisionAnalyzer = {
2302
- id: ID12,
2429
+ id: ID14,
2303
2430
  pluginId: CORE_PLUGIN_ID,
2304
2431
  kind: "analyzer",
2305
2432
  description: "Reports when two extractors detect something at the same span of body text and the resolver drops one.",
@@ -2323,7 +2450,7 @@ function makeIssue(signal) {
2323
2450
  const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
2324
2451
  const winnerRange = `${winner.range.start}-${winner.range.end}`;
2325
2452
  return {
2326
- analyzerId: ID12,
2453
+ analyzerId: ID14,
2327
2454
  severity: "warn",
2328
2455
  nodeIds: [signal.source],
2329
2456
  message: formatFinding({
@@ -2367,7 +2494,7 @@ var ISSUE_COUNTER_TEXTS = {
2367
2494
  };
2368
2495
 
2369
2496
  // plugins/core/analyzers/issue-counter/index.ts
2370
- var ID13 = "issue-counter";
2497
+ var ID15 = "issue-counter";
2371
2498
  var warnCount = {
2372
2499
  slot: "card.footer.right",
2373
2500
  icon: "pi-exclamation-triangle",
@@ -2403,7 +2530,7 @@ function emitTierChips(ctx, ref, severity, counts, singleTooltip, manyTooltip) {
2403
2530
  }
2404
2531
  }
2405
2532
  var issueCounterAnalyzer = {
2406
- id: ID13,
2533
+ id: ID15,
2407
2534
  pluginId: CORE_PLUGIN_ID,
2408
2535
  kind: "analyzer",
2409
2536
  description: "Emits one aggregate severity chip per node (error + warn counts) from the live issue accumulator.",
@@ -2466,7 +2593,7 @@ var LINK_COUNTER_TEXTS = {
2466
2593
  };
2467
2594
 
2468
2595
  // plugins/core/analyzers/link-counter/index.ts
2469
- var ID14 = "link-counter";
2596
+ var ID16 = "link-counter";
2470
2597
  var linksIn = {
2471
2598
  slot: "card.footer.left",
2472
2599
  icon: "pi-download",
@@ -2482,7 +2609,7 @@ var linksOut = {
2482
2609
  priority: 20
2483
2610
  };
2484
2611
  var linkCounterAnalyzer = {
2485
- id: ID14,
2612
+ id: ID16,
2486
2613
  pluginId: CORE_PLUGIN_ID,
2487
2614
  kind: "analyzer",
2488
2615
  description: "Counts incoming and outgoing links per node.",
@@ -2547,10 +2674,10 @@ var LINK_KIND_CONFLICT_TEXTS = {
2547
2674
  };
2548
2675
 
2549
2676
  // plugins/core/analyzers/link-kind-conflict/index.ts
2550
- var ID15 = "link-kind-conflict";
2677
+ var ID17 = "link-kind-conflict";
2551
2678
  var NON_CONFLICTING_KINDS = /* @__PURE__ */ new Set(["points"]);
2552
2679
  var linkKindConflictAnalyzer = {
2553
- id: ID15,
2680
+ id: ID17,
2554
2681
  pluginId: CORE_PLUGIN_ID,
2555
2682
  kind: "analyzer",
2556
2683
  description: "Flags conflicting arrow meanings between extractors (e.g. `references` vs `invokes`).",
@@ -2598,7 +2725,7 @@ var linkKindConflictAnalyzer = {
2598
2725
  const [source, target] = key.split("\0");
2599
2726
  const kindList = variants.map((v) => v.kind).join(" / ");
2600
2727
  issues.push({
2601
- analyzerId: ID15,
2728
+ analyzerId: ID17,
2602
2729
  severity: "warn",
2603
2730
  nodeIds: [source, target],
2604
2731
  message: formatFinding({
@@ -2637,9 +2764,9 @@ var LINK_SELF_LOOP_TEXTS = {
2637
2764
  };
2638
2765
 
2639
2766
  // plugins/core/analyzers/link-self-loop/index.ts
2640
- var ID16 = "link-self-loop";
2767
+ var ID18 = "link-self-loop";
2641
2768
  var linkSelfLoopAnalyzer = {
2642
- id: ID16,
2769
+ id: ID18,
2643
2770
  pluginId: CORE_PLUGIN_ID,
2644
2771
  kind: "analyzer",
2645
2772
  description: "Flags links whose source is also their own resolved target (e.g. a body heading like `# /deploy` inside the file that defines `/deploy`).",
@@ -2649,7 +2776,7 @@ var linkSelfLoopAnalyzer = {
2649
2776
  for (const link of ctx.links) {
2650
2777
  if (!isSelfLoop(link)) continue;
2651
2778
  issues.push({
2652
- analyzerId: ID16,
2779
+ analyzerId: ID18,
2653
2780
  severity: "warn",
2654
2781
  nodeIds: [link.source],
2655
2782
  message: formatFinding({
@@ -2681,9 +2808,9 @@ var NAME_COLLISION_TEXTS = {
2681
2808
  };
2682
2809
 
2683
2810
  // plugins/core/analyzers/name-collision/index.ts
2684
- var ID17 = "name-collision";
2811
+ var ID19 = "name-collision";
2685
2812
  var nameCollisionAnalyzer = {
2686
- id: ID17,
2813
+ id: ID19,
2687
2814
  pluginId: CORE_PLUGIN_ID,
2688
2815
  kind: "analyzer",
2689
2816
  mode: "deterministic",
@@ -2697,7 +2824,7 @@ var nameCollisionAnalyzer = {
2697
2824
  for (const [name, claims] of collisions) {
2698
2825
  const paths = claims.map((c) => c.path);
2699
2826
  issues.push({
2700
- analyzerId: ID17,
2827
+ analyzerId: ID19,
2701
2828
  severity: "error",
2702
2829
  nodeIds: paths,
2703
2830
  message: formatFinding({
@@ -2747,9 +2874,9 @@ var NAME_RESERVED_TEXTS = {
2747
2874
  };
2748
2875
 
2749
2876
  // plugins/core/analyzers/name-reserved/index.ts
2750
- var ID18 = "name-reserved";
2877
+ var ID20 = "name-reserved";
2751
2878
  var nameReservedAnalyzer = {
2752
- id: ID18,
2879
+ id: ID20,
2753
2880
  pluginId: CORE_PLUGIN_ID,
2754
2881
  kind: "analyzer",
2755
2882
  description: "Flags two kinds of reserved-name collision: a file whose name shadows a built-in command of the active runtime, and a link that resolves to one of those reserved names.",
@@ -2767,7 +2894,7 @@ var nameReservedAnalyzer = {
2767
2894
  const node = byPath3.get(path);
2768
2895
  if (!node) continue;
2769
2896
  issues.push({
2770
- analyzerId: ID18,
2897
+ analyzerId: ID20,
2771
2898
  severity: "warn",
2772
2899
  nodeIds: [node.path],
2773
2900
  message: formatFinding({
@@ -2789,7 +2916,7 @@ var nameReservedAnalyzer = {
2789
2916
  adjust(link, { kind: "delta", value: -RESERVED_PENALTY });
2790
2917
  }
2791
2918
  issues.push({
2792
- analyzerId: ID18,
2919
+ analyzerId: ID20,
2793
2920
  severity: "warn",
2794
2921
  nodeIds: [link.source],
2795
2922
  message: formatFinding({
@@ -2841,7 +2968,7 @@ var NODE_STABILITY_TEXTS = {
2841
2968
  };
2842
2969
 
2843
2970
  // plugins/core/analyzers/node-stability/index.ts
2844
- var ID19 = "node-stability";
2971
+ var ID21 = "node-stability";
2845
2972
  var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
2846
2973
  var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
2847
2974
  var experimental = {
@@ -2859,7 +2986,7 @@ var deprecated = {
2859
2986
  priority: 10
2860
2987
  };
2861
2988
  var nodeStabilityAnalyzer = {
2862
- id: ID19,
2989
+ id: ID21,
2863
2990
  pluginId: CORE_PLUGIN_ID,
2864
2991
  kind: "analyzer",
2865
2992
  description: "Surfaces a node's stability stage on the card: `deprecated` as a chip plus a finding, `experimental` as a chip only; `stable` and unset stay silent.",
@@ -2881,7 +3008,7 @@ var nodeStabilityAnalyzer = {
2881
3008
  severity: "warn"
2882
3009
  });
2883
3010
  issues.push({
2884
- analyzerId: ID19,
3011
+ analyzerId: ID21,
2885
3012
  severity: "warn",
2886
3013
  nodeIds: [node.path],
2887
3014
  message: formatFinding({ body: tx(NODE_STABILITY_TEXTS.deprecated) }),
@@ -2933,9 +3060,9 @@ var REFERENCE_BROKEN_TEXTS = {
2933
3060
  };
2934
3061
 
2935
3062
  // plugins/core/analyzers/reference-broken/index.ts
2936
- var ID20 = "reference-broken";
3063
+ var ID22 = "reference-broken";
2937
3064
  var referenceBrokenAnalyzer = {
2938
- id: ID20,
3065
+ id: ID22,
2939
3066
  pluginId: CORE_PLUGIN_ID,
2940
3067
  kind: "analyzer",
2941
3068
  description: "Flags arrows pointing at a node not part of the current scan.",
@@ -2978,7 +3105,7 @@ function penalizeBrokenConfidence(adjust, link) {
2978
3105
  }
2979
3106
  function buildIssue(link) {
2980
3107
  return {
2981
- analyzerId: ID20,
3108
+ analyzerId: ID22,
2982
3109
  // `error`, not `warn`: a link whose target is not in the scan is a
2983
3110
  // structural defect the operator must notice, and the card chip
2984
3111
  // paints `danger` (red) to match. Per the chip-vs-issue policy in
@@ -3042,9 +3169,9 @@ var REFERENCE_REDUNDANT_TEXTS = {
3042
3169
  };
3043
3170
 
3044
3171
  // plugins/core/analyzers/reference-redundant/index.ts
3045
- var ID21 = "reference-redundant";
3172
+ var ID23 = "reference-redundant";
3046
3173
  var referenceRedundantAnalyzer = {
3047
- id: ID21,
3174
+ id: ID23,
3048
3175
  pluginId: CORE_PLUGIN_ID,
3049
3176
  kind: "analyzer",
3050
3177
  description: "Flags when one node references the same target through two or more different links (e.g. a markdown link plus a `references:` entry).",
@@ -3067,7 +3194,7 @@ var referenceRedundantAnalyzer = {
3067
3194
  const [source, resolvedTarget] = key.split("\0");
3068
3195
  const flat = flattenOccurrences(links);
3069
3196
  issues.push({
3070
- analyzerId: ID21,
3197
+ analyzerId: ID23,
3071
3198
  severity: "info",
3072
3199
  nodeIds: [source],
3073
3200
  message: formatFinding({
@@ -3403,9 +3530,9 @@ var SCHEMA_VIOLATION_TEXTS = {
3403
3530
  };
3404
3531
 
3405
3532
  // plugins/core/analyzers/schema-violation/index.ts
3406
- var ID22 = "schema-violation";
3533
+ var ID24 = "schema-violation";
3407
3534
  var schemaViolationAnalyzer = {
3408
- id: ID22,
3535
+ id: ID24,
3409
3536
  pluginId: CORE_PLUGIN_ID,
3410
3537
  kind: "analyzer",
3411
3538
  description: "Flags nodes or links that violate the project schemas.",
@@ -3455,7 +3582,7 @@ function collectNodeFindings(v, node, out) {
3455
3582
  const result = v.validate("node", toNodeForSchema(node));
3456
3583
  if (result.ok) return;
3457
3584
  out.push({
3458
- analyzerId: ID22,
3585
+ analyzerId: ID24,
3459
3586
  severity: "error",
3460
3587
  nodeIds: [node.path],
3461
3588
  message: formatFinding({
@@ -3470,7 +3597,7 @@ function collectLinkFindings(v, link, out) {
3470
3597
  const result = v.validate("link", toLinkForSchema(link));
3471
3598
  if (result.ok) return;
3472
3599
  out.push({
3473
- analyzerId: ID22,
3600
+ analyzerId: ID24,
3474
3601
  severity: "error",
3475
3602
  nodeIds: [link.source],
3476
3603
  message: formatFinding({
@@ -3539,13 +3666,13 @@ var ASCII_FORMATTER_TEXTS = {
3539
3666
  };
3540
3667
 
3541
3668
  // plugins/core/formatters/ascii/index.ts
3542
- var ID23 = "ascii";
3669
+ var ID25 = "ascii";
3543
3670
  var KIND_ORDER = ["agent", "command", "skill", "markdown"];
3544
3671
  var asciiFormatter = {
3545
- id: ID23,
3672
+ id: ID25,
3546
3673
  pluginId: CORE_PLUGIN_ID,
3547
3674
  kind: "formatter",
3548
- formatId: ID23,
3675
+ formatId: ID25,
3549
3676
  description: "Renders the scan as plain text in three sections: nodes (grouped by kind), arrows, and issues. Used by `sm scan --format ascii`.",
3550
3677
  // ASCII tree formatter, header + per-kind sections + per-issue
3551
3678
  // section. Each section iterates and renders; splitting per section
@@ -3639,13 +3766,13 @@ function renderSection(out, kind, group) {
3639
3766
  }
3640
3767
 
3641
3768
  // plugins/core/formatters/json/index.ts
3642
- var ID24 = "json";
3769
+ var ID26 = "json";
3643
3770
  var jsonFormatter = {
3644
- id: ID24,
3771
+ id: ID26,
3645
3772
  pluginId: CORE_PLUGIN_ID,
3646
3773
  kind: "formatter",
3647
3774
  description: "Renders the persisted scan as JSON (conforms to `scan-result.schema.json`). Used by `sm graph --format json` and `GET /api/graph?format=json`.",
3648
- formatId: ID24,
3775
+ formatId: ID26,
3649
3776
  format(ctx) {
3650
3777
  if (ctx.scanResult !== void 0) {
3651
3778
  return JSON.stringify(ctx.scanResult);
@@ -3792,13 +3919,13 @@ var BUMP_TEXTS = {
3792
3919
  };
3793
3920
 
3794
3921
  // plugins/core/actions/node-bump/index.ts
3795
- var ID25 = "node-bump";
3922
+ var ID27 = "node-bump";
3796
3923
  var bumpButton = {
3797
3924
  slot: "inspector.action.button",
3798
3925
  priority: 10
3799
3926
  };
3800
3927
  var nodeBumpAction = {
3801
- id: ID25,
3928
+ id: ID27,
3802
3929
  pluginId: CORE_PLUGIN_ID,
3803
3930
  kind: "action",
3804
3931
  description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
@@ -3901,13 +4028,13 @@ var NODE_SET_STABILITY_TEXTS = {
3901
4028
  };
3902
4029
 
3903
4030
  // plugins/core/actions/node-set-stability/index.ts
3904
- var ID26 = "node-set-stability";
4031
+ var ID28 = "node-set-stability";
3905
4032
  var setStabilityButton = {
3906
4033
  slot: "inspector.action.button",
3907
4034
  priority: 15
3908
4035
  };
3909
4036
  var nodeSetStabilityAction = {
3910
- id: ID26,
4037
+ id: ID28,
3911
4038
  pluginId: CORE_PLUGIN_ID,
3912
4039
  kind: "action",
3913
4040
  description: "Sets the lifecycle stage of the current node (writes `stability` to the sidecar).",
@@ -3979,9 +4106,9 @@ function invokeSetStability(input, ctx) {
3979
4106
  }
3980
4107
 
3981
4108
  // plugins/core/actions/node-set-tags/index.ts
3982
- var ID27 = "node-set-tags";
4109
+ var ID29 = "node-set-tags";
3983
4110
  var nodeSetTagsAction = {
3984
- id: ID27,
4111
+ id: ID29,
3985
4112
  pluginId: CORE_PLUGIN_ID,
3986
4113
  kind: "action",
3987
4114
  description: "Sets the taxonomy tags of the current node (writes `tags` to the sidecar; whole-array replace).",
@@ -4519,16 +4646,18 @@ var updateCheckHook = {
4519
4646
  // plugins/built-ins.ts
4520
4647
  var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: VERSION };
4521
4648
  var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: VERSION };
4522
- var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: VERSION };
4523
4649
  var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "claude", version: VERSION };
4524
4650
  var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: VERSION };
4525
4651
  var codexProvider2 = { ...codexProvider, pluginId: "codex", version: VERSION };
4652
+ var dollarSkillExtractor2 = { ...dollarSkillExtractor, pluginId: "codex", version: VERSION };
4526
4653
  var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: VERSION };
4527
4654
  var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: VERSION };
4655
+ var atFileExtractor2 = { ...atFileExtractor, pluginId: "core", version: VERSION };
4528
4656
  var backtickPathExtractor2 = { ...backtickPathExtractor, pluginId: "core", version: VERSION };
4529
4657
  var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: VERSION };
4530
4658
  var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: VERSION };
4531
4659
  var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: VERSION };
4660
+ var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "core", version: VERSION };
4532
4661
  var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: VERSION };
4533
4662
  var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: VERSION };
4534
4663
  var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: VERSION };
@@ -4557,7 +4686,6 @@ var builtInPlugins = [
4557
4686
  extensions: [
4558
4687
  claudeProvider2,
4559
4688
  atDirectiveExtractor2,
4560
- slashCommandExtractor2,
4561
4689
  toolsCounterExtractor2
4562
4690
  ]
4563
4691
  },
@@ -4572,7 +4700,8 @@ var builtInPlugins = [
4572
4700
  id: "codex",
4573
4701
  description: "OpenAI Codex CLI platform integration. Classifies TOML sub-agent definitions under `.codex/agents/*.toml`.",
4574
4702
  extensions: [
4575
- codexProvider2
4703
+ codexProvider2,
4704
+ dollarSkillExtractor2
4576
4705
  ]
4577
4706
  },
4578
4707
  {
@@ -4587,10 +4716,12 @@ var builtInPlugins = [
4587
4716
  description: "Core extensions shared across providers: parsers, extractors, analyzers, actions, hooks, formatters, and the universal `.md` fallback provider.",
4588
4717
  extensions: [
4589
4718
  coreMarkdownProvider2,
4719
+ atFileExtractor2,
4590
4720
  backtickPathExtractor2,
4591
4721
  externalUrlCounterExtractor2,
4592
4722
  markdownLinkExtractor2,
4593
4723
  mcpToolsExtractor2,
4724
+ slashCommandExtractor2,
4594
4725
  annotationFieldUnknownAnalyzer2,
4595
4726
  annotationOrphanAnalyzer2,
4596
4727
  annotationStaleAnalyzer2,
@@ -12176,14 +12307,16 @@ import { existsSync as existsSync16 } from "fs";
12176
12307
  import { join as join10 } from "path";
12177
12308
  function detectProvidersFromFilesystem(cwd, providers) {
12178
12309
  const seen = /* @__PURE__ */ new Set();
12179
- const out = [];
12310
+ const matched = [];
12180
12311
  for (const provider of providers) {
12181
12312
  if (seen.has(provider.id)) continue;
12182
12313
  if (!isDetectableUnderCwd(cwd, provider)) continue;
12183
12314
  seen.add(provider.id);
12184
- out.push(provider.id);
12315
+ matched.push(provider);
12185
12316
  }
12186
- return out;
12317
+ const hasVendor = matched.some((p) => p.detect?.fallback !== true);
12318
+ const kept = hasVendor ? matched.filter((p) => p.detect?.fallback !== true) : matched;
12319
+ return kept.map((p) => p.id);
12187
12320
  }
12188
12321
  function isDetectableUnderCwd(cwd, provider) {
12189
12322
  if (!installedDefaultEnabled(provider.stability)) return false;
@@ -16400,6 +16533,8 @@ var ORCHESTRATOR_TEXTS = {
16400
16533
  frontmatterMalformedPasteWithIndent: "Frontmatter fence in {{path}} appears indented; YAML frontmatter MUST start with `---` at column 0. The file was scanned as body-only; the metadata block was silently lost. Move the `---` lines to the start of the line.",
16401
16534
  frontmatterMalformedByteOrderMark: "Frontmatter fence in {{path}} is preceded by a UTF-8 byte-order mark (BOM); the file was scanned as body-only. Re-save the file as UTF-8 without BOM. The metadata block was silently lost.",
16402
16535
  frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes (no matching `---` line at column 0 was found). The file was scanned as body-only and every metadata field was silently lost. Add a closing `---` line below the metadata block.",
16536
+ bodyBacktickUnclosedFence: "Body of {{path}} has an unclosed fenced code block opened at body line {{line}} (no matching closing ``` or ~~~). The code-strip policy then reads the rest of the file as code, so prose extractors stop emitting edges past it. Close the fence.",
16537
+ bodyBacktickUnclosedInline: "Body of {{path}} has an unclosed inline backtick at body line {{line}} (the backtick run has no equal-length closer). Close the inline span with a matching backtick run, or escape a literal backtick with a backslash.",
16403
16538
  extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
16404
16539
  extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
16405
16540
  extensionErrorContributionUndeclaredRef: 'Extension "{{extractorId}}" emitted a view contribution on {{nodePath}} whose object is not one declared in its `ui` map (pass the declared const by reference, do not spread or inline it). Contribution dropped.',
@@ -17085,6 +17220,11 @@ var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
17085
17220
  "frontmatter-malformed",
17086
17221
  "frontmatter-parse-error"
17087
17222
  ]);
17223
+ var BACKTICK_ISSUE_ID = "backtick-unbalanced";
17224
+ var CACHED_KERNEL_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
17225
+ ...FRONTMATTER_ISSUE_ANALYZERS,
17226
+ BACKTICK_ISSUE_ID
17227
+ ]);
17088
17228
 
17089
17229
  // kernel/orchestrator/cache.ts
17090
17230
  function indexPriorSnapshot(prior) {
@@ -17114,7 +17254,7 @@ function indexPriorLinks(links, byOriginating) {
17114
17254
  }
17115
17255
  function indexPriorFrontmatterIssues(issues, byNode) {
17116
17256
  for (const issue of issues) {
17117
- if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
17257
+ if (!CACHED_KERNEL_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
17118
17258
  if (issue.nodeIds.length !== 1) continue;
17119
17259
  const path = issue.nodeIds[0];
17120
17260
  const list = byNode.get(path);
@@ -17254,7 +17394,7 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
17254
17394
  }
17255
17395
 
17256
17396
  // kernel/orchestrator/node-identifiers.ts
17257
- import { posix as pathPosix4 } from "path";
17397
+ import { posix as pathPosix5 } from "path";
17258
17398
  function deriveNodeIdentifiers(node, kindDescriptor) {
17259
17399
  const sources = kindDescriptor?.identifiers;
17260
17400
  if (!sources || sources.length === 0) return [];
@@ -17278,16 +17418,16 @@ function readFrontmatterName(node) {
17278
17418
  return raw.length > 0 ? raw : null;
17279
17419
  }
17280
17420
  function readFilenameBasename(node) {
17281
- const base = pathPosix4.basename(node.path);
17421
+ const base = pathPosix5.basename(node.path);
17282
17422
  if (!base) return null;
17283
- const ext = pathPosix4.extname(base);
17423
+ const ext = pathPosix5.extname(base);
17284
17424
  const stem = ext ? base.slice(0, -ext.length) : base;
17285
17425
  return stem.length > 0 ? stem : null;
17286
17426
  }
17287
17427
  function readDirname(node) {
17288
- const dir = pathPosix4.dirname(node.path);
17428
+ const dir = pathPosix5.dirname(node.path);
17289
17429
  if (!dir || dir === "." || dir === "/") return null;
17290
- const base = pathPosix4.basename(dir);
17430
+ const base = pathPosix5.basename(dir);
17291
17431
  return base.length > 0 ? base : null;
17292
17432
  }
17293
17433
  function collectNameCollisions(nodes, kindRegistry) {
@@ -17384,7 +17524,7 @@ function lookupAllowedKinds(link, _indexes, ctx) {
17384
17524
  }
17385
17525
  function stripTriggerSigil(normalized) {
17386
17526
  if (!normalized) return null;
17387
- const trimmed = normalized.replace(/^[/@]/, "").trim();
17527
+ const trimmed = normalized.replace(/^[/@$]/, "").trim();
17388
17528
  return trimmed.length === 0 ? null : trimmed;
17389
17529
  }
17390
17530
  function indexNode(node, ctx, byName) {
@@ -17858,6 +17998,21 @@ function malformedMessage(hint, path) {
17858
17998
  }
17859
17999
  }
17860
18000
 
18001
+ // kernel/orchestrator/body-syntax.ts
18002
+ function detectUnclosedBacktick(body, path, strict) {
18003
+ const imbalance = findBacktickImbalance(body);
18004
+ if (!imbalance) return null;
18005
+ const template = imbalance.kind === "fence" ? ORCHESTRATOR_TEXTS.bodyBacktickUnclosedFence : ORCHESTRATOR_TEXTS.bodyBacktickUnclosedInline;
18006
+ return {
18007
+ analyzerId: BACKTICK_ISSUE_ID,
18008
+ severity: strict ? "error" : "warn",
18009
+ nodeIds: [path],
18010
+ message: tx(template, { path, line: imbalance.line }),
18011
+ detail: imbalance.sourceLine,
18012
+ data: { kind: imbalance.kind, line: imbalance.line }
18013
+ };
18014
+ }
18015
+
17861
18016
  // kernel/orchestrator/node-build.ts
17862
18017
  function buildNode(args2) {
17863
18018
  const bytesFrontmatter = Buffer.byteLength(args2.frontmatterRaw, "utf8");
@@ -17984,6 +18139,9 @@ function relativePathFromRoots(absolutePath, roots) {
17984
18139
  }
17985
18140
  return absolutePath;
17986
18141
  }
18142
+ function pushIssue(list, issue) {
18143
+ if (issue) list.push(issue);
18144
+ }
17987
18145
  function buildFreshNodeAndValidateFrontmatter(opts) {
17988
18146
  const node = buildNode({
17989
18147
  path: opts.raw.path,
@@ -18011,19 +18169,27 @@ function buildFreshNodeAndValidateFrontmatter(opts) {
18011
18169
  }
18012
18170
  }
18013
18171
  if (opts.raw.frontmatterRaw.length > 0) {
18014
- const fmIssue = validateFrontmatter(
18015
- opts.providerFrontmatter,
18016
- opts.provider,
18017
- opts.kind,
18018
- opts.raw.frontmatter,
18019
- opts.raw.path,
18020
- opts.strict
18172
+ pushIssue(
18173
+ frontmatterIssues,
18174
+ validateFrontmatter(
18175
+ opts.providerFrontmatter,
18176
+ opts.provider,
18177
+ opts.kind,
18178
+ opts.raw.frontmatter,
18179
+ opts.raw.path,
18180
+ opts.strict
18181
+ )
18021
18182
  );
18022
- if (fmIssue) frontmatterIssues.push(fmIssue);
18023
18183
  } else {
18024
- const malformed = detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict);
18025
- if (malformed) frontmatterIssues.push(malformed);
18184
+ pushIssue(
18185
+ frontmatterIssues,
18186
+ detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict)
18187
+ );
18026
18188
  }
18189
+ pushIssue(
18190
+ frontmatterIssues,
18191
+ detectUnclosedBacktick(opts.raw.body, opts.raw.path, opts.strict)
18192
+ );
18027
18193
  return { node, frontmatterIssues };
18028
18194
  }
18029
18195
 
@@ -30420,6 +30586,7 @@ function presentationOptionals(ui) {
30420
30586
  if (ui.emoji !== void 0) out.emoji = ui.emoji;
30421
30587
  if (ui.icon !== void 0) out.icon = ui.icon;
30422
30588
  if (ui.hideChip !== void 0) out.hideChip = ui.hideChip;
30589
+ if (ui.invocationSigil !== void 0) out.invocationSigil = ui.invocationSigil;
30423
30590
  return out;
30424
30591
  }
30425
30592
  function resolveProviderBodyField(read) {
@@ -32436,15 +32603,20 @@ var TUTORIAL_TEXTS = {
32436
32603
  writtenLabelEs: "Espa\xF1ol",
32437
32604
  // Destination-provider prompt (interactive stdin, no `--for`). Header
32438
32605
  // uses a yellow `?` glyph; options are a numbered list of the provider's
32439
- // vendor name (for a provider with an `aka`, the aka vendor leads and the
32440
- // provider label follows in parentheses), with a `(default)` marker on
32441
- // the first option (Claude). The destination folder is deliberately NOT
32442
- // shown: several providers share `.agents/skills`, so the folder does not
32443
- // identify the lens. The input line accepts a number, a provider id, or
32444
- // an empty answer (which takes the default).
32606
+ // label (for a provider with an `aka`, the standard label leads and the
32607
+ // supporting vendors follow in parentheses, closed by `akaOthers` to mark
32608
+ // the set as open), with a `(default)` marker on the first option (Claude).
32609
+ // The destination folder is deliberately NOT shown: several providers share
32610
+ // `.agents/skills`, so the folder does not identify the lens. The input line
32611
+ // accepts a number, a provider id, or an empty answer (which takes the
32612
+ // default).
32445
32613
  promptHeader: "{{glyph}} Which agent should host the tutorial skill?",
32446
32614
  promptOption: " {{index}}) {{label}}{{marker}}",
32447
32615
  promptDefaultMarker: " (default)",
32616
+ // Trailing token appended to the `aka` vendor list in the open-lens label
32617
+ // ("... (Google's Antigravity, others)"), signalling the open standard is
32618
+ // not tied to a single vendor and more will support it.
32619
+ akaOthers: "others",
32448
32620
  promptInput: " Enter the number or provider id [default {{index}}]: ",
32449
32621
  // Prompt answer matched neither an index nor an id. Goes to stderr,
32450
32622
  // exit code 2. Mirrors the error shape: glyph + headline + dim hint.
@@ -32694,7 +32866,7 @@ function listScaffoldTargets(includeExperimental = false) {
32694
32866
  return out;
32695
32867
  }
32696
32868
  function labelWithAka(target) {
32697
- return target.aka.length > 0 ? `${target.aka.join(", ")} (${target.label})` : target.label;
32869
+ return target.aka.length > 0 ? `${target.label} (${[...target.aka, TUTORIAL_TEXTS.akaOthers].join(", ")})` : target.label;
32698
32870
  }
32699
32871
  function renderTargetLines(targets, def, glyph) {
32700
32872
  const lines = [tx(TUTORIAL_TEXTS.promptHeader, { glyph })];
@@ -32989,4 +33161,4 @@ function resolveBareDefault() {
32989
33161
  process.exit(ExitCode.Error);
32990
33162
  }
32991
33163
  //# sourceMappingURL=cli.js.map
32992
- //# debugId=75d0c2c2-d08f-5d7c-aa62-3e1d53dd4ef1
33164
+ //# debugId=81f53ca6-a8bf-5fc1-909d-ce926a83f056