@skill-map/cli 0.32.0 → 0.33.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/index.js CHANGED
@@ -100,7 +100,7 @@ import cl100k_base from "js-tiktoken/ranks/cl100k_base";
100
100
  // package.json
101
101
  var package_default = {
102
102
  name: "@skill-map/cli",
103
- version: "0.32.0",
103
+ version: "0.33.0",
104
104
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
105
105
  license: "MIT",
106
106
  type: "module",
@@ -181,6 +181,7 @@ var package_default = {
181
181
  "js-yaml": "4.1.1",
182
182
  kysely: "0.28.17",
183
183
  semver: "7.7.4",
184
+ "smol-toml": "1.6.1",
184
185
  typanion: "3.14.0",
185
186
  ws: "8.20.0"
186
187
  },
@@ -692,12 +693,22 @@ var ORCHESTRATOR_TEXTS = {
692
693
  runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
693
694
  };
694
695
 
696
+ // kernel/types.ts
697
+ var ConfidenceTier = Object.freeze({
698
+ HIGH: 0.9,
699
+ MEDIUM: 0.6,
700
+ LOW: 0.3
701
+ });
702
+
695
703
  // kernel/orchestrator/extractors.ts
696
704
  async function runExtractorsForNode(opts) {
697
705
  const internalLinks = [];
698
706
  const externalLinks = [];
699
707
  const enrichmentBuffer = /* @__PURE__ */ new Map();
700
708
  const contributions = [];
709
+ const signals = [];
710
+ const virtualNodes = [];
711
+ const virtualNodePaths = /* @__PURE__ */ new Set();
701
712
  const validators = loadSchemaValidators();
702
713
  for (const extractor of opts.extractors) {
703
714
  const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
@@ -769,6 +780,18 @@ async function runExtractorsForNode(opts) {
769
780
  emittedAt: Date.now()
770
781
  });
771
782
  };
783
+ const emitSignal = (signal) => {
784
+ const validated = validateSignal(extractor, signal, opts.emitter);
785
+ if (!validated) return;
786
+ signals.push(validated);
787
+ };
788
+ const emitNode = (emitted) => {
789
+ if (virtualNodePaths.has(emitted.path)) return;
790
+ const node = buildVirtualNode(extractor, emitted, opts.emitter);
791
+ if (!node) return;
792
+ virtualNodePaths.add(node.path);
793
+ virtualNodes.push(node);
794
+ };
772
795
  const store = opts.pluginStores?.get(extractor.pluginId);
773
796
  const ctx = buildExtractorContext(
774
797
  extractor,
@@ -778,6 +801,8 @@ async function runExtractorsForNode(opts) {
778
801
  emitLink,
779
802
  enrichNode,
780
803
  emitContribution,
804
+ emitSignal,
805
+ emitNode,
781
806
  store
782
807
  );
783
808
  await extractor.extract(ctx);
@@ -786,7 +811,9 @@ async function runExtractorsForNode(opts) {
786
811
  internalLinks,
787
812
  externalLinks,
788
813
  enrichments: Array.from(enrichmentBuffer.values()),
789
- contributions
814
+ contributions,
815
+ signals,
816
+ virtualNodes
790
817
  };
791
818
  }
792
819
  function readDeclaredContributions(extension) {
@@ -811,7 +838,7 @@ function emitExtensionError(emitter, qualifiedId, nodePath, data) {
811
838
  })
812
839
  );
813
840
  }
814
- function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
841
+ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, emitSignal, emitNode, store) {
815
842
  const scope = extractor.scope ?? "both";
816
843
  const settings = extractor.resolvedSettings ?? {};
817
844
  return {
@@ -822,9 +849,62 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
822
849
  emitLink,
823
850
  enrichNode,
824
851
  emitContribution,
852
+ emitSignal,
853
+ emitNode,
825
854
  ...store !== void 0 ? { store } : {}
826
855
  };
827
856
  }
857
+ var VIRTUAL_NODE_PLACEHOLDER_HASH = "0".repeat(64);
858
+ function buildVirtualNode(extractor, emitted, emitter) {
859
+ const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
860
+ if (typeof emitted.path !== "string" || emitted.path.length === 0) {
861
+ emitter.emit(
862
+ makeEvent("extension.error", {
863
+ kind: "virtual-node-missing-path",
864
+ extensionId: qualifiedId,
865
+ message: `Extractor ${qualifiedId} emitted a virtual node with no path; dropped.`
866
+ })
867
+ );
868
+ return null;
869
+ }
870
+ if (typeof emitted.kind !== "string" || emitted.kind.length === 0) {
871
+ emitter.emit(
872
+ makeEvent("extension.error", {
873
+ kind: "virtual-node-missing-kind",
874
+ extensionId: qualifiedId,
875
+ virtualPath: emitted.path,
876
+ message: `Extractor ${qualifiedId} emitted a virtual node at '${emitted.path}' with no kind; dropped.`
877
+ })
878
+ );
879
+ return null;
880
+ }
881
+ if (!Array.isArray(emitted.derivedFrom) || emitted.derivedFrom.length === 0) {
882
+ emitter.emit(
883
+ makeEvent("extension.error", {
884
+ kind: "virtual-node-missing-derived-from",
885
+ extensionId: qualifiedId,
886
+ virtualPath: emitted.path,
887
+ message: `Extractor ${qualifiedId} emitted a virtual node at '${emitted.path}' with empty derivedFrom; dropped.`
888
+ })
889
+ );
890
+ return null;
891
+ }
892
+ const node = {
893
+ path: emitted.path,
894
+ kind: emitted.kind,
895
+ provider: emitted.provider,
896
+ bodyHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
897
+ frontmatterHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
898
+ bytes: { frontmatter: 0, body: 0, total: 0 },
899
+ linksOutCount: 0,
900
+ linksInCount: 0,
901
+ externalRefsCount: 0,
902
+ virtual: true,
903
+ derivedFrom: [...emitted.derivedFrom]
904
+ };
905
+ if (emitted.frontmatter) node.frontmatter = emitted.frontmatter;
906
+ return node;
907
+ }
828
908
  function validateLink(extractor, link, emitter) {
829
909
  const knownKinds = ["invokes", "references", "mentions", "supersedes"];
830
910
  if (!knownKinds.includes(link.kind)) {
@@ -845,9 +925,68 @@ function validateLink(extractor, link, emitter) {
845
925
  );
846
926
  return null;
847
927
  }
848
- const confidence = link.confidence ?? "medium";
928
+ const c = link.confidence;
929
+ if (c !== void 0 && (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1)) {
930
+ const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
931
+ emitter.emit(
932
+ makeEvent("extension.error", {
933
+ kind: "link-confidence-out-of-range",
934
+ extensionId: qualifiedId,
935
+ confidence: c,
936
+ message: `Extractor ${qualifiedId} emitted a Link with confidence ${String(c)} outside [0..1]; dropped.`
937
+ })
938
+ );
939
+ return null;
940
+ }
941
+ const confidence = c ?? ConfidenceTier.MEDIUM;
849
942
  return { ...link, confidence };
850
943
  }
944
+ var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes"];
945
+ function validateSignal(extractor, signal, emitter) {
946
+ const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
947
+ if (!Array.isArray(signal.candidates) || signal.candidates.length === 0) {
948
+ emitter.emit(
949
+ makeEvent("extension.error", {
950
+ kind: "signal-no-candidates",
951
+ extensionId: qualifiedId,
952
+ signal: { source: signal.source, scope: signal.scope },
953
+ message: `Extractor ${qualifiedId} emitted a Signal with no candidates; dropped.`
954
+ })
955
+ );
956
+ return null;
957
+ }
958
+ for (const candidate of signal.candidates) {
959
+ if (!isValidSignalCandidate(qualifiedId, candidate, emitter)) return null;
960
+ }
961
+ return signal;
962
+ }
963
+ function isValidSignalCandidate(qualifiedId, candidate, emitter) {
964
+ if (!KNOWN_LINK_KINDS.includes(candidate.kind)) {
965
+ emitter.emit(
966
+ makeEvent("extension.error", {
967
+ kind: "signal-candidate-kind-not-declared",
968
+ extensionId: qualifiedId,
969
+ candidateKind: candidate.kind,
970
+ declaredKinds: KNOWN_LINK_KINDS,
971
+ message: `Extractor ${qualifiedId} emitted a Signal candidate with off-enum kind '${String(candidate.kind)}'; dropped.`
972
+ })
973
+ );
974
+ return false;
975
+ }
976
+ const c = candidate.confidence;
977
+ if (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1) {
978
+ emitter.emit(
979
+ makeEvent("extension.error", {
980
+ kind: "signal-candidate-confidence-out-of-range",
981
+ extensionId: qualifiedId,
982
+ confidence: candidate.confidence,
983
+ message: `Extractor ${qualifiedId} emitted a Signal candidate with confidence ${String(c)} outside [0..1]; dropped.`
984
+ })
985
+ );
986
+ return false;
987
+ }
988
+ return true;
989
+ }
851
990
  function dedupeLinks(links) {
852
991
  const out = /* @__PURE__ */ new Map();
853
992
  for (const link of links) {
@@ -894,7 +1033,9 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
894
1033
  }
895
1034
  }
896
1035
  var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
1036
+ var VIRTUAL_NODE_SCHEME_RE = /^mcp:\/\//i;
897
1037
  function isExternalUrlLink(link) {
1038
+ if (VIRTUAL_NODE_SCHEME_RE.test(link.target)) return false;
898
1039
  return EXTERNAL_URL_SCHEME_RE.test(link.target);
899
1040
  }
900
1041
 
@@ -1053,13 +1194,9 @@ function originatingNodeOf(link, priorNodePaths) {
1053
1194
  }
1054
1195
  function computeCacheDecision(opts) {
1055
1196
  const applicableExtractors = opts.extractors.filter((ex) => {
1056
- const kinds = ex.precondition?.kind;
1057
- if (!kinds || kinds.length === 0) return true;
1058
- return kinds.some((qualified) => {
1059
- const slashIdx = qualified.indexOf("/");
1060
- const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
1061
- return kindOnly === opts.kind;
1062
- });
1197
+ if (!matchesKindPrecondition(ex, opts.kind)) return false;
1198
+ if (!matchesProviderPrecondition(ex, opts.provider)) return false;
1199
+ return true;
1063
1200
  });
1064
1201
  const applicableQualifiedIds = new Set(
1065
1202
  applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
@@ -1073,6 +1210,20 @@ function computeCacheDecision(opts) {
1073
1210
  fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
1074
1211
  };
1075
1212
  }
1213
+ function matchesKindPrecondition(ex, kind) {
1214
+ const kinds = ex.precondition?.kind;
1215
+ if (!kinds || kinds.length === 0) return true;
1216
+ return kinds.some((qualified) => {
1217
+ const slashIdx = qualified.indexOf("/");
1218
+ const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
1219
+ return kindOnly === kind;
1220
+ });
1221
+ }
1222
+ function matchesProviderPrecondition(ex, provider) {
1223
+ const providers = ex.precondition?.provider;
1224
+ if (!providers || providers.length === 0) return true;
1225
+ return providers.includes(provider);
1226
+ }
1076
1227
  function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
1077
1228
  const cachedQualifiedIds = /* @__PURE__ */ new Set();
1078
1229
  const missingExtractors = [];
@@ -1182,7 +1333,7 @@ function findHighConfidenceRenames(opts) {
1182
1333
  if (opts.claimedNew.has(toPath)) continue;
1183
1334
  const toNode = opts.currentByPath.get(toPath);
1184
1335
  if (toNode.bodyHash === fromNode.bodyHash) {
1185
- ops.push({ from: fromPath, to: toPath, confidence: "high" });
1336
+ ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.HIGH });
1186
1337
  opts.claimedDeleted.add(fromPath);
1187
1338
  opts.claimedNew.add(toPath);
1188
1339
  break;
@@ -1217,13 +1368,13 @@ function claimSingletonRenames(opts) {
1217
1368
  const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
1218
1369
  if (remaining.length === 1) {
1219
1370
  const fromPath = remaining[0];
1220
- ops.push({ from: fromPath, to: toPath, confidence: "medium" });
1371
+ ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM });
1221
1372
  opts.issues.push({
1222
1373
  analyzerId: "auto-rename-medium",
1223
1374
  severity: "warn",
1224
1375
  nodeIds: [toPath],
1225
1376
  message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
1226
- data: { from: fromPath, to: toPath, confidence: "medium" }
1377
+ data: { from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM }
1227
1378
  });
1228
1379
  opts.claimedDeleted.add(fromPath);
1229
1380
  opts.claimedNew.add(toPath);
@@ -1427,10 +1578,45 @@ var plainParser = {
1427
1578
  }
1428
1579
  };
1429
1580
 
1581
+ // plugins/core/parsers/toml/index.ts
1582
+ import { parse as parseToml } from "smol-toml";
1583
+ var tomlParser = {
1584
+ id: "toml",
1585
+ parse(raw, _path) {
1586
+ let parsed = {};
1587
+ const issues = [];
1588
+ try {
1589
+ const doc = parseToml(raw);
1590
+ if (doc && typeof doc === "object" && !Array.isArray(doc)) {
1591
+ parsed = stripPrototypePollution(doc);
1592
+ }
1593
+ } catch (err) {
1594
+ issues.push({
1595
+ code: "frontmatter-parse-error",
1596
+ message: sanitiseParseErrorMessage2(err)
1597
+ });
1598
+ }
1599
+ const out = {
1600
+ frontmatterRaw: raw,
1601
+ frontmatter: parsed,
1602
+ body: ""
1603
+ };
1604
+ if (issues.length > 0) {
1605
+ return { ...out, issues };
1606
+ }
1607
+ return out;
1608
+ }
1609
+ };
1610
+ function sanitiseParseErrorMessage2(err) {
1611
+ const raw = err instanceof Error ? err.message : String(err);
1612
+ return raw.replace(/[-]+/g, " ").replace(/\s+/g, " ").trim();
1613
+ }
1614
+
1430
1615
  // kernel/scan/parsers/index.ts
1431
1616
  var REGISTRY = /* @__PURE__ */ new Map([
1432
1617
  [frontmatterYamlParser.id, frontmatterYamlParser],
1433
- [plainParser.id, plainParser]
1618
+ [plainParser.id, plainParser],
1619
+ [tomlParser.id, tomlParser]
1434
1620
  ]);
1435
1621
  var FROZEN_IDS = new Set(REGISTRY.keys());
1436
1622
  function getParser(id) {
@@ -2043,6 +2229,7 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
2043
2229
  const cacheDecision = computeCacheDecision({
2044
2230
  extractors: wctx.opts.extractors,
2045
2231
  kind,
2232
+ provider: provider.id,
2046
2233
  nodePath: raw.path,
2047
2234
  bodyHash,
2048
2235
  sidecarAnnotationsHash,
@@ -2145,6 +2332,10 @@ function mergeExtractResult(extractResult, accum) {
2145
2332
  accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
2146
2333
  }
2147
2334
  for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
2335
+ for (const vn of extractResult.virtualNodes) {
2336
+ if (accum.nodes.some((n) => n.path === vn.path)) continue;
2337
+ accum.nodes.push(vn);
2338
+ }
2148
2339
  }
2149
2340
  function isPartialCacheHit(ctx) {
2150
2341
  return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;