@skill-map/cli 0.70.0 → 0.72.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 (29) hide show
  1. package/dist/cli/tutorial/sm-tutorial/SKILL.md +2 -2
  2. package/dist/cli/tutorial/sm-tutorial/fixtures-data/manifest.json +1 -0
  3. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/providers/codex/shared/CLAUDE.md +1 -0
  4. package/dist/cli/tutorial/sm-tutorial/references/_core.md +27 -20
  5. package/dist/cli/tutorial/sm-tutorial/references/_manifest.json +1 -1
  6. package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +3 -3
  7. package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +4 -1
  8. package/dist/cli/tutorial/sm-tutorial/references/part-authoring.md +0 -7
  9. package/dist/cli/tutorial/sm-tutorial/references/part-basic-daily.md +45 -22
  10. package/dist/cli/tutorial/sm-tutorial/references/part-basic-fundamentals.md +6 -13
  11. package/dist/cli/tutorial/sm-tutorial/references/part-basic-kickoff.md +19 -38
  12. package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +0 -4
  13. package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +0 -17
  14. package/dist/cli/tutorial/sm-tutorial/references/part-fundamentals.md +0 -4
  15. package/dist/cli/tutorial/sm-tutorial/references/part-mcp.md +4 -14
  16. package/dist/cli/tutorial/sm-tutorial/references/part-plugins.md +0 -8
  17. package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +5 -25
  18. package/dist/cli/tutorial/sm-tutorial/references/part-settings.md +2 -10
  19. package/dist/cli/tutorial/sm-tutorial/scripts/fixtures.js +6 -3
  20. package/dist/cli.js +471 -241
  21. package/dist/index.js +109 -14
  22. package/dist/kernel/index.js +109 -14
  23. package/dist/ui/chunk-GKN3HN4R.js +3 -0
  24. package/dist/ui/index.html +1 -1
  25. package/dist/ui/{main-K4O6LCIJ.js → main-Z2OYMXNS.js} +1 -1
  26. package/package.json +2 -2
  27. package/dist/ui/chunk-RJUHQQOF.js +0 -3
  28. /package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/providers/agent-skills/shared/{CLAUDE.md → README.md} +0 -0
  29. /package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/{shared → providers/claude/shared}/CLAUDE.md +0 -0
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]="ca8113af-a7ac-5cd7-b45c-03a5974bca86")}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]="2c7d4927-4422-59d2-a37a-59fed3a2ec80")}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.70.0",
253
+ version: "0.72.0",
254
254
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
255
255
  license: "MIT",
256
256
  type: "module",
@@ -581,9 +581,10 @@ 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
+ var OPENCODE_PLUGIN_ID = "opencode";
587
588
 
588
589
  // plugins/claude/providers/claude/index.ts
589
590
  var RESERVED_SLASH_NAMES = [
@@ -808,9 +809,6 @@ var claudeProvider = {
808
809
  }
809
810
  };
810
811
 
811
- // plugins/claude/extractors/at-directive/index.ts
812
- import { posix as pathPosix } from "path";
813
-
814
812
  // kernel/util/strip-code-blocks.ts
815
813
  var FENCE_RE = /^(?<indent> {0,3})(?<fence>`{3,}|~{3,})/;
816
814
  function stripCodeBlocks(input) {
@@ -818,6 +816,28 @@ function stripCodeBlocks(input) {
818
816
  const fenceless = stripFences(input);
819
817
  return stripInline(fenceless);
820
818
  }
819
+ function findBacktickImbalance(body) {
820
+ if (!body) return null;
821
+ const { stripped, openFenceLine } = scanFences(body);
822
+ if (openFenceLine > 0) {
823
+ return { kind: "fence", line: openFenceLine, sourceLine: sourceLineAt(body, openFenceLine) };
824
+ }
825
+ const survivor = stripInline(maskEscapes(stripped)).indexOf("`");
826
+ if (survivor < 0) return null;
827
+ const line = lineOfIndex(stripped, survivor);
828
+ return { kind: "inline", line, sourceLine: sourceLineAt(body, line) };
829
+ }
830
+ function maskEscapes(text) {
831
+ return text.replace(/\\[^\n]/g, " ");
832
+ }
833
+ function lineOfIndex(text, idx) {
834
+ let line = 1;
835
+ for (let i = 0; i < idx; i++) if (text[i] === "\n") line++;
836
+ return line;
837
+ }
838
+ function sourceLineAt(text, line) {
839
+ return text.split("\n")[line - 1]?.trim() ?? "";
840
+ }
821
841
  function extractCodeRegions(input) {
822
842
  if (!input) return input;
823
843
  const stripped = stripCodeBlocks(input);
@@ -837,30 +857,31 @@ function stripHtml(input) {
837
857
  function stripCodeAndHtml(input) {
838
858
  return stripHtml(stripCodeBlocks(input));
839
859
  }
840
- function stripFences(input) {
860
+ function scanFences(input) {
841
861
  const out = [];
842
862
  const lines = input.split("\n");
843
863
  let openFence = null;
844
- for (const line of lines) {
864
+ let openFenceLine = 0;
865
+ for (let i = 0; i < lines.length; i++) {
866
+ const line = lines[i];
845
867
  if (openFence) {
846
- const closer = matchClosingFence(line, openFence);
847
- if (closer) {
848
- out.push(blank(line));
849
- openFence = null;
850
- } else {
851
- out.push(blank(line));
852
- }
868
+ if (matchClosingFence(line, openFence)) openFence = null;
869
+ out.push(blank(line));
853
870
  continue;
854
871
  }
855
872
  const open3 = FENCE_RE.exec(line);
856
873
  if (open3?.groups) {
857
874
  openFence = open3.groups["fence"];
875
+ openFenceLine = i + 1;
858
876
  out.push(blank(line));
859
877
  continue;
860
878
  }
861
879
  out.push(line);
862
880
  }
863
- return out.join("\n");
881
+ return { stripped: out.join("\n"), openFenceLine: openFence ? openFenceLine : 0 };
882
+ }
883
+ function stripFences(input) {
884
+ return scanFences(input).stripped;
864
885
  }
865
886
  function matchClosingFence(line, openFence) {
866
887
  const m = FENCE_RE.exec(line);
@@ -906,82 +927,61 @@ function normalizeTrigger(source) {
906
927
  return out.trim();
907
928
  }
908
929
 
930
+ // kernel/util/at-token.ts
931
+ import { posix as pathPosix } from "path";
932
+ 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;
933
+ 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;
934
+ function classifyAtFileToken(bare) {
935
+ if (bare.startsWith("/")) return null;
936
+ if (bare.startsWith("./") || bare.startsWith("../")) return "relative path prefix";
937
+ if (FILE_EXT_RE.test(bare)) return "known file extension";
938
+ return null;
939
+ }
940
+ function resolveAtFileTarget(sourceDir, bare) {
941
+ const joined = sourceDir === "." ? bare : `${sourceDir}/${bare}`;
942
+ return pathPosix.normalize(joined);
943
+ }
944
+
909
945
  // plugins/claude/extractors/at-directive/index.ts
910
946
  var ID = "at-directive";
911
- 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;
912
- 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;
913
947
  var atDirectiveExtractor = {
914
948
  id: ID,
915
949
  pluginId: CLAUDE_PLUGIN_ID,
916
950
  kind: "extractor",
917
- 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.",
951
+ 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.",
918
952
  scope: "body",
919
- // Claude-only. This is Claude's `@<name>` grammar, where a bare handle is
920
- // an agent / entity MENTION. OpenAI Codex's `@` is a file-path picker, not
921
- // a mention grammar, so codex is NOT gated here; the codex-owned `at-file`
922
- // extractor covers `@`-as-file-reference under the codex lens.
953
+ // Claude-only. This is Claude's bare `@<handle>` MENTION grammar. The
954
+ // file-reference half of `@` (file-shaped tokens) is the shared
955
+ // `core/at-file` extractor, which also serves codex / antigravity; this
956
+ // extractor defers file-shaped tokens to it.
923
957
  precondition: { provider: ["claude"] },
924
- // eslint-disable-next-line complexity
925
958
  extract(ctx) {
926
959
  const seenMentions = /* @__PURE__ */ new Set();
927
- const seenReferences = /* @__PURE__ */ new Set();
928
960
  const body = stripCodeAndHtml(ctx.body);
929
961
  const lineStarts = computeLineStarts(body);
930
- const sourceDir = pathPosix.dirname(ctx.node.path);
931
- for (const match of body.matchAll(AT_RE)) {
962
+ for (const match of body.matchAll(AT_TOKEN_RE)) {
932
963
  const original = match[1];
933
964
  const bare = original.slice(1);
934
- const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
935
- const line = lineFor(lineStarts, captureOffset);
936
- const range = { start: captureOffset, end: captureOffset + original.length, line };
937
965
  if (bare.startsWith("/")) continue;
938
- const isReference = bare.startsWith("./") || bare.startsWith("../") || FILE_EXT_RE.test(bare);
939
- if (isReference) {
940
- const target = resolveSourceRelative(sourceDir, bare);
941
- const dedupKey = target.toLowerCase();
942
- if (seenReferences.has(dedupKey)) continue;
943
- seenReferences.add(dedupKey);
944
- ctx.emitSignal({
945
- source: ctx.node.path,
946
- scope: "body",
947
- range,
948
- raw: original,
949
- candidates: [
950
- {
951
- extractorId: ID,
952
- kind: "references",
953
- target,
954
- // 0.85: strong file signal (path prefix `./` / `../` OR
955
- // a known file extension on the tail). One degree of
956
- // inference (the runtime still resolves the path).
957
- confidence: 0.85,
958
- rationale: bare.startsWith("./") || bare.startsWith("../") ? "relative path prefix" : "known file extension",
959
- trigger: {
960
- originalTrigger: original,
961
- normalizedTrigger: target
962
- }
963
- }
964
- ]
965
- });
966
- continue;
967
- }
966
+ if (classifyAtFileToken(bare) !== null) continue;
968
967
  const normalized = normalizeTrigger(original);
969
968
  if (seenMentions.has(normalized)) continue;
970
969
  seenMentions.add(normalized);
970
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
971
+ const line = lineFor(lineStarts, captureOffset);
971
972
  ctx.emitSignal({
972
973
  source: ctx.node.path,
973
974
  scope: "body",
974
- range,
975
+ range: { start: captureOffset, end: captureOffset + original.length, line },
975
976
  raw: original,
976
977
  candidates: [
977
978
  {
978
979
  extractorId: ID,
979
980
  kind: "mentions",
980
981
  target: original,
981
- // 0.5: genuine ambiguity. A bare `@handle` (no extension, no
982
- // path prefix) could be an agent, a handle, or generic prose.
983
- // The runtime decides at invocation time; the extractor leaves
984
- // the question open.
982
+ // 0.5: genuine ambiguity. A bare `@handle` (no extension, no path
983
+ // prefix) could be an agent, a handle, or generic prose. The
984
+ // runtime decides at invocation time; the extractor leaves it open.
985
985
  confidence: 0.5,
986
986
  rationale: "no extension, no path prefix",
987
987
  trigger: {
@@ -994,72 +994,9 @@ var atDirectiveExtractor = {
994
994
  }
995
995
  }
996
996
  };
997
- function resolveSourceRelative(sourceDir, bare) {
998
- const joined = sourceDir === "." ? bare : `${sourceDir}/${bare}`;
999
- return pathPosix.normalize(joined);
1000
- }
1001
-
1002
- // plugins/claude/extractors/slash-command/index.ts
1003
- var ID2 = "slash-command";
1004
- var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
1005
- var slashCommandExtractor = {
1006
- id: ID2,
1007
- pluginId: CLAUDE_PLUGIN_ID,
1008
- kind: "extractor",
1009
- 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.",
1010
- scope: "body",
1011
- // Also authorised under the antigravity lens, which shares the `/command`
1012
- // grammar: a workflow / skill / AGENTS.md body's `/name` tokens resolve to
1013
- // BOTH skills and workflows (`invokes: ['skill', 'workflow']`), since
1014
- // Antigravity invokes either by the same slash. NOT gated under codex:
1015
- // OpenAI Codex reserves `/` for its OWN built-in commands (`/model`,
1016
- // `/init`, ...) and invokes user skills with `$` instead (parsed by the
1017
- // codex `dollar-skill` extractor). A lens that declares no `invokes`
1018
- // resolution leaves the signals unresolved (no spurious edges).
1019
- precondition: { provider: ["claude", "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
997
 
1061
998
  // plugins/claude/extractors/tools-counter/index.ts
1062
- var ID3 = "tools-counter";
999
+ var ID2 = "tools-counter";
1063
1000
  var count = {
1064
1001
  slot: "card.footer.left",
1065
1002
  icon: "pi-wrench",
@@ -1069,7 +1006,7 @@ var count = {
1069
1006
  };
1070
1007
  var TOOLTIP_MAX = 255;
1071
1008
  var toolsCounterExtractor = {
1072
- id: ID3,
1009
+ id: ID2,
1073
1010
  pluginId: CLAUDE_PLUGIN_ID,
1074
1011
  kind: "extractor",
1075
1012
  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.",
@@ -1355,10 +1292,15 @@ var antigravityProvider = {
1355
1292
  schemaJson: workflow_schema_default,
1356
1293
  ui: {
1357
1294
  label: "Workflows",
1358
- // Antigravity violet, so a workflow node reads as Antigravity's own
1359
- // (skills keep the normalised cross-provider green of COMMONS_KINDS).
1360
- color: "#7c3aed",
1361
- colorDark: "#a78bfa",
1295
+ // A workflow is Antigravity's command-equivalent (its own slash-
1296
+ // invocable procedural kind, the slot Claude fills with `command`),
1297
+ // so it adopts the SAME color as Claude's `command` kind (amber) for a
1298
+ // uniform cross-provider vocabulary, the same way skills normalise to
1299
+ // the cross-provider green of COMMONS_KINDS. The Antigravity violet
1300
+ // stays on the provider-level `presentation` (the lens identity), not
1301
+ // on this kind.
1302
+ color: "#f59e0b",
1303
+ colorDark: "#fbbf24",
1362
1304
  icon: { kind: "pi", id: "pi-sitemap" }
1363
1305
  },
1364
1306
  // The handle is ALWAYS the filename stem (`/<name>`): Antigravity
@@ -1370,7 +1312,12 @@ var antigravityProvider = {
1370
1312
  // `/<name>` slash invocations resolve to BOTH skills and workflows: under
1371
1313
  // the antigravity lens a `/deploy` links to either `.agents/skills/deploy`
1372
1314
  // or `.agent/workflows/deploy.md`. Overrides the open-standard default
1373
- // (`invokes: ['skill']`) to add the own `workflow` kind.
1315
+ // (`invokes: ['skill']`) to add the own `workflow` kind. Antigravity's OTHER
1316
+ // connector, `@filename` file references (the documented rules / skill /
1317
+ // workflow file pointer, a file-picker grammar like Codex's, distinct from
1318
+ // Claude's `@`-agent-mention), needs no entry here: the shared `core/at-file`
1319
+ // extractor (gated to claude / codex / antigravity) emits them as
1320
+ // `references` resolved by PATH, lens-independent.
1374
1321
  resolution: { invokes: ["skill", "workflow"] },
1375
1322
  classify(path) {
1376
1323
  if (/^\.agent\/workflows\/[^/]+\.md$/.test(path.toLowerCase())) return "workflow";
@@ -1461,7 +1408,7 @@ var agent_schema_default2 = {
1461
1408
  // plugins/codex/providers/codex/index.ts
1462
1409
  var codexProvider = {
1463
1410
  id: "codex",
1464
- pluginId: OPENAI_PLUGIN_ID,
1411
+ pluginId: CODEX_PLUGIN_ID,
1465
1412
  kind: "provider",
1466
1413
  description: "Classifies `.codex/agents/*.toml` as OpenAI Codex CLI sub-agents and `.agents/skills/*/SKILL.md` as Codex skills (open standard).",
1467
1414
  // Provider identity for the active-lens dropdown, the topbar lens chip,
@@ -1562,78 +1509,12 @@ var codexProvider = {
1562
1509
  }
1563
1510
  };
1564
1511
 
1565
- // plugins/codex/extractors/at-file/index.ts
1566
- import { posix as pathPosix2 } from "path";
1567
- var ID4 = "at-file";
1568
- var AT_RE2 = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-z0-9_])?(?::[a-z0-9][a-z0-9_-]*)?)/gi;
1569
- var FILE_EXT_RE2 = /\.(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;
1570
- var atFileExtractor = {
1571
- id: ID4,
1572
- pluginId: OPENAI_PLUGIN_ID,
1573
- kind: "extractor",
1574
- description: "Detects `@<file>` references in a node's body under the OpenAI Codex lens, where `@` is a file picker. A path- or extension-shaped token becomes a `references` link to that file; a bare `@handle` forms no edge. Example: `@builder.toml` in an agent's prompt draws an arrow to the `builder` agent file.",
1575
- scope: "body",
1576
- // Codex-only: under the codex lens `@` is a file-path picker, so only
1577
- // file-shaped tokens form references. The claude `at-directive` (bare
1578
- // `@handle` → agent mention) is NOT gated under codex.
1579
- precondition: { provider: ["codex"] },
1580
- extract(ctx) {
1581
- const seenReferences = /* @__PURE__ */ new Set();
1582
- const body = stripCodeAndHtml(ctx.body);
1583
- const lineStarts = computeLineStarts(body);
1584
- const sourceDir = pathPosix2.dirname(ctx.node.path);
1585
- for (const match of body.matchAll(AT_RE2)) {
1586
- const original = match[1];
1587
- const bare = original.slice(1);
1588
- const rationale = classifyAtToken(bare);
1589
- if (rationale === null) continue;
1590
- const target = resolveSourceRelative2(sourceDir, bare);
1591
- const dedupKey = target.toLowerCase();
1592
- if (seenReferences.has(dedupKey)) continue;
1593
- seenReferences.add(dedupKey);
1594
- const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
1595
- const line = lineFor(lineStarts, captureOffset);
1596
- ctx.emitSignal({
1597
- source: ctx.node.path,
1598
- scope: "body",
1599
- range: { start: captureOffset, end: captureOffset + original.length, line },
1600
- raw: original,
1601
- candidates: [
1602
- {
1603
- extractorId: ID4,
1604
- kind: "references",
1605
- target,
1606
- // 0.85: strong file signal (path prefix or known extension),
1607
- // one degree of inference (the runtime resolves the path).
1608
- confidence: 0.85,
1609
- rationale,
1610
- trigger: {
1611
- originalTrigger: original,
1612
- normalizedTrigger: target
1613
- }
1614
- }
1615
- ]
1616
- });
1617
- }
1618
- }
1619
- };
1620
- function classifyAtToken(bare) {
1621
- if (bare.startsWith("/")) return null;
1622
- if (bare.startsWith("./") || bare.startsWith("../")) return "relative path prefix";
1623
- if (FILE_EXT_RE2.test(bare)) return "known file extension";
1624
- return null;
1625
- }
1626
- function resolveSourceRelative2(sourceDir, bare) {
1627
- const joined = sourceDir === "." ? bare : `${sourceDir}/${bare}`;
1628
- return pathPosix2.normalize(joined);
1629
- }
1630
-
1631
1512
  // plugins/codex/extractors/dollar-skill/index.ts
1632
- var ID5 = "dollar-skill";
1513
+ var ID3 = "dollar-skill";
1633
1514
  var DOLLAR_RE = /(?<![A-Za-z0-9_$])(\$[a-z][a-z0-9_-]*)/g;
1634
1515
  var dollarSkillExtractor = {
1635
- id: ID5,
1636
- pluginId: OPENAI_PLUGIN_ID,
1516
+ id: ID3,
1517
+ pluginId: CODEX_PLUGIN_ID,
1637
1518
  kind: "extractor",
1638
1519
  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.",
1639
1520
  scope: "body",
@@ -1659,7 +1540,7 @@ var dollarSkillExtractor = {
1659
1540
  raw: original,
1660
1541
  candidates: [
1661
1542
  {
1662
- extractorId: ID5,
1543
+ extractorId: ID3,
1663
1544
  kind: "invokes",
1664
1545
  target: original,
1665
1546
  // 0.8: clean `$skill` match after code-block strip. The
@@ -1679,6 +1560,186 @@ var dollarSkillExtractor = {
1679
1560
  }
1680
1561
  };
1681
1562
 
1563
+ // plugins/opencode/providers/opencode/schemas/agent.schema.json
1564
+ var agent_schema_default3 = {
1565
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1566
+ $id: "https://skill-map.ai/providers/opencode/v1/frontmatter/agent.schema.json",
1567
+ title: "FrontmatterOpenCodeAgent",
1568
+ description: "Frontmatter shape for nodes classified as `agent` by the OpenCode Provider. OpenCode agents live as markdown files under `.opencode/agent/<name>.md` (https://opencode.ai/docs/agents); the markdown body is the agent prompt. There is NO `name` field, the filename stem is the handle. Field set mirrors OpenCode's documented agent frontmatter; `additionalProperties: true` lets any other inherited key flow through unchanged.",
1569
+ allOf: [
1570
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
1571
+ ],
1572
+ type: "object",
1573
+ additionalProperties: true,
1574
+ required: ["description"],
1575
+ properties: {
1576
+ description: {
1577
+ type: "string",
1578
+ description: "What the agent does and when to use it. OpenCode surfaces this when selecting a subagent; skill-map mirrors it in the card. Required: a description-less agent cannot be meaningfully selected."
1579
+ },
1580
+ mode: {
1581
+ type: "string",
1582
+ enum: ["all", "primary", "subagent"],
1583
+ description: "How the agent runs: `primary` (a top-level agent the user can switch to), `subagent` (invoked by another agent via the `task` tool), or `all`. Inherits OpenCode's default when omitted."
1584
+ },
1585
+ model: {
1586
+ type: "string",
1587
+ description: "Model the agent runs against, in OpenCode's `provider/model` form (e.g. `anthropic/claude-opus-4-8`). Inherits the parent's model when omitted."
1588
+ },
1589
+ permission: {
1590
+ type: "object",
1591
+ additionalProperties: true,
1592
+ description: "Per-tool permission policy. Each documented tool key maps to `allow`, `ask`, or `deny`; `bash` may instead be a map of command globs to policies. Any tool omitted inherits OpenCode's default.",
1593
+ properties: {
1594
+ edit: { type: "string", enum: ["allow", "ask", "deny"] },
1595
+ bash: {
1596
+ description: "Either a blanket policy or a per-command-glob map.",
1597
+ oneOf: [
1598
+ { type: "string", enum: ["allow", "ask", "deny"] },
1599
+ {
1600
+ type: "object",
1601
+ additionalProperties: { type: "string", enum: ["allow", "ask", "deny"] }
1602
+ }
1603
+ ]
1604
+ },
1605
+ webfetch: { type: "string", enum: ["allow", "ask", "deny"] }
1606
+ }
1607
+ }
1608
+ }
1609
+ };
1610
+
1611
+ // plugins/opencode/providers/opencode/schemas/command.schema.json
1612
+ var command_schema_default2 = {
1613
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1614
+ $id: "https://skill-map.ai/providers/opencode/v1/frontmatter/command.schema.json",
1615
+ title: "FrontmatterOpenCodeCommand",
1616
+ description: "Frontmatter shape for nodes classified as `command` by the OpenCode Provider. OpenCode custom commands live as markdown files under `.opencode/commands/<name>.md` (https://opencode.ai/docs/commands); the markdown body is the command template and the filename stem is the command name (invoked `/<name>`). No required fields beyond the base (OpenCode treats the frontmatter as optional metadata); `additionalProperties: true` lets inherited keys flow through unchanged.",
1617
+ allOf: [
1618
+ { $ref: "https://skill-map.ai/spec/v0/frontmatter/base.schema.json" }
1619
+ ],
1620
+ type: "object",
1621
+ additionalProperties: true,
1622
+ properties: {
1623
+ description: {
1624
+ type: "string",
1625
+ description: "Brief explanation of the command's purpose, shown in OpenCode's slash-command picker."
1626
+ },
1627
+ agent: {
1628
+ type: "string",
1629
+ description: "Optional agent this command runs under (matches an `.opencode/agent/<name>` handle)."
1630
+ },
1631
+ model: {
1632
+ type: "string",
1633
+ description: "Optional model override for this command, in OpenCode's `provider/model` form."
1634
+ }
1635
+ }
1636
+ };
1637
+
1638
+ // plugins/opencode/providers/opencode/index.ts
1639
+ var OPENCODE_RESERVED_SLASH_VERBS = [
1640
+ // Inherited open-standard base (universal cross-agent slash commands).
1641
+ ...COMMONS_RESERVED_NAMES["skill"] ?? [],
1642
+ // OpenCode-specific built-in verbs (`/help` already lives in the base).
1643
+ "init",
1644
+ "redo",
1645
+ "share",
1646
+ "undo"
1647
+ ];
1648
+ var opencodeProvider = {
1649
+ id: "opencode",
1650
+ pluginId: OPENCODE_PLUGIN_ID,
1651
+ kind: "provider",
1652
+ description: "Classifies `.opencode/agent/*.md` as OpenCode agents and `.opencode/commands/*.md` as OpenCode commands (its own kinds), and skills under `.opencode/skills/`, `.claude/skills/`, and `.agents/skills/` (the project-level homes OpenCode reads).",
1653
+ // Provider identity for the active-lens dropdown, the topbar lens chip, and
1654
+ // the per-node provider chip. OpenCode has no strong model vendor (it is
1655
+ // model-agnostic), so the label is the bare product name, NOT a possessive
1656
+ // `<Vendor>'s <product>` like the other vendor lenses. Cyan, distinct from
1657
+ // codex green, antigravity violet, and the agent-skills slate.
1658
+ presentation: {
1659
+ label: "OpenCode",
1660
+ color: "#0891b2",
1661
+ colorDark: "#22d3ee",
1662
+ // OpenCode invokes its custom commands with `/<name>`; the palette paints
1663
+ // this as the `invokes` edge glyph under the opencode lens. (Skills are
1664
+ // loaded by OpenCode's native `skill` tool, not a body sigil.)
1665
+ invocationSigil: "/"
1666
+ },
1667
+ // Auto-detect marker: a `.opencode/` directory marks an OpenCode project.
1668
+ detect: { markers: [".opencode"] },
1669
+ // Vendor lens: gated to the active lens. OpenCode only resolves its own
1670
+ // territory (plus the Claude-compat / open-standard skill homes it reads).
1671
+ // Gating keeps the walker from claiming OpenCode territory under another
1672
+ // lens, where the OpenCode runtime would never resolve it anyway, and keeps
1673
+ // the `.claude/skills/` claim here from colliding with the claude lens.
1674
+ gatedByActiveLens: true,
1675
+ // Beta: ships enabled, auto-detects `.opencode/`, selectable as the active
1676
+ // lens, with a maturity badge. Same posture as codex / antigravity, since
1677
+ // the lens is freshly landed. Promote to `stable` (drop the field) once it
1678
+ // has real-world mileage.
1679
+ stability: "beta",
1680
+ // Single read rule: all three families are `.md` + YAML frontmatter, so one
1681
+ // `COMMONS_READ` pass suffices (no multi-rule `read` like codex, which mixes
1682
+ // `.toml`). `classify()` below routes each path to its kind.
1683
+ read: COMMONS_READ,
1684
+ kinds: {
1685
+ agent: {
1686
+ schema: "./schemas/agent.schema.json",
1687
+ schemaJson: agent_schema_default3,
1688
+ ui: {
1689
+ label: "Agents",
1690
+ // Cross-provider agent vocabulary: same blue as Claude's `agent` kind,
1691
+ // so an agent paints the same regardless of which lens sourced it.
1692
+ color: "#3b82f6",
1693
+ colorDark: "#60a5fa",
1694
+ icon: { kind: "pi", id: "pi-user" }
1695
+ },
1696
+ // No `name` frontmatter field: the filename stem
1697
+ // (`.opencode/agent/<name>.md`) is the handle.
1698
+ identifiers: ["filename-basename"]
1699
+ },
1700
+ command: {
1701
+ schema: "./schemas/command.schema.json",
1702
+ schemaJson: command_schema_default2,
1703
+ ui: {
1704
+ label: "Commands",
1705
+ // Cross-provider command vocabulary: same amber (and icon) as Claude's
1706
+ // `command` kind and Antigravity's `workflow` kind.
1707
+ color: "#f59e0b",
1708
+ colorDark: "#fbbf24",
1709
+ icon: { kind: "svg", path: "M4 17 L10 11 L4 5 M12 19 L20 19" }
1710
+ },
1711
+ // The filename stem is the command name (`/<name>`); no `name` field.
1712
+ identifiers: ["filename-basename"]
1713
+ },
1714
+ // Open-standard `skill` kind, inherited from `agent-skills` by manifest
1715
+ // composition (same schema + green visuals every adopter shares).
1716
+ // `classify()` routes the three skill homes OpenCode reads into this kind.
1717
+ ...COMMONS_KINDS
1718
+ },
1719
+ // `/<name>` slash invocations resolve to commands ONLY: OpenCode reserves the
1720
+ // slash for its custom commands, and loads skills via its native `skill` tool
1721
+ // (no `/`-invocation), so `invokes` does NOT target `skill` here (unlike
1722
+ // claude). Overrides the open-standard default (`invokes: ['skill']`). The
1723
+ // `core/slash-command` extractor is authorised under the opencode lens (its
1724
+ // precondition lists `opencode`) so `/deploy` in a body emits the link.
1725
+ resolution: { invokes: ["command"] },
1726
+ // Reserved built-in slash commands, applied to the `command` kind (the only
1727
+ // `/`-invocable OpenCode kind: skills are tool-loaded, not slash-invoked, so
1728
+ // a skill named after a built-in cannot be shadowed through the slash
1729
+ // channel and is deliberately NOT reserved).
1730
+ reservedNames: {
1731
+ command: OPENCODE_RESERVED_SLASH_VERBS
1732
+ },
1733
+ classify(path) {
1734
+ const lower = path.toLowerCase();
1735
+ if (/^\.opencode\/agent\/[^/]+\.md$/.test(lower)) return "agent";
1736
+ if (/^\.opencode\/commands\/[^/]+\.md$/.test(lower)) return "command";
1737
+ if (/^\.opencode\/skills\/[^/]+\/skill\.md$/.test(lower)) return "skill";
1738
+ if (/^\.claude\/skills\/[^/]+\/skill\.md$/.test(lower)) return "skill";
1739
+ return classifyCommonsPath(path);
1740
+ }
1741
+ };
1742
+
1682
1743
  // plugins/core/providers/core-markdown/schemas/markdown.schema.json
1683
1744
  var markdown_schema_default = {
1684
1745
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -1765,12 +1826,68 @@ var coreMarkdownProvider = {
1765
1826
  }
1766
1827
  };
1767
1828
 
1829
+ // plugins/core/extractors/at-file/index.ts
1830
+ import { posix as pathPosix2 } from "path";
1831
+ var ID4 = "at-file";
1832
+ var atFileExtractor = {
1833
+ id: ID4,
1834
+ pluginId: CORE_PLUGIN_ID,
1835
+ kind: "extractor",
1836
+ 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.",
1837
+ scope: "body",
1838
+ // The lenses whose runtime reads `@` as a file-path picker. Codex (`@` is a
1839
+ // file picker, `$` invokes skills), Antigravity (documented `@filename` file
1840
+ // refs), and Claude (`@file.md` file ref; its bare-handle MENTION half lives
1841
+ // in `claude/at-directive`). NOT gated under `agent-skills` / `markdown`,
1842
+ // whose runtimes read `@` as nothing (no phantom edges).
1843
+ precondition: { provider: ["claude", "codex", "antigravity"] },
1844
+ extract(ctx) {
1845
+ const seenReferences = /* @__PURE__ */ new Set();
1846
+ const body = stripCodeAndHtml(ctx.body);
1847
+ const lineStarts = computeLineStarts(body);
1848
+ const sourceDir = pathPosix2.dirname(ctx.node.path);
1849
+ for (const match of body.matchAll(AT_TOKEN_RE)) {
1850
+ const original = match[1];
1851
+ const bare = original.slice(1);
1852
+ const rationale = classifyAtFileToken(bare);
1853
+ if (rationale === null) continue;
1854
+ const target = resolveAtFileTarget(sourceDir, bare);
1855
+ const dedupKey = target.toLowerCase();
1856
+ if (seenReferences.has(dedupKey)) continue;
1857
+ seenReferences.add(dedupKey);
1858
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
1859
+ const line = lineFor(lineStarts, captureOffset);
1860
+ ctx.emitSignal({
1861
+ source: ctx.node.path,
1862
+ scope: "body",
1863
+ range: { start: captureOffset, end: captureOffset + original.length, line },
1864
+ raw: original,
1865
+ candidates: [
1866
+ {
1867
+ extractorId: ID4,
1868
+ kind: "references",
1869
+ target,
1870
+ // 0.85: strong file signal (path prefix or known extension),
1871
+ // one degree of inference (the runtime resolves the path).
1872
+ confidence: 0.85,
1873
+ rationale,
1874
+ trigger: {
1875
+ originalTrigger: original,
1876
+ normalizedTrigger: target
1877
+ }
1878
+ }
1879
+ ]
1880
+ });
1881
+ }
1882
+ }
1883
+ };
1884
+
1768
1885
  // plugins/core/extractors/backtick-path/index.ts
1769
1886
  import { posix as pathPosix3 } from "path";
1770
- var ID6 = "backtick-path";
1887
+ var ID5 = "backtick-path";
1771
1888
  var PATH_RE = /(?<![\w/:.-])(?:\.{1,2}\/)?[\w][\w.-]*(?:\/[\w.-]+)*\.md\b(?![\w/])/g;
1772
1889
  var backtickPathExtractor = {
1773
- id: ID6,
1890
+ id: ID5,
1774
1891
  pluginId: CORE_PLUGIN_ID,
1775
1892
  kind: "extractor",
1776
1893
  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.",
@@ -1795,7 +1912,7 @@ var backtickPathExtractor = {
1795
1912
  raw: original,
1796
1913
  candidates: [
1797
1914
  {
1798
- extractorId: ID6,
1915
+ extractorId: ID5,
1799
1916
  kind: "points",
1800
1917
  target: resolved,
1801
1918
  // 0.85: a strong file signal with one degree of inference,
@@ -1824,7 +1941,7 @@ function resolveTarget(sourceDir, raw) {
1824
1941
  }
1825
1942
 
1826
1943
  // plugins/core/extractors/external-url-counter/index.ts
1827
- var ID7 = "external-url-counter";
1944
+ var ID6 = "external-url-counter";
1828
1945
  var count2 = {
1829
1946
  slot: "card.footer.left",
1830
1947
  icon: "pi-link",
@@ -1844,7 +1961,7 @@ var settings = {
1844
1961
  var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
1845
1962
  var TRAILING_PUNCT = /[.,;:!?]+$/;
1846
1963
  var externalUrlCounterExtractor = {
1847
- id: ID7,
1964
+ id: ID6,
1848
1965
  pluginId: CORE_PLUGIN_ID,
1849
1966
  kind: "extractor",
1850
1967
  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.",
@@ -1896,7 +2013,7 @@ var externalUrlCounterExtractor = {
1896
2013
  raw: original,
1897
2014
  candidates: [
1898
2015
  {
1899
- extractorId: ID7,
2016
+ extractorId: ID6,
1900
2017
  kind: "references",
1901
2018
  target: normalized.href,
1902
2019
  confidence: 0.3,
@@ -1938,11 +2055,11 @@ function normalizeUrl(raw) {
1938
2055
 
1939
2056
  // plugins/core/extractors/markdown-link/index.ts
1940
2057
  import { posix as pathPosix4 } from "path";
1941
- var ID8 = "markdown-link";
2058
+ var ID7 = "markdown-link";
1942
2059
  var LINK_RE = /(?<!!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
1943
2060
  var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
1944
2061
  var markdownLinkExtractor = {
1945
- id: ID8,
2062
+ id: ID7,
1946
2063
  pluginId: CORE_PLUGIN_ID,
1947
2064
  kind: "extractor",
1948
2065
  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`.",
@@ -1967,7 +2084,7 @@ var markdownLinkExtractor = {
1967
2084
  raw: match[0],
1968
2085
  candidates: [
1969
2086
  {
1970
- extractorId: ID8,
2087
+ extractorId: ID7,
1971
2088
  kind: "references",
1972
2089
  target: resolved,
1973
2090
  // 0.95: the `[text](path)` syntax is unambiguous (the spec's
@@ -2002,10 +2119,10 @@ function resolveTarget2(sourceDir, raw) {
2002
2119
  }
2003
2120
 
2004
2121
  // plugins/core/extractors/mcp-tools/index.ts
2005
- var ID9 = "mcp-tools";
2122
+ var ID8 = "mcp-tools";
2006
2123
  var MCP_PATTERN = /^mcp__([a-z0-9][a-z0-9_-]*)__[a-z0-9_-]+$/i;
2007
2124
  var mcpToolsExtractor = {
2008
- id: ID9,
2125
+ id: ID8,
2009
2126
  pluginId: CORE_PLUGIN_ID,
2010
2127
  kind: "extractor",
2011
2128
  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.",
@@ -2036,7 +2153,7 @@ var mcpToolsExtractor = {
2036
2153
  raw: `mcp__${server}__*`,
2037
2154
  candidates: [
2038
2155
  {
2039
- extractorId: ID9,
2156
+ extractorId: ID8,
2040
2157
  kind: "references",
2041
2158
  target: mcpPath,
2042
2159
  confidence: 0.85,
@@ -2066,6 +2183,71 @@ function collectMcpServers(tools) {
2066
2183
  return out;
2067
2184
  }
2068
2185
 
2186
+ // plugins/core/extractors/slash-command/index.ts
2187
+ var ID9 = "slash-command";
2188
+ var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
2189
+ var slashCommandExtractor = {
2190
+ id: ID9,
2191
+ pluginId: CORE_PLUGIN_ID,
2192
+ kind: "extractor",
2193
+ 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, Antigravity, and OpenCode. Example: `/deploy` in the body draws an arrow to the `deploy` command.",
2194
+ scope: "body",
2195
+ // Also authorised under the antigravity lens, which shares the `/command`
2196
+ // grammar: a workflow / skill / AGENTS.md body's `/name` tokens resolve to
2197
+ // BOTH skills and workflows (`invokes: ['skill', 'workflow']`), since
2198
+ // Antigravity invokes either by the same slash. NOT gated under codex:
2199
+ // OpenAI Codex reserves `/` for its OWN built-in commands (`/model`,
2200
+ // `/init`, ...) and invokes user skills with `$` instead (parsed by the
2201
+ // codex `dollar-skill` extractor). A lens that declares no `invokes`
2202
+ // resolution leaves the signals unresolved (no spurious edges).
2203
+ //
2204
+ // Also authorised under the opencode lens: OpenCode invokes its custom
2205
+ // `.opencode/commands/<name>.md` with `/<name>` (the opencode provider maps
2206
+ // `invokes: ['command']`, since OpenCode loads skills via its native `skill`
2207
+ // tool rather than the slash channel). Additive: claude / antigravity runs
2208
+ // are unchanged.
2209
+ precondition: { provider: ["claude", "antigravity", "opencode"] },
2210
+ extract(ctx) {
2211
+ const seen = /* @__PURE__ */ new Set();
2212
+ const body = stripCodeAndHtml(ctx.body);
2213
+ const lineStarts = computeLineStarts(body);
2214
+ for (const match of body.matchAll(SLASH_RE)) {
2215
+ const original = match[1];
2216
+ const endIdx = (match.index ?? 0) + match[0].length;
2217
+ const nextChar = body[endIdx];
2218
+ if (nextChar && /[A-Za-z0-9_/-]/.test(nextChar)) continue;
2219
+ const normalized = normalizeTrigger(original);
2220
+ if (seen.has(normalized)) continue;
2221
+ seen.add(normalized);
2222
+ const captureOffset = (match.index ?? 0) + match[0].indexOf(original);
2223
+ const line = lineFor(lineStarts, captureOffset);
2224
+ ctx.emitSignal({
2225
+ source: ctx.node.path,
2226
+ scope: "body",
2227
+ range: { start: captureOffset, end: captureOffset + original.length, line },
2228
+ raw: original,
2229
+ candidates: [
2230
+ {
2231
+ extractorId: ID9,
2232
+ kind: "invokes",
2233
+ target: original,
2234
+ // 0.8: clean `/command` match after code-block strip. The
2235
+ // post-match path guard above filters URL / file-path noise,
2236
+ // so a hit is unambiguous syntax. Resolution against the
2237
+ // live skill / command catalog happens downstream.
2238
+ confidence: 0.8,
2239
+ rationale: "unambiguous slash syntax post code-block strip",
2240
+ trigger: {
2241
+ originalTrigger: original,
2242
+ normalizedTrigger: normalized
2243
+ }
2244
+ }
2245
+ ]
2246
+ });
2247
+ }
2248
+ }
2249
+ };
2250
+
2069
2251
  // plugins/core/analyzers/annotation-field-unknown/index.ts
2070
2252
  import { readFileSync } from "fs";
2071
2253
  import { dirname, resolve } from "path";
@@ -4651,18 +4833,19 @@ var updateCheckHook = {
4651
4833
  // plugins/built-ins.ts
4652
4834
  var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: VERSION };
4653
4835
  var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: VERSION };
4654
- var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: VERSION };
4655
4836
  var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "claude", version: VERSION };
4656
4837
  var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: VERSION };
4657
4838
  var codexProvider2 = { ...codexProvider, pluginId: "codex", version: VERSION };
4658
- var atFileExtractor2 = { ...atFileExtractor, pluginId: "codex", version: VERSION };
4659
4839
  var dollarSkillExtractor2 = { ...dollarSkillExtractor, pluginId: "codex", version: VERSION };
4840
+ var opencodeProvider2 = { ...opencodeProvider, pluginId: "opencode", version: VERSION };
4660
4841
  var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: VERSION };
4661
4842
  var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: VERSION };
4843
+ var atFileExtractor2 = { ...atFileExtractor, pluginId: "core", version: VERSION };
4662
4844
  var backtickPathExtractor2 = { ...backtickPathExtractor, pluginId: "core", version: VERSION };
4663
4845
  var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: VERSION };
4664
4846
  var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: VERSION };
4665
4847
  var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: VERSION };
4848
+ var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "core", version: VERSION };
4666
4849
  var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: VERSION };
4667
4850
  var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: VERSION };
4668
4851
  var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: VERSION };
@@ -4691,7 +4874,6 @@ var builtInPlugins = [
4691
4874
  extensions: [
4692
4875
  claudeProvider2,
4693
4876
  atDirectiveExtractor2,
4694
- slashCommandExtractor2,
4695
4877
  toolsCounterExtractor2
4696
4878
  ]
4697
4879
  },
@@ -4707,10 +4889,16 @@ var builtInPlugins = [
4707
4889
  description: "OpenAI Codex CLI platform integration. Classifies TOML sub-agent definitions under `.codex/agents/*.toml`.",
4708
4890
  extensions: [
4709
4891
  codexProvider2,
4710
- atFileExtractor2,
4711
4892
  dollarSkillExtractor2
4712
4893
  ]
4713
4894
  },
4895
+ {
4896
+ id: "opencode",
4897
+ description: "OpenCode CLI platform integration (open-source, model-agnostic terminal coding agent). Classifies OpenCode agents under `.opencode/agent/*.md` and commands under `.opencode/commands/*.md` (its own kinds), and adopts the skill homes OpenCode reads (`.opencode/skills/`, `.claude/skills/`, `.agents/skills/`); contributes the OpenCode runtime identity and reserved built-in names.",
4898
+ extensions: [
4899
+ opencodeProvider2
4900
+ ]
4901
+ },
4714
4902
  {
4715
4903
  id: "agent-skills",
4716
4904
  description: "Open-standard Agent Skills layout. Classifies skills under the vendor-neutral path `.agents/skills/<name>/SKILL.md` (adopted by Anthropic, OpenAI, Google). See agentskills.io.",
@@ -4723,10 +4911,12 @@ var builtInPlugins = [
4723
4911
  description: "Core extensions shared across providers: parsers, extractors, analyzers, actions, hooks, formatters, and the universal `.md` fallback provider.",
4724
4912
  extensions: [
4725
4913
  coreMarkdownProvider2,
4914
+ atFileExtractor2,
4726
4915
  backtickPathExtractor2,
4727
4916
  externalUrlCounterExtractor2,
4728
4917
  markdownLinkExtractor2,
4729
4918
  mcpToolsExtractor2,
4919
+ slashCommandExtractor2,
4730
4920
  annotationFieldUnknownAnalyzer2,
4731
4921
  annotationOrphanAnalyzer2,
4732
4922
  annotationStaleAnalyzer2,
@@ -5447,6 +5637,7 @@ var BUILT_IN_PLUGIN_IDS = /* @__PURE__ */ new Set([
5447
5637
  "claude",
5448
5638
  "antigravity",
5449
5639
  "codex",
5640
+ "opencode",
5450
5641
  "agent-skills",
5451
5642
  "core"
5452
5643
  ]);
@@ -16538,6 +16729,8 @@ var ORCHESTRATOR_TEXTS = {
16538
16729
  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.",
16539
16730
  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.",
16540
16731
  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.",
16732
+ 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.",
16733
+ 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.",
16541
16734
  extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
16542
16735
  extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
16543
16736
  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.',
@@ -17223,6 +17416,11 @@ var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
17223
17416
  "frontmatter-malformed",
17224
17417
  "frontmatter-parse-error"
17225
17418
  ]);
17419
+ var BACKTICK_ISSUE_ID = "backtick-unbalanced";
17420
+ var CACHED_KERNEL_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
17421
+ ...FRONTMATTER_ISSUE_ANALYZERS,
17422
+ BACKTICK_ISSUE_ID
17423
+ ]);
17226
17424
 
17227
17425
  // kernel/orchestrator/cache.ts
17228
17426
  function indexPriorSnapshot(prior) {
@@ -17252,7 +17450,7 @@ function indexPriorLinks(links, byOriginating) {
17252
17450
  }
17253
17451
  function indexPriorFrontmatterIssues(issues, byNode) {
17254
17452
  for (const issue of issues) {
17255
- if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
17453
+ if (!CACHED_KERNEL_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
17256
17454
  if (issue.nodeIds.length !== 1) continue;
17257
17455
  const path = issue.nodeIds[0];
17258
17456
  const list = byNode.get(path);
@@ -17996,6 +18194,21 @@ function malformedMessage(hint, path) {
17996
18194
  }
17997
18195
  }
17998
18196
 
18197
+ // kernel/orchestrator/body-syntax.ts
18198
+ function detectUnclosedBacktick(body, path, strict) {
18199
+ const imbalance = findBacktickImbalance(body);
18200
+ if (!imbalance) return null;
18201
+ const template = imbalance.kind === "fence" ? ORCHESTRATOR_TEXTS.bodyBacktickUnclosedFence : ORCHESTRATOR_TEXTS.bodyBacktickUnclosedInline;
18202
+ return {
18203
+ analyzerId: BACKTICK_ISSUE_ID,
18204
+ severity: strict ? "error" : "warn",
18205
+ nodeIds: [path],
18206
+ message: tx(template, { path, line: imbalance.line }),
18207
+ detail: imbalance.sourceLine,
18208
+ data: { kind: imbalance.kind, line: imbalance.line }
18209
+ };
18210
+ }
18211
+
17999
18212
  // kernel/orchestrator/node-build.ts
18000
18213
  function buildNode(args2) {
18001
18214
  const bytesFrontmatter = Buffer.byteLength(args2.frontmatterRaw, "utf8");
@@ -18122,6 +18335,9 @@ function relativePathFromRoots(absolutePath, roots) {
18122
18335
  }
18123
18336
  return absolutePath;
18124
18337
  }
18338
+ function pushIssue(list, issue) {
18339
+ if (issue) list.push(issue);
18340
+ }
18125
18341
  function buildFreshNodeAndValidateFrontmatter(opts) {
18126
18342
  const node = buildNode({
18127
18343
  path: opts.raw.path,
@@ -18149,19 +18365,27 @@ function buildFreshNodeAndValidateFrontmatter(opts) {
18149
18365
  }
18150
18366
  }
18151
18367
  if (opts.raw.frontmatterRaw.length > 0) {
18152
- const fmIssue = validateFrontmatter(
18153
- opts.providerFrontmatter,
18154
- opts.provider,
18155
- opts.kind,
18156
- opts.raw.frontmatter,
18157
- opts.raw.path,
18158
- opts.strict
18368
+ pushIssue(
18369
+ frontmatterIssues,
18370
+ validateFrontmatter(
18371
+ opts.providerFrontmatter,
18372
+ opts.provider,
18373
+ opts.kind,
18374
+ opts.raw.frontmatter,
18375
+ opts.raw.path,
18376
+ opts.strict
18377
+ )
18159
18378
  );
18160
- if (fmIssue) frontmatterIssues.push(fmIssue);
18161
18379
  } else {
18162
- const malformed = detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict);
18163
- if (malformed) frontmatterIssues.push(malformed);
18380
+ pushIssue(
18381
+ frontmatterIssues,
18382
+ detectMalformedFrontmatter(opts.raw.body, opts.raw.path, opts.strict)
18383
+ );
18164
18384
  }
18385
+ pushIssue(
18386
+ frontmatterIssues,
18387
+ detectUnclosedBacktick(opts.raw.body, opts.raw.path, opts.strict)
18388
+ );
18165
18389
  return { node, frontmatterIssues };
18166
18390
  }
18167
18391
 
@@ -22153,6 +22377,7 @@ var BUILT_IN_PLUGIN_PRESENTATION_ORDER = [
22153
22377
  "claude",
22154
22378
  "antigravity",
22155
22379
  "codex",
22380
+ "opencode",
22156
22381
  "agent-skills"
22157
22382
  ];
22158
22383
  function sortPluginsForPresentation(plugins) {
@@ -32575,15 +32800,20 @@ var TUTORIAL_TEXTS = {
32575
32800
  writtenLabelEs: "Espa\xF1ol",
32576
32801
  // Destination-provider prompt (interactive stdin, no `--for`). Header
32577
32802
  // uses a yellow `?` glyph; options are a numbered list of the provider's
32578
- // vendor name (for a provider with an `aka`, the aka vendor leads and the
32579
- // provider label follows in parentheses), with a `(default)` marker on
32580
- // the first option (Claude). The destination folder is deliberately NOT
32581
- // shown: several providers share `.agents/skills`, so the folder does not
32582
- // identify the lens. The input line accepts a number, a provider id, or
32583
- // an empty answer (which takes the default).
32803
+ // label (for a provider with an `aka`, the standard label leads and the
32804
+ // supporting vendors follow in parentheses, closed by `akaOthers` to mark
32805
+ // the set as open), with a `(default)` marker on the first option (Claude).
32806
+ // The destination folder is deliberately NOT shown: several providers share
32807
+ // `.agents/skills`, so the folder does not identify the lens. The input line
32808
+ // accepts a number, a provider id, or an empty answer (which takes the
32809
+ // default).
32584
32810
  promptHeader: "{{glyph}} Which agent should host the tutorial skill?",
32585
32811
  promptOption: " {{index}}) {{label}}{{marker}}",
32586
32812
  promptDefaultMarker: " (default)",
32813
+ // Trailing token appended to the `aka` vendor list in the open-lens label
32814
+ // ("... (Google's Antigravity, others)"), signalling the open standard is
32815
+ // not tied to a single vendor and more will support it.
32816
+ akaOthers: "others",
32587
32817
  promptInput: " Enter the number or provider id [default {{index}}]: ",
32588
32818
  // Prompt answer matched neither an index nor an id. Goes to stderr,
32589
32819
  // exit code 2. Mirrors the error shape: glyph + headline + dim hint.
@@ -32833,7 +33063,7 @@ function listScaffoldTargets(includeExperimental = false) {
32833
33063
  return out;
32834
33064
  }
32835
33065
  function labelWithAka(target) {
32836
- return target.aka.length > 0 ? `${target.aka.join(", ")} (${target.label})` : target.label;
33066
+ return target.aka.length > 0 ? `${target.label} (${[...target.aka, TUTORIAL_TEXTS.akaOthers].join(", ")})` : target.label;
32837
33067
  }
32838
33068
  function renderTargetLines(targets, def, glyph) {
32839
33069
  const lines = [tx(TUTORIAL_TEXTS.promptHeader, { glyph })];
@@ -33128,4 +33358,4 @@ function resolveBareDefault() {
33128
33358
  process.exit(ExitCode.Error);
33129
33359
  }
33130
33360
  //# sourceMappingURL=cli.js.map
33131
- //# debugId=ca8113af-a7ac-5cd7-b45c-03a5974bca86
33361
+ //# debugId=2c7d4927-4422-59d2-a37a-59fed3a2ec80