@skill-map/cli 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -470,7 +470,13 @@ var claudeProvider = {
470
470
  color: "#3b82f6",
471
471
  colorDark: "#60a5fa",
472
472
  icon: { kind: "pi", id: "pi-user" }
473
- }
473
+ },
474
+ // `frontmatter.name` is the documented canonical identifier
475
+ // (https://code.claude.com/docs/en/agents.md); `filename-basename`
476
+ // is a graceful fallback for agents authored without an explicit
477
+ // `name:` field, the file at `.claude/agents/<id>.md` resolves
478
+ // `@<id>` even when frontmatter is partial.
479
+ identifiers: ["frontmatter.name", "filename-basename"]
474
480
  },
475
481
  command: {
476
482
  schema: "./schemas/command.schema.json",
@@ -483,7 +489,12 @@ var claudeProvider = {
483
489
  kind: "svg",
484
490
  path: "M4 17 L10 11 L4 5 M12 19 L20 19"
485
491
  }
486
- }
492
+ },
493
+ // Commands share Anthropic's documented merger with skills
494
+ // (skills.md), so the same dual-source applies: explicit
495
+ // `frontmatter.name` first, filename basename fallback for
496
+ // `.claude/commands/<name>.md`.
497
+ identifiers: ["frontmatter.name", "filename-basename"]
487
498
  },
488
499
  skill: {
489
500
  schema: "./schemas/skill.schema.json",
@@ -493,7 +504,13 @@ var claudeProvider = {
493
504
  color: "#10b981",
494
505
  colorDark: "#34d399",
495
506
  icon: { kind: "pi", id: "pi-bolt" }
496
- }
507
+ },
508
+ // Anthropic explicitly documents the directory name as the
509
+ // canonical identifier for skills (skills.md: "The directory name
510
+ // becomes the command you type"). `frontmatter.name` is an
511
+ // optional override; when absent, the dirname between
512
+ // `.claude/skills/` and `/SKILL.md` is the invocation handle.
513
+ identifiers: ["frontmatter.name", "dirname"]
497
514
  },
498
515
  /**
499
516
  * Phase 5 of the active-lens migration: MCP servers surface as
@@ -518,9 +535,24 @@ var claudeProvider = {
518
535
  color: "#8b5cf6",
519
536
  colorDark: "#a78bfa",
520
537
  icon: { kind: "pi", id: "pi-server" }
521
- }
538
+ },
539
+ // MCP nodes are virtual (`mcp://<server>`), no filesystem path
540
+ // and no Claude invocation surface (mentions / slashes don't
541
+ // resolve to MCPs). `frontmatter.name` is set by the
542
+ // `core/mcp-tools` extractor at emission so the index stays
543
+ // consistent if a future link.kind ever targets MCPs by name.
544
+ identifiers: ["frontmatter.name"]
522
545
  }
523
546
  },
547
+ // Strict resolution matrix consumed by `liftResolvedLinkConfidence`:
548
+ // an `@<name>` mention resolves to an agent only; a `/<name>` slash
549
+ // resolves to a command OR skill (Anthropic merged the two surfaces
550
+ // per skills.md). MCP nodes are not invocable through either
551
+ // mention or slash, so they intentionally do not appear here.
552
+ resolution: {
553
+ mentions: ["agent"],
554
+ invokes: ["command", "skill"]
555
+ },
524
556
  // Auxiliary schemas the per-kind schemas $ref by $id. AJV needs them
525
557
  // registered via addSchema BEFORE the per-kind schemas compile, so the
526
558
  // validator builder pre-registers them. `skill-base.schema.json` is the
@@ -803,7 +835,8 @@ var geminiProvider = {
803
835
  color: "#3b82f6",
804
836
  colorDark: "#60a5fa",
805
837
  icon: { kind: "pi", id: "pi-user" }
806
- }
838
+ },
839
+ identifiers: ["frontmatter.name", "filename-basename"]
807
840
  },
808
841
  skill: {
809
842
  schema: "./schemas/skill.schema.json",
@@ -813,9 +846,20 @@ var geminiProvider = {
813
846
  color: "#10b981",
814
847
  colorDark: "#34d399",
815
848
  icon: { kind: "pi", id: "pi-bolt" }
816
- }
849
+ },
850
+ // Gemini skills live at `.gemini/skills/<name>/SKILL.md`; the
851
+ // dirname is the invocation handle per Google's docs
852
+ // (https://geminicli.com/docs/cli/creating-skills/).
853
+ identifiers: ["frontmatter.name", "dirname"]
817
854
  }
818
855
  },
856
+ // Gemini's invocation surfaces mirror Claude's at the kernel level
857
+ // (mentions for agents, slash invokes for skills). Commands are not
858
+ // a Gemini concept; only `skill` belongs to `invokes`.
859
+ resolution: {
860
+ mentions: ["agent"],
861
+ invokes: ["skill"]
862
+ },
819
863
  classify(path) {
820
864
  const lower = path.toLowerCase();
821
865
  if (lower.startsWith(".gemini/agents/")) return "agent";
@@ -894,9 +938,20 @@ var openaiProvider = {
894
938
  color: "#22c55e",
895
939
  colorDark: "#4ade80",
896
940
  icon: { kind: "pi", id: "pi-bolt" }
897
- }
941
+ },
942
+ // Codex sub-agents are referenced by file basename
943
+ // (`.codex/agents/<name>.toml`). `frontmatter.name` lives inside
944
+ // the TOML structured frontmatter when the author declared it
945
+ // explicitly.
946
+ identifiers: ["frontmatter.name", "filename-basename"]
898
947
  }
899
948
  },
949
+ // Codex's invocation surface is mention-style today (`@<name>`); slash
950
+ // invocation and skill nodes land in Phase 6b. Empty `invokes` keeps
951
+ // the contract narrow until skills arrive.
952
+ resolution: {
953
+ mentions: ["agent"]
954
+ },
900
955
  classify(path) {
901
956
  const lower = path.toLowerCase();
902
957
  if (lower.startsWith(".codex/agents/") && lower.endsWith(".toml")) return "agent";
@@ -935,9 +990,18 @@ var agentSkillsProvider = {
935
990
  color: "#10b981",
936
991
  colorDark: "#34d399",
937
992
  icon: { kind: "pi", id: "pi-bolt" }
938
- }
993
+ },
994
+ // Open-standard skills mirror Anthropic's: dirname between
995
+ // `.agents/skills/` and `/SKILL.md` is the canonical handle,
996
+ // `frontmatter.name` overrides when present.
997
+ identifiers: ["frontmatter.name", "dirname"]
939
998
  }
940
999
  },
1000
+ // The open standard documents slash-style invocation of skills; no
1001
+ // mention surface (no agents in this Provider's territory).
1002
+ resolution: {
1003
+ invokes: ["skill"]
1004
+ },
941
1005
  classify(path) {
942
1006
  if (/^\.agents\/skills\/[^/]+\/skill\.md$/.test(path.toLowerCase())) return "skill";
943
1007
  return null;
@@ -989,8 +1053,18 @@ var coreMarkdownProvider = {
989
1053
  path: "M14 2 H6 a2 2 0 0 0 -2 2 V20 a2 2 0 0 0 2 2 H18 a2 2 0 0 0 2 -2 V8 L14 2 M14 2 V8 H20 M16 13 H8 M16 17 H8 M10 9 H8"
990
1054
  }
991
1055
  }
1056
+ // No `identifiers`: markdown nodes are addressed by path, not by a
1057
+ // canonical name. The name index built by `liftResolvedLinkConfidence`
1058
+ // never sees markdown entries; resolution falls through to the
1059
+ // path-match rule only.
992
1060
  }
993
1061
  },
1062
+ // No `resolution`: `core/markdown` is the universal fallback Provider,
1063
+ // it does not declare an invocation surface of its own. Mentions /
1064
+ // slashes sourced from markdown bodies still resolve via OTHER
1065
+ // Providers' resolution maps (the lookup keys on the source node's
1066
+ // Provider id, not on `core/markdown`). Leaving the field absent keeps
1067
+ // the contract narrow.
994
1068
  classify() {
995
1069
  return "markdown";
996
1070
  }
@@ -1174,7 +1248,13 @@ var markdownLinkExtractor = {
1174
1248
  source: ctx.node.path,
1175
1249
  target: resolved,
1176
1250
  kind: "references",
1177
- confidence: 0.95,
1251
+ // 1.0: the `[text](path)` syntax is unambiguous. Markdown
1252
+ // explicitly designates an out-link via the brackets +
1253
+ // parentheses pair; there is no inference left to discount.
1254
+ // Whether the path resolves to a real node is a separate
1255
+ // concern (the `core/broken-ref` analyzer flags unresolved
1256
+ // targets), not a confidence question.
1257
+ confidence: 1,
1178
1258
  sources: [ID5],
1179
1259
  trigger: {
1180
1260
  originalTrigger: original,
@@ -3134,7 +3214,7 @@ var UPDATE_CHECK_TEXTS = {
3134
3214
  // package.json
3135
3215
  var package_default = {
3136
3216
  name: "@skill-map/cli",
3137
- version: "0.34.0",
3217
+ version: "0.35.0",
3138
3218
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
3139
3219
  license: "MIT",
3140
3220
  type: "module",
@@ -13583,7 +13663,7 @@ function originatingNodeOf(link2, priorNodePaths) {
13583
13663
  function computeCacheDecision(opts) {
13584
13664
  const applicableExtractors = opts.extractors.filter((ex) => {
13585
13665
  if (!matchesKindPrecondition(ex, opts.kind)) return false;
13586
- if (!matchesProviderPrecondition(ex, opts.provider, opts.activeProvider)) return false;
13666
+ if (!matchesProviderPrecondition(ex, opts.activeProvider)) return false;
13587
13667
  return true;
13588
13668
  });
13589
13669
  const applicableQualifiedIds = new Set(
@@ -13607,10 +13687,9 @@ function matchesKindPrecondition(ex, kind) {
13607
13687
  return kindOnly === kind;
13608
13688
  });
13609
13689
  }
13610
- function matchesProviderPrecondition(ex, nodeProvider, activeProvider) {
13690
+ function matchesProviderPrecondition(ex, activeProvider) {
13611
13691
  const providers = ex.precondition?.provider;
13612
13692
  if (!providers || providers.length === 0) return true;
13613
- if (!providers.includes(nodeProvider)) return false;
13614
13693
  if (activeProvider === null) return false;
13615
13694
  return providers.includes(activeProvider);
13616
13695
  }
@@ -13713,37 +13792,93 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
13713
13792
  return "obsolete";
13714
13793
  }
13715
13794
 
13716
- // kernel/orchestrator/lift-mention-confidence.ts
13717
- function liftMentionConfidence(links, nodes) {
13718
- if (!links.some((l) => l.kind === "mentions")) return;
13719
- const byPath3 = /* @__PURE__ */ new Set();
13720
- for (const node of nodes) byPath3.add(node.path);
13721
- const byNormalizedName = indexByNormalizedName2(nodes);
13795
+ // kernel/orchestrator/lift-resolved-link-confidence.ts
13796
+ import { posix as pathPosix4 } from "path";
13797
+ function liftResolvedLinkConfidence(links, nodes, ctx) {
13798
+ if (!links.some((l) => l.confidence < 1)) return;
13799
+ const indexes = buildIndexes(nodes, ctx);
13722
13800
  for (const link2 of links) {
13723
- if (link2.kind !== "mentions") continue;
13724
- if (isResolved2(link2, byPath3, byNormalizedName)) {
13801
+ if (link2.confidence < 1 && resolves(link2, indexes, ctx)) {
13725
13802
  link2.confidence = 1;
13726
13803
  }
13727
13804
  }
13728
13805
  }
13729
- function isResolved2(link2, byPath3, byNormalizedName) {
13730
- const normalized = link2.trigger?.normalizedTrigger;
13731
- if (normalized) {
13732
- const withoutSigil = normalized.replace(/^[/@]/, "").trim();
13733
- if (byNormalizedName.has(withoutSigil)) return true;
13734
- }
13735
- if (byPath3.has(link2.target)) return true;
13736
- return false;
13737
- }
13738
- function indexByNormalizedName2(nodes) {
13739
- const out = /* @__PURE__ */ new Map();
13806
+ function buildIndexes(nodes, ctx) {
13807
+ const byPath3 = /* @__PURE__ */ new Set();
13808
+ const byName = /* @__PURE__ */ new Map();
13809
+ const nodeByPath = /* @__PURE__ */ new Map();
13740
13810
  for (const node of nodes) {
13741
- const raw = node.frontmatter?.["name"];
13742
- const name = typeof raw === "string" ? raw : "";
13743
- if (!name) continue;
13744
- out.set(normalizeTrigger(name), true);
13811
+ byPath3.add(node.path);
13812
+ nodeByPath.set(node.path, node);
13813
+ indexNode(node, ctx, byName);
13814
+ }
13815
+ return { byPath: byPath3, byName, nodeByPath };
13816
+ }
13817
+ function resolves(link2, indexes, ctx) {
13818
+ if (indexes.byPath.has(link2.target)) return true;
13819
+ return resolvesByName(link2, indexes, ctx);
13820
+ }
13821
+ function resolvesByName(link2, indexes, ctx) {
13822
+ const stripped = stripTriggerSigil(link2.trigger?.normalizedTrigger);
13823
+ if (stripped === null) return false;
13824
+ const candidates = indexes.byName.get(stripped);
13825
+ if (!candidates?.length) return false;
13826
+ const allowedKinds = lookupAllowedKinds(link2, indexes, ctx);
13827
+ if (!allowedKinds?.length) return false;
13828
+ return candidates.some((c) => allowedKinds.includes(c.kind));
13829
+ }
13830
+ function lookupAllowedKinds(link2, indexes, ctx) {
13831
+ const sourceNode = indexes.nodeByPath.get(link2.source);
13832
+ if (!sourceNode) return void 0;
13833
+ return ctx.providerResolution.get(sourceNode.provider)?.[link2.kind];
13834
+ }
13835
+ function stripTriggerSigil(normalized) {
13836
+ if (!normalized) return null;
13837
+ const trimmed = normalized.replace(/^[/@]/, "").trim();
13838
+ return trimmed.length === 0 ? null : trimmed;
13839
+ }
13840
+ function indexNode(node, ctx, byName) {
13841
+ const kindDescriptor = ctx.kindRegistry.get(kindKey(node));
13842
+ const sources = kindDescriptor?.identifiers;
13843
+ if (!sources || sources.length === 0) return;
13844
+ for (const source of sources) {
13845
+ const raw = deriveIdentifier(source, node);
13846
+ if (!raw) continue;
13847
+ const normalized = normalizeTrigger(raw);
13848
+ if (!normalized) continue;
13849
+ const bucket = byName.get(normalized);
13850
+ if (bucket) {
13851
+ bucket.push({ kind: node.kind });
13852
+ } else {
13853
+ byName.set(normalized, [{ kind: node.kind }]);
13854
+ }
13745
13855
  }
13746
- return out;
13856
+ }
13857
+ function deriveIdentifier(source, node) {
13858
+ if (source === "frontmatter.name") return readFrontmatterName(node);
13859
+ if (source === "filename-basename") return readFilenameBasename(node);
13860
+ return readDirname(node);
13861
+ }
13862
+ function readFrontmatterName(node) {
13863
+ const raw = node.frontmatter?.["name"];
13864
+ if (typeof raw !== "string") return null;
13865
+ return raw.length > 0 ? raw : null;
13866
+ }
13867
+ function readFilenameBasename(node) {
13868
+ const base = pathPosix4.basename(node.path);
13869
+ if (!base) return null;
13870
+ const ext = pathPosix4.extname(base);
13871
+ const stem = ext ? base.slice(0, -ext.length) : base;
13872
+ return stem.length > 0 ? stem : null;
13873
+ }
13874
+ function readDirname(node) {
13875
+ const dir = pathPosix4.dirname(node.path);
13876
+ if (!dir || dir === "." || dir === "/") return null;
13877
+ const base = pathPosix4.basename(dir);
13878
+ return base.length > 0 ? base : null;
13879
+ }
13880
+ function kindKey(node) {
13881
+ return `${node.provider}/${node.kind}`;
13747
13882
  }
13748
13883
 
13749
13884
  // kernel/orchestrator/post-walk-transforms.ts
@@ -13756,17 +13891,17 @@ var POST_WALK_TRANSFORMS = [
13756
13891
  }
13757
13892
  },
13758
13893
  {
13759
- id: "lift-mention-confidence",
13760
- description: "Bump resolved `mentions` links to confidence 1.0 once the full node graph is known (post-merge polish).",
13761
- run(links, nodes) {
13762
- liftMentionConfidence(links, nodes);
13894
+ id: "lift-resolved-link-confidence",
13895
+ description: "Bump invocation links to confidence 1.0 when target / trigger resolves against the full node graph per the source Provider rules.",
13896
+ run(links, nodes, ctx) {
13897
+ liftResolvedLinkConfidence(links, nodes, ctx);
13763
13898
  }
13764
13899
  }
13765
13900
  ];
13766
- function applyPostWalkTransforms(links, nodes, transforms = POST_WALK_TRANSFORMS) {
13901
+ function applyPostWalkTransforms(links, nodes, ctx, transforms = POST_WALK_TRANSFORMS) {
13767
13902
  let current = links;
13768
13903
  for (const transform of transforms) {
13769
- const next = transform.run(current, nodes);
13904
+ const next = transform.run(current, nodes, ctx);
13770
13905
  if (next) current = next;
13771
13906
  }
13772
13907
  return current;
@@ -14261,7 +14396,6 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
14261
14396
  const cacheDecision = computeCacheDecision({
14262
14397
  extractors: wctx.opts.extractors,
14263
14398
  kind,
14264
- provider: provider.id,
14265
14399
  activeProvider: wctx.opts.activeProvider,
14266
14400
  nodePath: raw.path,
14267
14401
  bodyHash,
@@ -14479,7 +14613,8 @@ async function runScanInternal(_kernel, options) {
14479
14613
  pluginStores: options.pluginStores,
14480
14614
  activeProvider: resolveActiveProviderOption(options.activeProvider, options.roots)
14481
14615
  });
14482
- walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes);
14616
+ const postWalkCtx = buildPostWalkTransformCtx(_kernel);
14617
+ walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
14483
14618
  recomputeLinkCounts(walked.nodes, walked.internalLinks);
14484
14619
  recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
14485
14620
  await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
@@ -14512,6 +14647,22 @@ async function runScanInternal(_kernel, options) {
14512
14647
  await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
14513
14648
  return buildScanReturn(walked, issues, renameOps, stats, options, setup);
14514
14649
  }
14650
+ function buildPostWalkTransformCtx(kernel) {
14651
+ const kindRegistry = /* @__PURE__ */ new Map();
14652
+ const providerResolution = /* @__PURE__ */ new Map();
14653
+ const providers = kernel.registry.all("provider");
14654
+ for (const provider of providers) {
14655
+ if (provider.kinds) {
14656
+ for (const [kindName, descriptor] of Object.entries(provider.kinds)) {
14657
+ kindRegistry.set(`${provider.id}/${kindName}`, descriptor);
14658
+ }
14659
+ }
14660
+ if (provider.resolution) {
14661
+ providerResolution.set(provider.id, provider.resolution);
14662
+ }
14663
+ }
14664
+ return { kindRegistry, providerResolution };
14665
+ }
14515
14666
  function buildScanSetup(options) {
14516
14667
  const start = Date.now();
14517
14668
  const emitter = options.emitter ?? new InMemoryProgressEmitter();