@skill-map/cli 0.34.1 → 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.
@@ -1742,7 +1742,42 @@ interface IProviderKind {
1742
1742
  * invent visuals for a Provider-declared kind.
1743
1743
  */
1744
1744
  ui: IProviderKindUi;
1745
+ /**
1746
+ * Priority-ordered list of identifier sources the post-walk resolver
1747
+ * uses to derive this kind's canonical name(s). Each entry contributes
1748
+ * one normalized name to the name index built by
1749
+ * `liftResolvedLinkConfidence`; multiple sources accumulate (e.g. a
1750
+ * skill with `name: foo` AND dirname `foo` yields one entry, a skill
1751
+ * with `name: bar` and dirname `foo` yields two).
1752
+ *
1753
+ * Defaults to `[]` (no name-resolvable). Source semantics:
1754
+ *
1755
+ * - `'frontmatter.name'`, read `node.frontmatter.name`. Required-name
1756
+ * kinds (`agent`, `command`, `skill` per their schemas) typically
1757
+ * declare this first.
1758
+ * - `'filename-basename'`, `basename(path)` without the extension.
1759
+ * For Claude/Gemini/OpenAI agents and commands the filename IS the
1760
+ * invocation handle when `name:` is absent.
1761
+ * - `'dirname'`, `basename(dirname(path))`. Anthropic skills + Gemini
1762
+ * skills + agent-skills resolve to the directory between the
1763
+ * skills root and the SKILL.md (e.g.
1764
+ * `.claude/skills/foo/SKILL.md` → `foo`).
1765
+ *
1766
+ * Compare with `IProvider.resolution` (which declares which target
1767
+ * kinds resolve which link.kind): `identifiers` is a per-kind detail
1768
+ * about WHERE the name lives; `resolution` is a per-provider strict
1769
+ * matrix about WHICH kinds count as resolution for a given link.kind.
1770
+ */
1771
+ identifiers?: TIdentifierSource[];
1745
1772
  }
1773
+ /**
1774
+ * Sources the post-walk confidence-lift transform consults to derive a
1775
+ * node's canonical name. Closed set: extending it is a spec + kernel
1776
+ * change. Order is meaningful inside `IProviderKind.identifiers`, the
1777
+ * resolver visits sources in declaration order, but the resulting index
1778
+ * is presence-based so multiple matches collapse.
1779
+ */
1780
+ type TIdentifierSource = 'frontmatter.name' | 'filename-basename' | 'dirname';
1746
1781
  /**
1747
1782
  * Presentation contract for one Provider kind. The Provider declares
1748
1783
  * intent (label + base color, optional dark variant + emoji + icon);
@@ -1902,6 +1937,38 @@ interface IProvider extends IExtensionBase {
1902
1937
  * kind against `NodeKind`.
1903
1938
  */
1904
1939
  classify(path: string, frontmatter: Record<string, unknown>): string | null;
1940
+ /**
1941
+ * Strict resolution matrix consumed by the post-walk confidence-lift
1942
+ * transform: maps a `link.kind` (emitted by an Extractor in this
1943
+ * Provider's bundle, e.g. `'mentions'`, `'invokes'`) to the set of
1944
+ * target `node.kind` values that count as a valid resolution.
1945
+ *
1946
+ * Used to decide whether to bump a link's confidence to 1.0 when its
1947
+ * normalized trigger matches some node's identifier (see
1948
+ * `IProviderKind.identifiers`). A link whose name resolves to a node
1949
+ * whose kind is NOT in `resolution[link.kind]` stays at its
1950
+ * extractor-emitted confidence, the name exists but does not resolve
1951
+ * AS THIS link.kind. Example: in `claude`, `invokes` resolves to
1952
+ * `['command', 'skill']`, so a `/foo` slash matching an `agent` named
1953
+ * `foo` does not bump (agents are mentioned with `@`, not invoked
1954
+ * with `/`).
1955
+ *
1956
+ * The lookup uses the Provider id attached to the link's SOURCE node
1957
+ * (i.e. who wrote the trigger). A link sourced from a markdown body
1958
+ * outside any Provider's territory falls under `core/markdown`'s
1959
+ * empty rules, no bump applies via the name path (the path-match rule
1960
+ * still does).
1961
+ *
1962
+ * Default `undefined` ≡ empty map ≡ no link.kind bumps under this
1963
+ * Provider's name index. Path matches (`link.target === node.path`)
1964
+ * are unaffected, those always bump regardless of `resolution`.
1965
+ *
1966
+ * Distinct from the spec's `IProvider.resolverRules` (referenced in
1967
+ * §Resolver phase): `resolverRules` rank candidates inside the Signal
1968
+ * IR (Phase 3+, not wired today); `resolution` is the post-walk
1969
+ * confidence-lift contract, which runs against the merged Link graph.
1970
+ */
1971
+ resolution?: Record<string, string[]>;
1905
1972
  }
1906
1973
  /**
1907
1974
  * Declarative read config a Provider declares via `IProvider.read`.
@@ -101,7 +101,7 @@ import cl100k_base from "js-tiktoken/ranks/cl100k_base";
101
101
  // package.json
102
102
  var package_default = {
103
103
  name: "@skill-map/cli",
104
- version: "0.34.1",
104
+ version: "0.35.0",
105
105
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
106
106
  license: "MIT",
107
107
  type: "module",
@@ -1713,6 +1713,9 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
1713
1713
  return "obsolete";
1714
1714
  }
1715
1715
 
1716
+ // kernel/orchestrator/lift-resolved-link-confidence.ts
1717
+ import { posix as pathPosix } from "path";
1718
+
1716
1719
  // kernel/trigger-normalize.ts
1717
1720
  function normalizeTrigger(source) {
1718
1721
  let out = source.normalize("NFD");
@@ -1723,37 +1726,92 @@ function normalizeTrigger(source) {
1723
1726
  return out.trim();
1724
1727
  }
1725
1728
 
1726
- // kernel/orchestrator/lift-mention-confidence.ts
1727
- function liftMentionConfidence(links, nodes) {
1728
- if (!links.some((l) => l.kind === "mentions")) return;
1729
- const byPath2 = /* @__PURE__ */ new Set();
1730
- for (const node of nodes) byPath2.add(node.path);
1731
- const byNormalizedName = indexByNormalizedName(nodes);
1729
+ // kernel/orchestrator/lift-resolved-link-confidence.ts
1730
+ function liftResolvedLinkConfidence(links, nodes, ctx) {
1731
+ if (!links.some((l) => l.confidence < 1)) return;
1732
+ const indexes = buildIndexes(nodes, ctx);
1732
1733
  for (const link of links) {
1733
- if (link.kind !== "mentions") continue;
1734
- if (isResolved(link, byPath2, byNormalizedName)) {
1734
+ if (link.confidence < 1 && resolves(link, indexes, ctx)) {
1735
1735
  link.confidence = 1;
1736
1736
  }
1737
1737
  }
1738
1738
  }
1739
- function isResolved(link, byPath2, byNormalizedName) {
1740
- const normalized = link.trigger?.normalizedTrigger;
1741
- if (normalized) {
1742
- const withoutSigil = normalized.replace(/^[/@]/, "").trim();
1743
- if (byNormalizedName.has(withoutSigil)) return true;
1744
- }
1745
- if (byPath2.has(link.target)) return true;
1746
- return false;
1747
- }
1748
- function indexByNormalizedName(nodes) {
1749
- const out = /* @__PURE__ */ new Map();
1739
+ function buildIndexes(nodes, ctx) {
1740
+ const byPath2 = /* @__PURE__ */ new Set();
1741
+ const byName = /* @__PURE__ */ new Map();
1742
+ const nodeByPath = /* @__PURE__ */ new Map();
1750
1743
  for (const node of nodes) {
1751
- const raw = node.frontmatter?.["name"];
1752
- const name = typeof raw === "string" ? raw : "";
1753
- if (!name) continue;
1754
- out.set(normalizeTrigger(name), true);
1744
+ byPath2.add(node.path);
1745
+ nodeByPath.set(node.path, node);
1746
+ indexNode(node, ctx, byName);
1747
+ }
1748
+ return { byPath: byPath2, byName, nodeByPath };
1749
+ }
1750
+ function resolves(link, indexes, ctx) {
1751
+ if (indexes.byPath.has(link.target)) return true;
1752
+ return resolvesByName(link, indexes, ctx);
1753
+ }
1754
+ function resolvesByName(link, indexes, ctx) {
1755
+ const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
1756
+ if (stripped === null) return false;
1757
+ const candidates = indexes.byName.get(stripped);
1758
+ if (!candidates?.length) return false;
1759
+ const allowedKinds = lookupAllowedKinds(link, indexes, ctx);
1760
+ if (!allowedKinds?.length) return false;
1761
+ return candidates.some((c) => allowedKinds.includes(c.kind));
1762
+ }
1763
+ function lookupAllowedKinds(link, indexes, ctx) {
1764
+ const sourceNode = indexes.nodeByPath.get(link.source);
1765
+ if (!sourceNode) return void 0;
1766
+ return ctx.providerResolution.get(sourceNode.provider)?.[link.kind];
1767
+ }
1768
+ function stripTriggerSigil(normalized) {
1769
+ if (!normalized) return null;
1770
+ const trimmed = normalized.replace(/^[/@]/, "").trim();
1771
+ return trimmed.length === 0 ? null : trimmed;
1772
+ }
1773
+ function indexNode(node, ctx, byName) {
1774
+ const kindDescriptor = ctx.kindRegistry.get(kindKey(node));
1775
+ const sources = kindDescriptor?.identifiers;
1776
+ if (!sources || sources.length === 0) return;
1777
+ for (const source of sources) {
1778
+ const raw = deriveIdentifier(source, node);
1779
+ if (!raw) continue;
1780
+ const normalized = normalizeTrigger(raw);
1781
+ if (!normalized) continue;
1782
+ const bucket = byName.get(normalized);
1783
+ if (bucket) {
1784
+ bucket.push({ kind: node.kind });
1785
+ } else {
1786
+ byName.set(normalized, [{ kind: node.kind }]);
1787
+ }
1755
1788
  }
1756
- return out;
1789
+ }
1790
+ function deriveIdentifier(source, node) {
1791
+ if (source === "frontmatter.name") return readFrontmatterName(node);
1792
+ if (source === "filename-basename") return readFilenameBasename(node);
1793
+ return readDirname(node);
1794
+ }
1795
+ function readFrontmatterName(node) {
1796
+ const raw = node.frontmatter?.["name"];
1797
+ if (typeof raw !== "string") return null;
1798
+ return raw.length > 0 ? raw : null;
1799
+ }
1800
+ function readFilenameBasename(node) {
1801
+ const base = pathPosix.basename(node.path);
1802
+ if (!base) return null;
1803
+ const ext = pathPosix.extname(base);
1804
+ const stem = ext ? base.slice(0, -ext.length) : base;
1805
+ return stem.length > 0 ? stem : null;
1806
+ }
1807
+ function readDirname(node) {
1808
+ const dir = pathPosix.dirname(node.path);
1809
+ if (!dir || dir === "." || dir === "/") return null;
1810
+ const base = pathPosix.basename(dir);
1811
+ return base.length > 0 ? base : null;
1812
+ }
1813
+ function kindKey(node) {
1814
+ return `${node.provider}/${node.kind}`;
1757
1815
  }
1758
1816
 
1759
1817
  // kernel/orchestrator/post-walk-transforms.ts
@@ -1766,17 +1824,17 @@ var POST_WALK_TRANSFORMS = [
1766
1824
  }
1767
1825
  },
1768
1826
  {
1769
- id: "lift-mention-confidence",
1770
- description: "Bump resolved `mentions` links to confidence 1.0 once the full node graph is known (post-merge polish).",
1771
- run(links, nodes) {
1772
- liftMentionConfidence(links, nodes);
1827
+ id: "lift-resolved-link-confidence",
1828
+ description: "Bump invocation links to confidence 1.0 when target / trigger resolves against the full node graph per the source Provider rules.",
1829
+ run(links, nodes, ctx) {
1830
+ liftResolvedLinkConfidence(links, nodes, ctx);
1773
1831
  }
1774
1832
  }
1775
1833
  ];
1776
- function applyPostWalkTransforms(links, nodes, transforms = POST_WALK_TRANSFORMS) {
1834
+ function applyPostWalkTransforms(links, nodes, ctx, transforms = POST_WALK_TRANSFORMS) {
1777
1835
  let current = links;
1778
1836
  for (const transform of transforms) {
1779
- const next = transform.run(current, nodes);
1837
+ const next = transform.run(current, nodes, ctx);
1780
1838
  if (next) current = next;
1781
1839
  }
1782
1840
  return current;
@@ -2846,7 +2904,8 @@ async function runScanInternal(_kernel, options) {
2846
2904
  pluginStores: options.pluginStores,
2847
2905
  activeProvider: resolveActiveProviderOption(options.activeProvider, options.roots)
2848
2906
  });
2849
- walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes);
2907
+ const postWalkCtx = buildPostWalkTransformCtx(_kernel);
2908
+ walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
2850
2909
  recomputeLinkCounts(walked.nodes, walked.internalLinks);
2851
2910
  recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
2852
2911
  await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
@@ -2879,6 +2938,22 @@ async function runScanInternal(_kernel, options) {
2879
2938
  await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
2880
2939
  return buildScanReturn(walked, issues, renameOps, stats, options, setup);
2881
2940
  }
2941
+ function buildPostWalkTransformCtx(kernel) {
2942
+ const kindRegistry = /* @__PURE__ */ new Map();
2943
+ const providerResolution = /* @__PURE__ */ new Map();
2944
+ const providers = kernel.registry.all("provider");
2945
+ for (const provider of providers) {
2946
+ if (provider.kinds) {
2947
+ for (const [kindName, descriptor] of Object.entries(provider.kinds)) {
2948
+ kindRegistry.set(`${provider.id}/${kindName}`, descriptor);
2949
+ }
2950
+ }
2951
+ if (provider.resolution) {
2952
+ providerResolution.set(provider.id, provider.resolution);
2953
+ }
2954
+ }
2955
+ return { kindRegistry, providerResolution };
2956
+ }
2882
2957
  function buildScanSetup(options) {
2883
2958
  const start = Date.now();
2884
2959
  const emitter = options.emitter ?? new InMemoryProgressEmitter();