@skill-map/cli 0.18.0 → 0.20.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 (43) hide show
  1. package/README.md +4 -0
  2. package/dist/cli/tutorial/sm-tutorial.md +9 -10
  3. package/dist/cli.js +11062 -7069
  4. package/dist/cli.js.map +1 -1
  5. package/dist/conformance/index.js +1 -1
  6. package/dist/conformance/index.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +424 -148
  9. package/dist/index.js.map +1 -1
  10. package/dist/kernel/index.d.ts +776 -119
  11. package/dist/kernel/index.js +424 -148
  12. package/dist/kernel/index.js.map +1 -1
  13. package/dist/migrations/001_initial.sql +125 -15
  14. package/dist/ui/chunk-2W62S3FU.js +2638 -0
  15. package/dist/ui/chunk-C7QWBAYP.js +247 -0
  16. package/dist/ui/chunk-DLT5AP43.js +237 -0
  17. package/dist/ui/chunk-HJSRMZTK.js +450 -0
  18. package/dist/ui/chunk-HOBQ4G4O.js +125 -0
  19. package/dist/ui/chunk-IBUV6OG2.js +1 -0
  20. package/dist/ui/chunk-LQTUSDHD.js +124 -0
  21. package/dist/ui/chunk-QICH7GU2.js +5 -0
  22. package/dist/ui/chunk-UJRROL5X.js +1 -0
  23. package/dist/ui/chunk-VLNLJAUB.js +61 -0
  24. package/dist/ui/chunk-W3JLG7BI.js +965 -0
  25. package/dist/ui/index.html +10 -2
  26. package/dist/ui/main-QHE47BCM.js +1 -0
  27. package/dist/ui/{styles-CBPFNGXA.css → styles-VJ5Q6D2X.css} +1 -1
  28. package/migrations/001_initial.sql +125 -15
  29. package/package.json +2 -2
  30. package/dist/migrations/002_sidecar_columns.sql +0 -53
  31. package/dist/migrations/003_drop_node_author.sql +0 -20
  32. package/dist/migrations/004_sidecar_root_json.sql +0 -23
  33. package/dist/migrations/005_node_favorites.sql +0 -20
  34. package/dist/ui/chunk-JKJGGXCS.js +0 -1025
  35. package/dist/ui/chunk-SX2A3WBX.js +0 -247
  36. package/dist/ui/chunk-TWZHUCAT.js +0 -237
  37. package/dist/ui/chunk-WTAL2RK4.js +0 -1
  38. package/dist/ui/chunk-Z3UJHHTC.js +0 -3091
  39. package/dist/ui/main-AAYGMON4.js +0 -1
  40. package/migrations/002_sidecar_columns.sql +0 -53
  41. package/migrations/003_drop_node_author.sql +0 -20
  42. package/migrations/004_sidecar_root_json.sql +0 -23
  43. package/migrations/005_node_favorites.sql +0 -20
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ function tx(template, vars = {}) {
28
28
  var EXTENSION_KINDS = Object.freeze([
29
29
  "provider",
30
30
  "extractor",
31
- "rule",
31
+ "analyzer",
32
32
  "action",
33
33
  "formatter",
34
34
  "hook"
@@ -103,7 +103,7 @@ import yaml4 from "js-yaml";
103
103
  // package.json
104
104
  var package_default = {
105
105
  name: "@skill-map/cli",
106
- version: "0.18.0",
106
+ version: "0.20.0",
107
107
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
108
108
  license: "MIT",
109
109
  type: "module",
@@ -169,7 +169,7 @@ var package_default = {
169
169
  },
170
170
  dependencies: {
171
171
  "@hono/node-server": "2.0.1",
172
- "@skill-map/spec": "0.18.0",
172
+ "@skill-map/spec": "0.20.0",
173
173
  ajv: "8.18.0",
174
174
  "ajv-formats": "3.0.1",
175
175
  chokidar: "5.0.0",
@@ -263,15 +263,15 @@ function readSidecarFor(mdAbsolutePath) {
263
263
  };
264
264
  }
265
265
  const root = parsedYaml;
266
- const forBlock = root["for"];
266
+ const identityBlock = root["identity"];
267
267
  const annotationsRaw = root["annotations"];
268
268
  const annotations = isPlainObject(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
269
269
  return {
270
270
  parsed: {
271
271
  filePath: sidecarPath,
272
- forBodyHash: String(forBlock["bodyHash"]),
273
- forFrontmatterHash: String(forBlock["frontmatterHash"]),
274
- forPath: String(forBlock["path"]),
272
+ identityBodyHash: String(identityBlock["bodyHash"]),
273
+ identityFrontmatterHash: String(identityBlock["frontmatterHash"]),
274
+ identityPath: String(identityBlock["path"]),
275
275
  annotations,
276
276
  raw: root
277
277
  },
@@ -508,18 +508,20 @@ function formatAjvErrors(errors) {
508
508
 
509
509
  // kernel/extensions/hook.ts
510
510
  var HOOK_TRIGGERS = Object.freeze([
511
+ "boot",
511
512
  "scan.started",
512
513
  "scan.completed",
513
514
  "extractor.completed",
514
- "rule.completed",
515
+ "analyzer.completed",
515
516
  "action.completed",
516
517
  "job.spawning",
517
518
  "job.completed",
518
- "job.failed"
519
+ "job.failed",
520
+ "shutdown"
519
521
  ]);
520
522
 
521
523
  // kernel/adapters/plugin-loader.ts
522
- var KNOWN_KINDS = /* @__PURE__ */ new Set(["provider", "extractor", "rule", "action", "formatter", "hook"]);
524
+ var KNOWN_KINDS = /* @__PURE__ */ new Set(["provider", "extractor", "analyzer", "action", "formatter", "hook"]);
523
525
  var KNOWN_KINDS_LIST = [...KNOWN_KINDS].join(" / ");
524
526
  var HOOKABLE_TRIGGERS_LIST = HOOK_TRIGGERS.join(", ");
525
527
  function installedSpecVersion() {
@@ -535,6 +537,135 @@ import { readFileSync as readFileSync4 } from "fs";
535
537
  import { dirname as dirname3, resolve as resolve4 } from "path";
536
538
  import { createRequire as createRequire4 } from "module";
537
539
  import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
540
+ var SCHEMA_FILES = {
541
+ node: "schemas/node.schema.json",
542
+ link: "schemas/link.schema.json",
543
+ issue: "schemas/issue.schema.json",
544
+ "scan-result": "schemas/scan-result.schema.json",
545
+ "execution-record": "schemas/execution-record.schema.json",
546
+ "project-config": "schemas/project-config.schema.json",
547
+ "plugins-registry": "schemas/plugins-registry.schema.json",
548
+ job: "schemas/job.schema.json",
549
+ "report-base": "schemas/report-base.schema.json",
550
+ "conformance-case": "schemas/conformance-case.schema.json",
551
+ "history-stats": "schemas/history-stats.schema.json",
552
+ "extension-provider": "schemas/extensions/provider.schema.json",
553
+ "extension-extractor": "schemas/extensions/extractor.schema.json",
554
+ "extension-analyzer": "schemas/extensions/analyzer.schema.json",
555
+ "extension-action": "schemas/extensions/action.schema.json",
556
+ "extension-formatter": "schemas/extensions/formatter.schema.json",
557
+ "extension-hook": "schemas/extensions/hook.schema.json",
558
+ "frontmatter-base": "schemas/frontmatter/base.schema.json"
559
+ };
560
+ var SUPPORTING_SCHEMAS = [
561
+ "schemas/extensions/base.schema.json",
562
+ "schemas/frontmatter/base.schema.json",
563
+ "schemas/summaries/security-scanner.schema.json",
564
+ "schemas/view-slots.schema.json",
565
+ "schemas/input-types.schema.json"
566
+ ];
567
+ var cachedValidators = null;
568
+ function loadSchemaValidators() {
569
+ if (cachedValidators !== null) return cachedValidators;
570
+ cachedValidators = buildSchemaValidators();
571
+ return cachedValidators;
572
+ }
573
+ function buildSchemaValidators() {
574
+ const specRoot = resolveSpecRoot2();
575
+ const ajv = new Ajv20204({
576
+ strict: false,
577
+ allErrors: true,
578
+ allowUnionTypes: true
579
+ });
580
+ applyAjvFormats(ajv);
581
+ for (const rel of SUPPORTING_SCHEMAS) {
582
+ const file = resolve4(specRoot, rel);
583
+ if (!existsSyncSafe(file)) continue;
584
+ const schema = JSON.parse(readFileSync4(file, "utf8"));
585
+ ajv.addSchema(schema);
586
+ }
587
+ const validators = /* @__PURE__ */ new Map();
588
+ for (const [name, rel] of Object.entries(SCHEMA_FILES)) {
589
+ const file = resolve4(specRoot, rel);
590
+ const schema = JSON.parse(readFileSync4(file, "utf8"));
591
+ const byId = typeof schema.$id === "string" ? ajv.getSchema(schema.$id) : void 0;
592
+ validators.set(name, byId ?? ajv.compile(schema));
593
+ }
594
+ const extensionByKind = {
595
+ provider: "extension-provider",
596
+ extractor: "extension-extractor",
597
+ analyzer: "extension-analyzer",
598
+ action: "extension-action",
599
+ formatter: "extension-formatter",
600
+ hook: "extension-hook"
601
+ };
602
+ const pluginManifestValidator = ajv.compile({
603
+ $ref: "https://skill-map.dev/spec/v0/plugins-registry.schema.json#/$defs/PluginManifest"
604
+ });
605
+ const contributionValidators = /* @__PURE__ */ new Map();
606
+ const VIEW_SLOTS_ID = "https://skill-map.dev/spec/v0/view-slots.schema.json";
607
+ const KNOWN_SLOTS = /* @__PURE__ */ new Set([
608
+ "card.title.right",
609
+ "card.subtitle.left",
610
+ "card.footer.left.counter",
611
+ "card.footer.right",
612
+ "graph.node.alert",
613
+ "inspector.header.badge.counter",
614
+ "inspector.header.badge.tag",
615
+ "inspector.body.panel.breakdown",
616
+ "inspector.body.panel.records",
617
+ "inspector.body.panel.tree",
618
+ "inspector.body.panel.key-values",
619
+ "inspector.body.panel.link-list",
620
+ "inspector.body.panel.markdown",
621
+ "topbar.actions.indicator"
622
+ ]);
623
+ function getContributionValidator(slot) {
624
+ if (!KNOWN_SLOTS.has(slot)) return null;
625
+ const existing = contributionValidators.get(slot);
626
+ if (existing) return existing;
627
+ const ref = `${VIEW_SLOTS_ID}#/$defs/payloads/${slot}`;
628
+ let compiled;
629
+ try {
630
+ compiled = ajv.compile({ $ref: ref });
631
+ } catch {
632
+ return null;
633
+ }
634
+ contributionValidators.set(slot, compiled);
635
+ return compiled;
636
+ }
637
+ return {
638
+ getValidator(name) {
639
+ const v = validators.get(name);
640
+ if (!v) throw new Error(`Unknown schema: ${name}`);
641
+ return v;
642
+ },
643
+ validatorForExtension(kind) {
644
+ return validators.get(extensionByKind[kind]);
645
+ },
646
+ validate(name, data) {
647
+ const v = validators.get(name);
648
+ if (!v) throw new Error(`Unknown schema: ${name}`);
649
+ if (v(data)) return { ok: true, data };
650
+ const errors = (v.errors ?? []).map(formatError).join("; ");
651
+ return { ok: false, errors };
652
+ },
653
+ validatePluginManifest(data) {
654
+ if (pluginManifestValidator(data)) return { ok: true, data };
655
+ const errors = (pluginManifestValidator.errors ?? []).map(formatError).join("; ");
656
+ return { ok: false, errors };
657
+ },
658
+ validateContributionPayload(slot, payload) {
659
+ const validator = getContributionValidator(slot);
660
+ if (!validator) {
661
+ return { ok: false, errors: "unknown-slot" };
662
+ }
663
+ if (validator(payload)) return { ok: true };
664
+ const errors = (validator.errors ?? []).map(formatError).join("; ");
665
+ return { ok: false, errors };
666
+ }
667
+ };
668
+ }
538
669
  function buildProviderFrontmatterValidator(providers) {
539
670
  const specRoot = resolveSpecRoot2();
540
671
  const ajv = new Ajv20204({
@@ -592,6 +723,14 @@ function resolveSpecRoot2() {
592
723
  );
593
724
  }
594
725
  }
726
+ function existsSyncSafe(path) {
727
+ try {
728
+ readFileSync4(path, "utf8");
729
+ return true;
730
+ } catch {
731
+ return false;
732
+ }
733
+ }
595
734
 
596
735
  // kernel/i18n/orchestrator.texts.ts
597
736
  var ORCHESTRATOR_TEXTS = {
@@ -600,7 +739,9 @@ var ORCHESTRATOR_TEXTS = {
600
739
  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.",
601
740
  frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes \u2014 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.",
602
741
  extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
603
- extensionErrorIssueInvalidSeverity: `Rule "{{ruleId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
742
+ extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
743
+ extensionErrorContributionUnknownId: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}} but did not declare it in its `viewContributions` map. Contribution dropped.',
744
+ extensionErrorContributionPayloadInvalid: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}}; payload failed the "{{slot}}" schema: {{errors}}. Contribution dropped.',
604
745
  runScanRootEmptyArray: "runScan: roots must contain at least one path (spec requires minItems: 1)",
605
746
  runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
606
747
  };
@@ -667,7 +808,7 @@ function readDefaultsFromDisk() {
667
808
  return "";
668
809
  }
669
810
 
670
- // kernel/scan/parsers/frontmatter-yaml.ts
811
+ // built-in-plugins/parsers/frontmatter-yaml/index.ts
671
812
  import yaml3 from "js-yaml";
672
813
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
673
814
  var FORBIDDEN_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
@@ -693,7 +834,7 @@ var frontmatterYamlParser = {
693
834
  }
694
835
  };
695
836
 
696
- // kernel/scan/parsers/plain.ts
837
+ // built-in-plugins/parsers/plain/index.ts
697
838
  var plainParser = {
698
839
  id: "plain",
699
840
  parse(raw, _path) {
@@ -794,6 +935,85 @@ function resolveProviderWalk(provider) {
794
935
  };
795
936
  }
796
937
 
938
+ // kernel/extensions/hook-dispatcher.ts
939
+ function makeHookDispatcher(hooks, emitter) {
940
+ if (hooks.length === 0) {
941
+ return { dispatch: async () => {
942
+ } };
943
+ }
944
+ const byTrigger = /* @__PURE__ */ new Map();
945
+ for (const hook of hooks) {
946
+ if (hook.mode === "probabilistic") {
947
+ const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
948
+ log.warn(
949
+ `Probabilistic hook ${qualifiedId} deferred to job subsystem (future job subsystem). The hook is registered but will not dispatch in-scan.`,
950
+ { hookId: qualifiedId, mode: "probabilistic" }
951
+ );
952
+ continue;
953
+ }
954
+ for (const trig of hook.triggers) {
955
+ const bucket = byTrigger.get(trig);
956
+ if (bucket) bucket.push(hook);
957
+ else byTrigger.set(trig, [hook]);
958
+ }
959
+ }
960
+ return {
961
+ async dispatch(trigger, event) {
962
+ const subs = byTrigger.get(trigger);
963
+ if (!subs || subs.length === 0) return;
964
+ for (const hook of subs) {
965
+ if (!matchesFilter(hook, event)) continue;
966
+ const ctx = buildHookContext(hook, trigger, event);
967
+ try {
968
+ await hook.on(ctx);
969
+ } catch (err) {
970
+ const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
971
+ const message = formatErrorMessage(err);
972
+ emitter.emit(
973
+ makeEvent("extension.error", {
974
+ kind: "hook-error",
975
+ extensionId: qualifiedId,
976
+ trigger,
977
+ message
978
+ })
979
+ );
980
+ }
981
+ }
982
+ }
983
+ };
984
+ }
985
+ function makeEvent(type, data) {
986
+ return { type, timestamp: (/* @__PURE__ */ new Date()).toISOString(), data };
987
+ }
988
+ function matchesFilter(hook, event) {
989
+ if (!hook.filter) return true;
990
+ const data = event.data ?? {};
991
+ for (const [key, expected] of Object.entries(hook.filter)) {
992
+ if (data[key] !== expected) return false;
993
+ }
994
+ return true;
995
+ }
996
+ function buildHookContext(_hook, trigger, event) {
997
+ const data = event.data ?? {};
998
+ const ctx = {
999
+ event: {
1000
+ type: trigger,
1001
+ timestamp: event.timestamp,
1002
+ ...event.runId !== void 0 ? { runId: event.runId } : {},
1003
+ ...event.jobId !== void 0 ? { jobId: event.jobId } : {},
1004
+ data: event.data
1005
+ }
1006
+ };
1007
+ if (typeof data["extractorId"] === "string") ctx.extractorId = data["extractorId"];
1008
+ if (typeof data["analyzerId"] === "string") ctx.analyzerId = data["analyzerId"];
1009
+ if (typeof data["actionId"] === "string") ctx.actionId = data["actionId"];
1010
+ if (data["node"] && typeof data["node"] === "object") {
1011
+ ctx.node = data["node"];
1012
+ }
1013
+ if (data["jobResult"] !== void 0) ctx.jobResult = data["jobResult"];
1014
+ return ctx;
1015
+ }
1016
+
797
1017
  // kernel/orchestrator.ts
798
1018
  var SCANNED_BY = {
799
1019
  name: "skill-map",
@@ -819,7 +1039,7 @@ async function runScanInternal(_kernel, options) {
819
1039
  const start = Date.now();
820
1040
  const scannedAt = start;
821
1041
  const emitter = options.emitter ?? new InMemoryProgressEmitter();
822
- const exts = options.extensions ?? { providers: [], extractors: [], rules: [] };
1042
+ const exts = options.extensions ?? { providers: [], extractors: [], analyzers: [] };
823
1043
  const hookDispatcher = makeHookDispatcher(exts.hooks ?? [], emitter);
824
1044
  const tokenize = options.tokenize !== false;
825
1045
  const scope = options.scope ?? "project";
@@ -856,16 +1076,28 @@ async function runScanInternal(_kernel, options) {
856
1076
  emitter.emit(evt);
857
1077
  await hookDispatcher.dispatch("extractor.completed", evt);
858
1078
  }
859
- const issues = await runRules(
860
- exts.rules,
1079
+ const analyzerResult = await runAnalyzers(
1080
+ exts.analyzers,
861
1081
  walked.nodes,
862
1082
  walked.internalLinks,
863
1083
  walked.orphanSidecars,
864
1084
  walked.sidecarRoots,
865
1085
  options.annotationContributions ?? [],
1086
+ options.viewContributions ?? [],
1087
+ options.orphanJobFiles ?? [],
1088
+ options.referenceablePaths,
1089
+ options.cwd,
866
1090
  emitter,
867
1091
  hookDispatcher
868
1092
  );
1093
+ const issues = analyzerResult.issues;
1094
+ for (const c of analyzerResult.contributions) walked.contributions.push(c);
1095
+ for (const analyzer of exts.analyzers ?? []) {
1096
+ if (analyzer.viewContributions === void 0) continue;
1097
+ for (const node of walked.nodes) {
1098
+ walked.freshlyRunTuples.add(`${analyzer.pluginId}/${analyzer.id}/${node.path}`);
1099
+ }
1100
+ }
869
1101
  for (const issue of walked.frontmatterIssues) issues.push(issue);
870
1102
  const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues) : [];
871
1103
  const stats = {
@@ -899,7 +1131,9 @@ async function runScanInternal(_kernel, options) {
899
1131
  },
900
1132
  renameOps,
901
1133
  extractorRuns: walked.extractorRuns,
902
- enrichments: walked.enrichments
1134
+ enrichments: walked.enrichments,
1135
+ contributions: walked.contributions,
1136
+ freshlyRunTuples: walked.freshlyRunTuples
903
1137
  };
904
1138
  }
905
1139
  function validateRoots(roots) {
@@ -931,7 +1165,7 @@ function indexPriorSnapshot(prior) {
931
1165
  else priorLinksByOriginating.set(key, [link]);
932
1166
  }
933
1167
  for (const issue of prior.issues) {
934
- if (issue.ruleId !== "frontmatter-invalid" && issue.ruleId !== "frontmatter-malformed") continue;
1168
+ if (issue.analyzerId !== "frontmatter-invalid" && issue.analyzerId !== "frontmatter-malformed") continue;
935
1169
  if (issue.nodeIds.length !== 1) continue;
936
1170
  const path = issue.nodeIds[0];
937
1171
  const list = priorFrontmatterIssuesByNode.get(path);
@@ -944,9 +1178,10 @@ async function runExtractorsForNode(opts) {
944
1178
  const internalLinks = [];
945
1179
  const externalLinks = [];
946
1180
  const enrichmentBuffer = /* @__PURE__ */ new Map();
1181
+ const contributions = [];
1182
+ const validators = loadSchemaValidators();
947
1183
  for (const extractor of opts.extractors) {
948
1184
  const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
949
- const isProb = extractor.mode === "probabilistic";
950
1185
  const emitLink = (link) => {
951
1186
  const validated = validateLink(extractor, link, opts.emitter);
952
1187
  if (!validated) return;
@@ -966,9 +1201,54 @@ async function runExtractorsForNode(opts) {
966
1201
  bodyHashAtEnrichment: opts.bodyHash,
967
1202
  value: { ...partial },
968
1203
  enrichedAt: Date.now(),
969
- isProbabilistic: isProb
1204
+ // Extractors are deterministic-only; `is_probabilistic` is
1205
+ // reserved on the row for future Action-issued enrichments.
1206
+ isProbabilistic: false
1207
+ });
1208
+ }
1209
+ };
1210
+ const declaredContributions = readDeclaredContributions(extractor);
1211
+ const emitContribution = (contributionId, payload) => {
1212
+ const declared = declaredContributions.get(contributionId);
1213
+ if (!declared) {
1214
+ emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
1215
+ phase: "emitContribution",
1216
+ contributionId,
1217
+ reason: "unknown-contribution-id",
1218
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
1219
+ extractorId: qualifiedId,
1220
+ contributionId,
1221
+ nodePath: opts.node.path
1222
+ })
1223
+ });
1224
+ return;
1225
+ }
1226
+ const result = validators.validateContributionPayload(declared.slot, payload);
1227
+ if (!result.ok) {
1228
+ emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
1229
+ phase: "emitContribution",
1230
+ contributionId,
1231
+ slot: declared.slot,
1232
+ reason: result.errors,
1233
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
1234
+ extractorId: qualifiedId,
1235
+ contributionId,
1236
+ nodePath: opts.node.path,
1237
+ slot: declared.slot,
1238
+ errors: result.errors
1239
+ })
970
1240
  });
1241
+ return;
971
1242
  }
1243
+ contributions.push({
1244
+ pluginId: extractor.pluginId,
1245
+ extensionId: extractor.id,
1246
+ nodePath: opts.node.path,
1247
+ contributionId,
1248
+ slot: declared.slot,
1249
+ payload,
1250
+ emittedAt: Date.now()
1251
+ });
972
1252
  };
973
1253
  const store = opts.pluginStores?.get(extractor.pluginId);
974
1254
  const ctx = buildExtractorContext(
@@ -978,6 +1258,7 @@ async function runExtractorsForNode(opts) {
978
1258
  opts.frontmatter,
979
1259
  emitLink,
980
1260
  enrichNode,
1261
+ emitContribution,
981
1262
  store
982
1263
  );
983
1264
  await extractor.extract(ctx);
@@ -985,9 +1266,32 @@ async function runExtractorsForNode(opts) {
985
1266
  return {
986
1267
  internalLinks,
987
1268
  externalLinks,
988
- enrichments: Array.from(enrichmentBuffer.values())
1269
+ enrichments: Array.from(enrichmentBuffer.values()),
1270
+ contributions
989
1271
  };
990
1272
  }
1273
+ function readDeclaredContributions(extension) {
1274
+ const out = /* @__PURE__ */ new Map();
1275
+ const raw = extension.viewContributions;
1276
+ if (typeof raw !== "object" || raw === null) return out;
1277
+ for (const [id, value] of Object.entries(raw)) {
1278
+ if (typeof value !== "object" || value === null) continue;
1279
+ const slot = value.slot;
1280
+ if (typeof slot !== "string") continue;
1281
+ out.set(id, { slot });
1282
+ }
1283
+ return out;
1284
+ }
1285
+ function emitExtensionError(emitter, qualifiedId, nodePath, data) {
1286
+ emitter.emit(
1287
+ makeEvent("extension.error", {
1288
+ kind: "contribution-rejected",
1289
+ extensionId: qualifiedId,
1290
+ nodePath,
1291
+ ...data
1292
+ })
1293
+ );
1294
+ }
991
1295
  function computeCacheDecision(opts) {
992
1296
  const applicableExtractors = opts.extractors.filter(
993
1297
  (ex) => ex.applicableKinds === void 0 || ex.applicableKinds.includes(opts.kind)
@@ -1110,6 +1414,8 @@ async function walkAndExtract(opts) {
1110
1414
  const cachedPaths = /* @__PURE__ */ new Set();
1111
1415
  const frontmatterIssues = [];
1112
1416
  const enrichmentBuffer = /* @__PURE__ */ new Map();
1417
+ const contributionsBuffer = [];
1418
+ const freshlyRunTuples = /* @__PURE__ */ new Set();
1113
1419
  const extractorRuns = [];
1114
1420
  const sidecarRoots = /* @__PURE__ */ new Map();
1115
1421
  let filesWalked = 0;
@@ -1122,9 +1428,11 @@ async function walkAndExtract(opts) {
1122
1428
  if (list) list.push(qualified);
1123
1429
  else shortIdToQualified.set(ex.id, [qualified]);
1124
1430
  }
1431
+ const claimedPaths = /* @__PURE__ */ new Set();
1125
1432
  for (const provider of providers) {
1126
1433
  for await (const raw of resolveProviderWalk(provider)(roots, walkOptions)) {
1127
1434
  filesWalked += 1;
1435
+ if (claimedPaths.has(raw.path)) continue;
1128
1436
  const bodyHash = sha256(raw.body);
1129
1437
  const frontmatterHash = sha256(canonicalFrontmatter(raw.frontmatter, raw.frontmatterRaw));
1130
1438
  const priorNode = priorNodesByPath.get(raw.path);
@@ -1133,6 +1441,7 @@ async function walkAndExtract(opts) {
1133
1441
  if (kind === null) {
1134
1442
  continue;
1135
1443
  }
1444
+ claimedPaths.add(raw.path);
1136
1445
  index += 1;
1137
1446
  const cacheDecision = computeCacheDecision({
1138
1447
  extractors,
@@ -1160,8 +1469,6 @@ async function walkAndExtract(opts) {
1160
1469
  priorLinksByOriginating,
1161
1470
  priorFrontmatterIssuesByNode
1162
1471
  });
1163
- reused.node.stability = null;
1164
- reused.node.version = null;
1165
1472
  const reusedSidecarIssues = resolveAndApplySidecar(
1166
1473
  reused.node,
1167
1474
  raw.path,
@@ -1227,6 +1534,9 @@ async function walkAndExtract(opts) {
1227
1534
  ...partialCacheHit ? { partialCache: true } : {}
1228
1535
  }));
1229
1536
  const extractorsToRun = partialCacheHit ? missingExtractors : applicableExtractors;
1537
+ for (const ex of extractorsToRun) {
1538
+ freshlyRunTuples.add(`${ex.pluginId}/${ex.id}/${node.path}`);
1539
+ }
1230
1540
  const extractResult = await runExtractorsForNode({
1231
1541
  extractors: extractorsToRun,
1232
1542
  node,
@@ -1241,6 +1551,7 @@ async function walkAndExtract(opts) {
1241
1551
  for (const enr of extractResult.enrichments) {
1242
1552
  enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
1243
1553
  }
1554
+ for (const c of extractResult.contributions) contributionsBuffer.push(c);
1244
1555
  const ranAt = Date.now();
1245
1556
  for (const ex of applicableExtractors) {
1246
1557
  const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
@@ -1263,6 +1574,8 @@ async function walkAndExtract(opts) {
1263
1574
  filesWalked,
1264
1575
  enrichments: [...enrichmentBuffer.values()],
1265
1576
  extractorRuns,
1577
+ contributions: contributionsBuffer,
1578
+ freshlyRunTuples,
1266
1579
  orphanSidecars,
1267
1580
  sidecarRoots
1268
1581
  };
@@ -1293,30 +1606,80 @@ function reuseCachedLink(link, shortIdToQualified, cachedQualifiedIds, applicabl
1293
1606
  if (obsoleteSources.length === 0) return link;
1294
1607
  return { ...link, sources: cachedSources };
1295
1608
  }
1296
- async function runRules(rules, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, emitter, hookDispatcher) {
1609
+ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, emitter, hookDispatcher) {
1297
1610
  const issues = [];
1298
- const ruleOrphans = orphanSidecars.map((o) => ({
1611
+ const contributions = [];
1612
+ const validators = loadSchemaValidators();
1613
+ const analyzerOrphans = orphanSidecars.map((o) => ({
1299
1614
  relativePath: o.relativePath,
1300
1615
  expectedMdPath: o.expectedMdPath
1301
1616
  }));
1302
- for (const rule of rules) {
1303
- const emitted = await rule.evaluate({
1617
+ for (const analyzer of analyzers) {
1618
+ const qualifiedId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
1619
+ const declaredContributions = readDeclaredContributions(analyzer);
1620
+ const emitContribution = (nodePath, contributionId, payload) => {
1621
+ const declared = declaredContributions.get(contributionId);
1622
+ if (!declared) {
1623
+ emitExtensionError(emitter, qualifiedId, nodePath, {
1624
+ phase: "emitContribution",
1625
+ contributionId,
1626
+ reason: "unknown-contribution-id",
1627
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
1628
+ extractorId: qualifiedId,
1629
+ contributionId,
1630
+ nodePath
1631
+ })
1632
+ });
1633
+ return;
1634
+ }
1635
+ const result = validators.validateContributionPayload(declared.slot, payload);
1636
+ if (!result.ok) {
1637
+ emitExtensionError(emitter, qualifiedId, nodePath, {
1638
+ phase: "emitContribution",
1639
+ contributionId,
1640
+ slot: declared.slot,
1641
+ reason: result.errors,
1642
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
1643
+ extractorId: qualifiedId,
1644
+ contributionId,
1645
+ nodePath,
1646
+ slot: declared.slot,
1647
+ errors: result.errors
1648
+ })
1649
+ });
1650
+ return;
1651
+ }
1652
+ contributions.push({
1653
+ pluginId: analyzer.pluginId,
1654
+ extensionId: analyzer.id,
1655
+ nodePath,
1656
+ contributionId,
1657
+ slot: declared.slot,
1658
+ payload,
1659
+ emittedAt: Date.now()
1660
+ });
1661
+ };
1662
+ const emitted = await analyzer.evaluate({
1304
1663
  nodes,
1305
1664
  links: internalLinks,
1306
- orphanSidecars: ruleOrphans,
1665
+ orphanSidecars: analyzerOrphans,
1307
1666
  sidecarRoots,
1308
- annotationContributions
1667
+ annotationContributions,
1668
+ viewContributions,
1669
+ orphanJobFiles,
1670
+ ...referenceablePaths ? { referenceablePaths } : {},
1671
+ ...cwd ? { cwd } : {},
1672
+ emitContribution
1309
1673
  });
1310
1674
  for (const issue of emitted) {
1311
- const validated = validateIssue(rule, issue, emitter);
1675
+ const validated = validateIssue(analyzer, issue, emitter);
1312
1676
  if (validated) issues.push(validated);
1313
1677
  }
1314
- const ruleId = qualifiedExtensionId(rule.pluginId, rule.id);
1315
- const evt = makeEvent("rule.completed", { ruleId });
1678
+ const evt = makeEvent("analyzer.completed", { analyzerId: qualifiedId });
1316
1679
  emitter.emit(evt);
1317
- await hookDispatcher.dispatch("rule.completed", evt);
1680
+ await hookDispatcher.dispatch("analyzer.completed", evt);
1318
1681
  }
1319
- return issues;
1682
+ return { issues, contributions };
1320
1683
  }
1321
1684
  function originatingNodeOf(link, priorNodePaths) {
1322
1685
  if (link.kind === "supersedes" && !priorNodePaths.has(link.source)) {
@@ -1370,7 +1733,7 @@ function claimSingletonRenames(opts) {
1370
1733
  const fromPath = remaining[0];
1371
1734
  ops.push({ from: fromPath, to: toPath, confidence: "medium" });
1372
1735
  opts.issues.push({
1373
- ruleId: "auto-rename-medium",
1736
+ analyzerId: "auto-rename-medium",
1374
1737
  severity: "warn",
1375
1738
  nodeIds: [toPath],
1376
1739
  message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
@@ -1390,7 +1753,7 @@ function flagAmbiguousRenames(opts) {
1390
1753
  const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
1391
1754
  if (remaining.length > 1) {
1392
1755
  opts.issues.push({
1393
- ruleId: "auto-rename-ambiguous",
1756
+ analyzerId: "auto-rename-ambiguous",
1394
1757
  severity: "warn",
1395
1758
  nodeIds: [toPath],
1396
1759
  message: `Auto-rename ambiguous: ${toPath} matches ${remaining.length} prior frontmatters \u2014 pick one with \`sm orphans undo-rename ${toPath} --from <old.path>\`.`,
@@ -1403,7 +1766,7 @@ function flagOrphans(opts) {
1403
1766
  for (const fromPath of opts.deletedPaths) {
1404
1767
  if (opts.claimedDeleted.has(fromPath)) continue;
1405
1768
  opts.issues.push({
1406
- ruleId: "orphan",
1769
+ analyzerId: "orphan",
1407
1770
  severity: "info",
1408
1771
  nodeIds: [fromPath],
1409
1772
  message: `Orphan history: ${fromPath} was deleted; no rename match found.`,
@@ -1452,83 +1815,6 @@ var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
1452
1815
  function isExternalUrlLink(link) {
1453
1816
  return EXTERNAL_URL_SCHEME_RE.test(link.target);
1454
1817
  }
1455
- function makeEvent(type, data) {
1456
- return { type, timestamp: (/* @__PURE__ */ new Date()).toISOString(), data };
1457
- }
1458
- function makeHookDispatcher(hooks, emitter) {
1459
- if (hooks.length === 0) {
1460
- return { dispatch: async () => {
1461
- } };
1462
- }
1463
- const byTrigger = /* @__PURE__ */ new Map();
1464
- for (const hook of hooks) {
1465
- if (hook.mode === "probabilistic") {
1466
- const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
1467
- log.warn(
1468
- `Probabilistic hook ${qualifiedId} deferred to job subsystem (future job subsystem). The hook is registered but will not dispatch in-scan.`,
1469
- { hookId: qualifiedId, mode: "probabilistic" }
1470
- );
1471
- continue;
1472
- }
1473
- for (const trig of hook.triggers) {
1474
- const bucket = byTrigger.get(trig);
1475
- if (bucket) bucket.push(hook);
1476
- else byTrigger.set(trig, [hook]);
1477
- }
1478
- }
1479
- return {
1480
- async dispatch(trigger, event) {
1481
- const subs = byTrigger.get(trigger);
1482
- if (!subs || subs.length === 0) return;
1483
- for (const hook of subs) {
1484
- if (!matchesFilter(hook, event)) continue;
1485
- const ctx = buildHookContext(hook, trigger, event);
1486
- try {
1487
- await hook.on(ctx);
1488
- } catch (err) {
1489
- const qualifiedId = qualifiedExtensionId(hook.pluginId, hook.id);
1490
- const message = formatErrorMessage(err);
1491
- emitter.emit(
1492
- makeEvent("extension.error", {
1493
- kind: "hook-error",
1494
- extensionId: qualifiedId,
1495
- trigger,
1496
- message
1497
- })
1498
- );
1499
- }
1500
- }
1501
- }
1502
- };
1503
- }
1504
- function matchesFilter(hook, event) {
1505
- if (!hook.filter) return true;
1506
- const data = event.data ?? {};
1507
- for (const [key, expected] of Object.entries(hook.filter)) {
1508
- if (data[key] !== expected) return false;
1509
- }
1510
- return true;
1511
- }
1512
- function buildHookContext(_hook, trigger, event) {
1513
- const data = event.data ?? {};
1514
- const ctx = {
1515
- event: {
1516
- type: trigger,
1517
- timestamp: event.timestamp,
1518
- ...event.runId !== void 0 ? { runId: event.runId } : {},
1519
- ...event.jobId !== void 0 ? { jobId: event.jobId } : {},
1520
- data: event.data
1521
- }
1522
- };
1523
- if (typeof data["extractorId"] === "string") ctx.extractorId = data["extractorId"];
1524
- if (typeof data["ruleId"] === "string") ctx.ruleId = data["ruleId"];
1525
- if (typeof data["actionId"] === "string") ctx.actionId = data["actionId"];
1526
- if (data["node"] && typeof data["node"] === "object") {
1527
- ctx.node = data["node"];
1528
- }
1529
- if (data["jobResult"] !== void 0) ctx.jobResult = data["jobResult"];
1530
- return ctx;
1531
- }
1532
1818
  function buildNode(args) {
1533
1819
  const bytesFrontmatter = Buffer.byteLength(args.frontmatterRaw, "utf8");
1534
1820
  const bytesBody = Buffer.byteLength(args.body, "utf8");
@@ -1546,11 +1832,7 @@ function buildNode(args) {
1546
1832
  linksOutCount: 0,
1547
1833
  linksInCount: 0,
1548
1834
  externalRefsCount: 0,
1549
- frontmatter: args.frontmatter,
1550
- title: pickString(args.frontmatter["name"]),
1551
- description: pickString(args.frontmatter["description"]),
1552
- stability: null,
1553
- version: null
1835
+ frontmatter: args.frontmatter
1554
1836
  };
1555
1837
  if (args.encoder) {
1556
1838
  node.tokens = countTokens(args.encoder, args.frontmatterRaw, args.body);
@@ -1594,7 +1876,7 @@ function resolveAndApplySidecar(node, relativePath, roots, liveBodyHash, liveFro
1594
1876
  node.sidecar = { present: true, status: null, annotations: null, root: null };
1595
1877
  for (const parseIssue of result.issues) {
1596
1878
  issues.push({
1597
- ruleId: "invalid-sidecar",
1879
+ analyzerId: "invalid-sidecar",
1598
1880
  severity: "warn",
1599
1881
  nodeIds: [node.path],
1600
1882
  message: parseIssue.message,
@@ -1604,12 +1886,11 @@ function resolveAndApplySidecar(node, relativePath, roots, liveBodyHash, liveFro
1604
1886
  return issues;
1605
1887
  }
1606
1888
  const status = computeDriftStatus({
1607
- storedBodyHash: result.parsed.forBodyHash,
1608
- storedFrontmatterHash: result.parsed.forFrontmatterHash,
1889
+ storedBodyHash: result.parsed.identityBodyHash,
1890
+ storedFrontmatterHash: result.parsed.identityFrontmatterHash,
1609
1891
  liveBodyHash,
1610
1892
  liveFrontmatterHash
1611
1893
  });
1612
- applyAnnotationsOverlay(node, result.parsed);
1613
1894
  node.sidecar = {
1614
1895
  present: true,
1615
1896
  status,
@@ -1619,18 +1900,6 @@ function resolveAndApplySidecar(node, relativePath, roots, liveBodyHash, liveFro
1619
1900
  sidecarRoots.set(node.path, result.parsed.raw);
1620
1901
  return issues;
1621
1902
  }
1622
- function applyAnnotationsOverlay(node, parsed) {
1623
- const annotations = parsed.annotations;
1624
- if (annotations === null) return;
1625
- const stability = annotations["stability"];
1626
- if (stability === "experimental" || stability === "stable" || stability === "deprecated") {
1627
- node.stability = stability;
1628
- }
1629
- const version = annotations["version"];
1630
- if (typeof version === "number" && Number.isInteger(version) && version >= 1) {
1631
- node.version = version;
1632
- }
1633
- }
1634
1903
  function resolveAbsoluteMdPath(relativePath, roots) {
1635
1904
  if (isAbsolute2(relativePath)) {
1636
1905
  return existsSync6(relativePath) ? relativePath : null;
@@ -1650,10 +1919,7 @@ function relativePathFromRoots(absolutePath, roots) {
1650
1919
  }
1651
1920
  return absolutePath;
1652
1921
  }
1653
- function pickString(value) {
1654
- return typeof value === "string" && value.length > 0 ? value : null;
1655
- }
1656
- function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, store) {
1922
+ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
1657
1923
  const scope = extractor.scope;
1658
1924
  return {
1659
1925
  node,
@@ -1661,6 +1927,7 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
1661
1927
  frontmatter: scope === "body" ? {} : frontmatter,
1662
1928
  emitLink,
1663
1929
  enrichNode,
1930
+ emitContribution,
1664
1931
  ...store !== void 0 ? { store } : {}
1665
1932
  };
1666
1933
  }
@@ -1690,7 +1957,7 @@ function validateFrontmatter(providerFrontmatter, provider, kind, frontmatter, p
1690
1957
  const result = providerFrontmatter.validate(provider, kind, frontmatter);
1691
1958
  if (result.ok) return null;
1692
1959
  return {
1693
- ruleId: "frontmatter-invalid",
1960
+ analyzerId: "frontmatter-invalid",
1694
1961
  severity: strict ? "error" : "warn",
1695
1962
  nodeIds: [path],
1696
1963
  message: tx(ORCHESTRATOR_TEXTS.frontmatterInvalid, { path, kind, errors: result.errors }),
@@ -1701,7 +1968,7 @@ function detectMalformedFrontmatter(body, path, strict) {
1701
1968
  const hint = classifyMalformedFrontmatter(body);
1702
1969
  if (!hint) return null;
1703
1970
  return {
1704
- ruleId: "frontmatter-malformed",
1971
+ analyzerId: "frontmatter-malformed",
1705
1972
  severity: strict ? "error" : "warn",
1706
1973
  nodeIds: [path],
1707
1974
  message: malformedMessage(hint, path),
@@ -1735,25 +2002,25 @@ function malformedMessage(hint, path) {
1735
2002
  return tx(ORCHESTRATOR_TEXTS.frontmatterMalformedMissingClose, { path });
1736
2003
  }
1737
2004
  }
1738
- function validateIssue(rule, issue, emitter) {
2005
+ function validateIssue(analyzer, issue, emitter) {
1739
2006
  const severity = issue.severity;
1740
2007
  if (severity !== "error" && severity !== "warn" && severity !== "info") {
1741
- const qualifiedId = `${rule.pluginId}/${rule.id}`;
2008
+ const qualifiedId = `${analyzer.pluginId}/${analyzer.id}`;
1742
2009
  emitter.emit(
1743
2010
  makeEvent("extension.error", {
1744
2011
  kind: "issue-invalid-severity",
1745
2012
  extensionId: qualifiedId,
1746
2013
  severity,
1747
- issue: { ruleId: issue.ruleId || rule.id, message: issue.message, nodeIds: issue.nodeIds },
2014
+ issue: { analyzerId: issue.analyzerId || analyzer.id, message: issue.message, nodeIds: issue.nodeIds },
1748
2015
  message: tx(ORCHESTRATOR_TEXTS.extensionErrorIssueInvalidSeverity, {
1749
- ruleId: qualifiedId,
2016
+ analyzerId: qualifiedId,
1750
2017
  severity: JSON.stringify(severity)
1751
2018
  })
1752
2019
  })
1753
2020
  );
1754
2021
  return null;
1755
2022
  }
1756
- return { ...issue, ruleId: issue.ruleId || rule.id };
2023
+ return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
1757
2024
  }
1758
2025
  function recomputeLinkCounts(nodes, links) {
1759
2026
  const byPath2 = /* @__PURE__ */ new Map();
@@ -1990,10 +2257,10 @@ function diffIssues(priorIssues, currentIssues) {
1990
2257
  }
1991
2258
  function issueIdentity(issue) {
1992
2259
  const ids = [...issue.nodeIds].sort().join(",");
1993
- return `${issue.ruleId}\0${ids}\0${issue.message}`;
2260
+ return `${issue.analyzerId}\0${ids}\0${issue.message}`;
1994
2261
  }
1995
2262
  function byIssueSort(a, b) {
1996
- if (a.ruleId !== b.ruleId) return a.ruleId.localeCompare(b.ruleId);
2263
+ if (a.analyzerId !== b.analyzerId) return a.analyzerId.localeCompare(b.analyzerId);
1997
2264
  return a.message.localeCompare(b.message);
1998
2265
  }
1999
2266
 
@@ -2149,6 +2416,7 @@ function parseLogLevel(value) {
2149
2416
  // kernel/index.ts
2150
2417
  function createKernel() {
2151
2418
  let annotationKeys = Object.freeze([]);
2419
+ let viewContributions = Object.freeze([]);
2152
2420
  return {
2153
2421
  registry: new Registry(),
2154
2422
  getRegisteredAnnotationKeys() {
@@ -2156,6 +2424,12 @@ function createKernel() {
2156
2424
  },
2157
2425
  setRegisteredAnnotationKeys(entries) {
2158
2426
  annotationKeys = Object.freeze([...entries]);
2427
+ },
2428
+ getRegisteredViewContributions() {
2429
+ return viewContributions;
2430
+ },
2431
+ setRegisteredViewContributions(entries) {
2432
+ viewContributions = Object.freeze([...entries]);
2159
2433
  }
2160
2434
  };
2161
2435
  }
@@ -2181,6 +2455,8 @@ export {
2181
2455
  log,
2182
2456
  logLevelRank,
2183
2457
  makeDedicatedStoreWrapper,
2458
+ makeEvent,
2459
+ makeHookDispatcher,
2184
2460
  makeKvStoreWrapper,
2185
2461
  makePluginStore,
2186
2462
  mergeNodeWithEnrichments,