@skill-map/cli 0.18.0 → 0.19.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 (41) hide show
  1. package/README.md +4 -0
  2. package/dist/cli/tutorial/sm-tutorial.md +6 -7
  3. package/dist/cli.js +7011 -4047
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +277 -42
  7. package/dist/index.js.map +1 -1
  8. package/dist/kernel/index.d.ts +566 -68
  9. package/dist/kernel/index.js +277 -42
  10. package/dist/kernel/index.js.map +1 -1
  11. package/dist/migrations/001_initial.sql +122 -12
  12. package/dist/ui/chunk-7CAK6MVK.js +2638 -0
  13. package/dist/ui/chunk-BORRASJB.js +247 -0
  14. package/dist/ui/chunk-CZSS4D6J.js +454 -0
  15. package/dist/ui/chunk-EQD7AYYJ.js +227 -0
  16. package/dist/ui/chunk-ETTRVTFV.js +1 -0
  17. package/dist/ui/chunk-LFIE4SCX.js +965 -0
  18. package/dist/ui/chunk-OKO3QOH6.js +1 -0
  19. package/dist/ui/chunk-PMIMYHBM.js +61 -0
  20. package/dist/ui/chunk-UHFGCO24.js +1 -0
  21. package/dist/ui/chunk-VHIPW3TH.js +1 -0
  22. package/dist/ui/chunk-VWAUXWQX.js +237 -0
  23. package/dist/ui/index.html +10 -2
  24. package/dist/ui/main-BSYMJKTL.js +1 -0
  25. package/dist/ui/{styles-CBPFNGXA.css → styles-UAABA7VK.css} +1 -1
  26. package/migrations/001_initial.sql +122 -12
  27. package/package.json +2 -2
  28. package/dist/migrations/002_sidecar_columns.sql +0 -53
  29. package/dist/migrations/003_drop_node_author.sql +0 -20
  30. package/dist/migrations/004_sidecar_root_json.sql +0 -23
  31. package/dist/migrations/005_node_favorites.sql +0 -20
  32. package/dist/ui/chunk-JKJGGXCS.js +0 -1025
  33. package/dist/ui/chunk-SX2A3WBX.js +0 -247
  34. package/dist/ui/chunk-TWZHUCAT.js +0 -237
  35. package/dist/ui/chunk-WTAL2RK4.js +0 -1
  36. package/dist/ui/chunk-Z3UJHHTC.js +0 -3091
  37. package/dist/ui/main-AAYGMON4.js +0 -1
  38. package/migrations/002_sidecar_columns.sql +0 -53
  39. package/migrations/003_drop_node_author.sql +0 -20
  40. package/migrations/004_sidecar_root_json.sql +0 -23
  41. package/migrations/005_node_favorites.sql +0 -20
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { Confidence, DuplicateExtensionError, EXTENSION_KINDS, ExecutionFailureReason, ExecutionKind, ExecutionRecord, ExecutionRunner, ExecutionStatus, ExportQueryError, Extension, ExtensionKind, FilesystemPort, HOOK_TRIGGERS, HistoryStats, HistoryStatsErrorRates, HistoryStatsExecutionsPerPeriod, HistoryStatsPerActionRate, HistoryStatsTokensPerAction, HistoryStatsTopNode, HistoryStatsTotals, IAction, IActionContext, IActionPrecondition, IActionResult, IAnnotationContribution, ICreateFsWatcherOptions, IDedicatedStorePersist, IDedicatedStoreWrapper, IDiscoveredPlugin, IEnrichmentRecord, IExportQuery, IExportSubset, IExtensionBase, IExtractor, IExtractorCallbacks, IExtractorContext, IExtractorRunRecord, IFormatter, IFormatterContext, IFsWatcher, IHook, IHookContext, IIssueRow, IKvStorePersist, IKvStoreWrapper, ILoadedExtension, INodeBundle, INodeChange, INodeCounts, INodeFilter, IPersistOptions, IPersistedEnrichment, IPluginManifest, IPluginStorageSchema, IPluginStore, IProvider, IRawNode, IRegisteredAnnotationKey, IRule, IRuleContext, IRunOptions, IRunResult, IScanDelta, ITransactionalStorage, IWalkOptions, IWatchBatch, IWatchEvent, InMemoryProgressEmitter, Issue, IssueFix, KV_SCHEMA_KEY, Kernel, LOG_LEVELS, Link, LinkKind, LinkLocation, LinkTrigger, LogRecord, LoggerPort, Node, NodeKind, NodeStat, PluginLoaderPort, ProgressEmitterPort, ProgressEvent, Registry, RenameOp, RunScanOptions, RunnerPort, ScanResult, ScanScannedBy, ScanStats, Severity, SilentLogger, Stability, StoragePort, TActionWrite, TExecutionMode, TGranularity, THookFilter, THookTrigger, TLogLevel, TLogMethodLevel, TNodeChangeReason, TPluginLoadStatus, TPluginStorage, TProgressListener, TWatchEventKind, TripleSplit, applyExportQuery, computeScanDelta, configureLogger, createChokidarWatcher, createKernel, detectRenamesAndOrphans, getActiveLogger, isEmptyDelta, isLogLevel, log, logLevelRank, makeDedicatedStoreWrapper, makeKvStoreWrapper, makePluginStore, mergeNodeWithEnrichments, parseExportQuery, parseLogLevel, qualifiedExtensionId, resetLogger, runExtractorsForNode, runScan, runScanWithRenames } from './kernel/index.js';
1
+ export { Confidence, DuplicateExtensionError, EXTENSION_KINDS, ExecutionFailureReason, ExecutionKind, ExecutionRecord, ExecutionRunner, ExecutionStatus, ExportQueryError, Extension, ExtensionKind, FilesystemPort, HOOK_TRIGGERS, HistoryStats, HistoryStatsErrorRates, HistoryStatsExecutionsPerPeriod, HistoryStatsPerActionRate, HistoryStatsTokensPerAction, HistoryStatsTopNode, HistoryStatsTotals, IAction, IActionContext, IActionPrecondition, IActionResult, IAnnotationContribution, ICreateFsWatcherOptions, IDedicatedStorePersist, IDedicatedStoreWrapper, IDiscoveredPlugin, IEnrichmentRecord, IExportQuery, IExportSubset, IExtensionBase, IExtractor, IExtractorCallbacks, IExtractorContext, IExtractorRunRecord, IFormatter, IFormatterContext, IFsWatcher, IHook, IHookContext, IIssueRow, IKvStorePersist, IKvStoreWrapper, ILoadedExtension, INodeBundle, INodeChange, INodeCounts, INodeFilter, IPersistOptions, IPersistedEnrichment, IPluginManifest, IPluginStorageSchema, IPluginStore, IProvider, IRawNode, IRegisteredAnnotationKey, IRegisteredViewContribution, IRule, IRuleContext, IRunOptions, IRunResult, IScanDelta, ISettingDeclaration, ITransactionalStorage, IViewContribution, IWalkOptions, IWatchBatch, IWatchEvent, InMemoryProgressEmitter, Issue, IssueFix, KV_SCHEMA_KEY, Kernel, LOG_LEVELS, Link, LinkKind, LinkLocation, LinkTrigger, LogRecord, LoggerPort, Node, NodeKind, NodeStat, PluginLoaderPort, ProgressEmitterPort, ProgressEvent, Registry, RenameOp, RunScanOptions, RunnerPort, ScanResult, ScanScannedBy, ScanStats, Severity, SilentLogger, Stability, StoragePort, TActionWrite, TContractName, TExecutionMode, TGranularity, THookFilter, THookTrigger, TInputTypeName, TLogLevel, TLogMethodLevel, TNodeChangeReason, TPluginLoadStatus, TPluginStorage, TProgressListener, TSettingValue, TSeverity, TWatchEventKind, TripleSplit, applyExportQuery, computeScanDelta, configureLogger, createChokidarWatcher, createKernel, detectRenamesAndOrphans, getActiveLogger, isEmptyDelta, isLogLevel, log, logLevelRank, makeDedicatedStoreWrapper, makeKvStoreWrapper, makePluginStore, mergeNodeWithEnrichments, parseExportQuery, parseLogLevel, qualifiedExtensionId, resetLogger, runExtractorsForNode, runScan, runScanWithRenames } from './kernel/index.js';
package/dist/index.js CHANGED
@@ -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.19.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.19.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
  },
@@ -535,6 +535,118 @@ import { readFileSync as readFileSync4 } from "fs";
535
535
  import { dirname as dirname3, resolve as resolve4 } from "path";
536
536
  import { createRequire as createRequire4 } from "module";
537
537
  import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
538
+ var SCHEMA_FILES = {
539
+ node: "schemas/node.schema.json",
540
+ link: "schemas/link.schema.json",
541
+ issue: "schemas/issue.schema.json",
542
+ "scan-result": "schemas/scan-result.schema.json",
543
+ "execution-record": "schemas/execution-record.schema.json",
544
+ "project-config": "schemas/project-config.schema.json",
545
+ "plugins-registry": "schemas/plugins-registry.schema.json",
546
+ job: "schemas/job.schema.json",
547
+ "report-base": "schemas/report-base.schema.json",
548
+ "conformance-case": "schemas/conformance-case.schema.json",
549
+ "history-stats": "schemas/history-stats.schema.json",
550
+ "extension-provider": "schemas/extensions/provider.schema.json",
551
+ "extension-extractor": "schemas/extensions/extractor.schema.json",
552
+ "extension-rule": "schemas/extensions/rule.schema.json",
553
+ "extension-action": "schemas/extensions/action.schema.json",
554
+ "extension-formatter": "schemas/extensions/formatter.schema.json",
555
+ "extension-hook": "schemas/extensions/hook.schema.json",
556
+ "frontmatter-base": "schemas/frontmatter/base.schema.json"
557
+ };
558
+ var SUPPORTING_SCHEMAS = [
559
+ "schemas/extensions/base.schema.json",
560
+ "schemas/frontmatter/base.schema.json",
561
+ "schemas/summaries/security-scanner.schema.json",
562
+ "schemas/view-contracts.schema.json",
563
+ "schemas/input-types.schema.json"
564
+ ];
565
+ var cachedValidators = null;
566
+ function loadSchemaValidators() {
567
+ if (cachedValidators !== null) return cachedValidators;
568
+ cachedValidators = buildSchemaValidators();
569
+ return cachedValidators;
570
+ }
571
+ function buildSchemaValidators() {
572
+ const specRoot = resolveSpecRoot2();
573
+ const ajv = new Ajv20204({
574
+ strict: false,
575
+ allErrors: true,
576
+ allowUnionTypes: true
577
+ });
578
+ applyAjvFormats(ajv);
579
+ for (const rel of SUPPORTING_SCHEMAS) {
580
+ const file = resolve4(specRoot, rel);
581
+ if (!existsSyncSafe(file)) continue;
582
+ const schema = JSON.parse(readFileSync4(file, "utf8"));
583
+ ajv.addSchema(schema);
584
+ }
585
+ const validators = /* @__PURE__ */ new Map();
586
+ for (const [name, rel] of Object.entries(SCHEMA_FILES)) {
587
+ const file = resolve4(specRoot, rel);
588
+ const schema = JSON.parse(readFileSync4(file, "utf8"));
589
+ const byId = typeof schema.$id === "string" ? ajv.getSchema(schema.$id) : void 0;
590
+ validators.set(name, byId ?? ajv.compile(schema));
591
+ }
592
+ const extensionByKind = {
593
+ provider: "extension-provider",
594
+ extractor: "extension-extractor",
595
+ rule: "extension-rule",
596
+ action: "extension-action",
597
+ formatter: "extension-formatter",
598
+ hook: "extension-hook"
599
+ };
600
+ const pluginManifestValidator = ajv.compile({
601
+ $ref: "https://skill-map.dev/spec/v0/plugins-registry.schema.json#/$defs/PluginManifest"
602
+ });
603
+ const contributionValidators = /* @__PURE__ */ new Map();
604
+ const VIEW_CONTRACTS_ID = "https://skill-map.dev/spec/v0/view-contracts.schema.json";
605
+ function getContributionValidator(contract) {
606
+ const existing = contributionValidators.get(contract);
607
+ if (existing) return existing;
608
+ const ref = `${VIEW_CONTRACTS_ID}#/$defs/payloads/${contract}`;
609
+ let compiled;
610
+ try {
611
+ compiled = ajv.compile({ $ref: ref });
612
+ } catch {
613
+ return null;
614
+ }
615
+ contributionValidators.set(contract, compiled);
616
+ return compiled;
617
+ }
618
+ return {
619
+ getValidator(name) {
620
+ const v = validators.get(name);
621
+ if (!v) throw new Error(`Unknown schema: ${name}`);
622
+ return v;
623
+ },
624
+ validatorForExtension(kind) {
625
+ return validators.get(extensionByKind[kind]);
626
+ },
627
+ validate(name, data) {
628
+ const v = validators.get(name);
629
+ if (!v) throw new Error(`Unknown schema: ${name}`);
630
+ if (v(data)) return { ok: true, data };
631
+ const errors = (v.errors ?? []).map(formatError).join("; ");
632
+ return { ok: false, errors };
633
+ },
634
+ validatePluginManifest(data) {
635
+ if (pluginManifestValidator(data)) return { ok: true, data };
636
+ const errors = (pluginManifestValidator.errors ?? []).map(formatError).join("; ");
637
+ return { ok: false, errors };
638
+ },
639
+ validateContributionPayload(contract, payload) {
640
+ const validator = getContributionValidator(contract);
641
+ if (!validator) {
642
+ return { ok: false, errors: "unknown-contract" };
643
+ }
644
+ if (validator(payload)) return { ok: true };
645
+ const errors = (validator.errors ?? []).map(formatError).join("; ");
646
+ return { ok: false, errors };
647
+ }
648
+ };
649
+ }
538
650
  function buildProviderFrontmatterValidator(providers) {
539
651
  const specRoot = resolveSpecRoot2();
540
652
  const ajv = new Ajv20204({
@@ -592,6 +704,14 @@ function resolveSpecRoot2() {
592
704
  );
593
705
  }
594
706
  }
707
+ function existsSyncSafe(path) {
708
+ try {
709
+ readFileSync4(path, "utf8");
710
+ return true;
711
+ } catch {
712
+ return false;
713
+ }
714
+ }
595
715
 
596
716
  // kernel/i18n/orchestrator.texts.ts
597
717
  var ORCHESTRATOR_TEXTS = {
@@ -601,6 +721,8 @@ var ORCHESTRATOR_TEXTS = {
601
721
  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
722
  extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
603
723
  extensionErrorIssueInvalidSeverity: `Rule "{{ruleId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
724
+ extensionErrorContributionUnknownId: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}} but did not declare it in its `viewContributions` map. Contribution dropped.',
725
+ extensionErrorContributionPayloadInvalid: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}}; payload failed the "{{contract}}" schema: {{errors}}. Contribution dropped.',
604
726
  runScanRootEmptyArray: "runScan: roots must contain at least one path (spec requires minItems: 1)",
605
727
  runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
606
728
  };
@@ -856,16 +978,19 @@ async function runScanInternal(_kernel, options) {
856
978
  emitter.emit(evt);
857
979
  await hookDispatcher.dispatch("extractor.completed", evt);
858
980
  }
859
- const issues = await runRules(
981
+ const ruleResult = await runRules(
860
982
  exts.rules,
861
983
  walked.nodes,
862
984
  walked.internalLinks,
863
985
  walked.orphanSidecars,
864
986
  walked.sidecarRoots,
865
987
  options.annotationContributions ?? [],
988
+ options.viewContributions ?? [],
866
989
  emitter,
867
990
  hookDispatcher
868
991
  );
992
+ const issues = ruleResult.issues;
993
+ for (const c of ruleResult.contributions) walked.contributions.push(c);
869
994
  for (const issue of walked.frontmatterIssues) issues.push(issue);
870
995
  const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues) : [];
871
996
  const stats = {
@@ -899,7 +1024,8 @@ async function runScanInternal(_kernel, options) {
899
1024
  },
900
1025
  renameOps,
901
1026
  extractorRuns: walked.extractorRuns,
902
- enrichments: walked.enrichments
1027
+ enrichments: walked.enrichments,
1028
+ contributions: walked.contributions
903
1029
  };
904
1030
  }
905
1031
  function validateRoots(roots) {
@@ -944,9 +1070,10 @@ async function runExtractorsForNode(opts) {
944
1070
  const internalLinks = [];
945
1071
  const externalLinks = [];
946
1072
  const enrichmentBuffer = /* @__PURE__ */ new Map();
1073
+ const contributions = [];
1074
+ const validators = loadSchemaValidators();
947
1075
  for (const extractor of opts.extractors) {
948
1076
  const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
949
- const isProb = extractor.mode === "probabilistic";
950
1077
  const emitLink = (link) => {
951
1078
  const validated = validateLink(extractor, link, opts.emitter);
952
1079
  if (!validated) return;
@@ -966,10 +1093,55 @@ async function runExtractorsForNode(opts) {
966
1093
  bodyHashAtEnrichment: opts.bodyHash,
967
1094
  value: { ...partial },
968
1095
  enrichedAt: Date.now(),
969
- isProbabilistic: isProb
1096
+ // Extractors are deterministic-only; `is_probabilistic` is
1097
+ // reserved on the row for future Action-issued enrichments.
1098
+ isProbabilistic: false
970
1099
  });
971
1100
  }
972
1101
  };
1102
+ const declaredContributions = readDeclaredContributions(extractor);
1103
+ const emitContribution = (contributionId, payload) => {
1104
+ const declared = declaredContributions.get(contributionId);
1105
+ if (!declared) {
1106
+ emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
1107
+ phase: "emitContribution",
1108
+ contributionId,
1109
+ reason: "unknown-contribution-id",
1110
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
1111
+ extractorId: qualifiedId,
1112
+ contributionId,
1113
+ nodePath: opts.node.path
1114
+ })
1115
+ });
1116
+ return;
1117
+ }
1118
+ const result = validators.validateContributionPayload(declared.contract, payload);
1119
+ if (!result.ok) {
1120
+ emitExtensionError(opts.emitter, qualifiedId, opts.node.path, {
1121
+ phase: "emitContribution",
1122
+ contributionId,
1123
+ contract: declared.contract,
1124
+ reason: result.errors,
1125
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
1126
+ extractorId: qualifiedId,
1127
+ contributionId,
1128
+ nodePath: opts.node.path,
1129
+ contract: declared.contract,
1130
+ errors: result.errors
1131
+ })
1132
+ });
1133
+ return;
1134
+ }
1135
+ contributions.push({
1136
+ pluginId: extractor.pluginId,
1137
+ extensionId: extractor.id,
1138
+ nodePath: opts.node.path,
1139
+ contributionId,
1140
+ contract: declared.contract,
1141
+ payload,
1142
+ emittedAt: Date.now()
1143
+ });
1144
+ };
973
1145
  const store = opts.pluginStores?.get(extractor.pluginId);
974
1146
  const ctx = buildExtractorContext(
975
1147
  extractor,
@@ -978,6 +1150,7 @@ async function runExtractorsForNode(opts) {
978
1150
  opts.frontmatter,
979
1151
  emitLink,
980
1152
  enrichNode,
1153
+ emitContribution,
981
1154
  store
982
1155
  );
983
1156
  await extractor.extract(ctx);
@@ -985,9 +1158,32 @@ async function runExtractorsForNode(opts) {
985
1158
  return {
986
1159
  internalLinks,
987
1160
  externalLinks,
988
- enrichments: Array.from(enrichmentBuffer.values())
1161
+ enrichments: Array.from(enrichmentBuffer.values()),
1162
+ contributions
989
1163
  };
990
1164
  }
1165
+ function readDeclaredContributions(extension) {
1166
+ const out = /* @__PURE__ */ new Map();
1167
+ const raw = extension.viewContributions;
1168
+ if (typeof raw !== "object" || raw === null) return out;
1169
+ for (const [id, value] of Object.entries(raw)) {
1170
+ if (typeof value !== "object" || value === null) continue;
1171
+ const contract = value.contract;
1172
+ if (typeof contract !== "string") continue;
1173
+ out.set(id, { contract });
1174
+ }
1175
+ return out;
1176
+ }
1177
+ function emitExtensionError(emitter, qualifiedId, nodePath, data) {
1178
+ emitter.emit(
1179
+ makeEvent("extension.error", {
1180
+ kind: "contribution-rejected",
1181
+ extensionId: qualifiedId,
1182
+ nodePath,
1183
+ ...data
1184
+ })
1185
+ );
1186
+ }
991
1187
  function computeCacheDecision(opts) {
992
1188
  const applicableExtractors = opts.extractors.filter(
993
1189
  (ex) => ex.applicableKinds === void 0 || ex.applicableKinds.includes(opts.kind)
@@ -1110,6 +1306,7 @@ async function walkAndExtract(opts) {
1110
1306
  const cachedPaths = /* @__PURE__ */ new Set();
1111
1307
  const frontmatterIssues = [];
1112
1308
  const enrichmentBuffer = /* @__PURE__ */ new Map();
1309
+ const contributionsBuffer = [];
1113
1310
  const extractorRuns = [];
1114
1311
  const sidecarRoots = /* @__PURE__ */ new Map();
1115
1312
  let filesWalked = 0;
@@ -1122,9 +1319,11 @@ async function walkAndExtract(opts) {
1122
1319
  if (list) list.push(qualified);
1123
1320
  else shortIdToQualified.set(ex.id, [qualified]);
1124
1321
  }
1322
+ const claimedPaths = /* @__PURE__ */ new Set();
1125
1323
  for (const provider of providers) {
1126
1324
  for await (const raw of resolveProviderWalk(provider)(roots, walkOptions)) {
1127
1325
  filesWalked += 1;
1326
+ if (claimedPaths.has(raw.path)) continue;
1128
1327
  const bodyHash = sha256(raw.body);
1129
1328
  const frontmatterHash = sha256(canonicalFrontmatter(raw.frontmatter, raw.frontmatterRaw));
1130
1329
  const priorNode = priorNodesByPath.get(raw.path);
@@ -1133,6 +1332,7 @@ async function walkAndExtract(opts) {
1133
1332
  if (kind === null) {
1134
1333
  continue;
1135
1334
  }
1335
+ claimedPaths.add(raw.path);
1136
1336
  index += 1;
1137
1337
  const cacheDecision = computeCacheDecision({
1138
1338
  extractors,
@@ -1160,8 +1360,6 @@ async function walkAndExtract(opts) {
1160
1360
  priorLinksByOriginating,
1161
1361
  priorFrontmatterIssuesByNode
1162
1362
  });
1163
- reused.node.stability = null;
1164
- reused.node.version = null;
1165
1363
  const reusedSidecarIssues = resolveAndApplySidecar(
1166
1364
  reused.node,
1167
1365
  raw.path,
@@ -1241,6 +1439,7 @@ async function walkAndExtract(opts) {
1241
1439
  for (const enr of extractResult.enrichments) {
1242
1440
  enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
1243
1441
  }
1442
+ for (const c of extractResult.contributions) contributionsBuffer.push(c);
1244
1443
  const ranAt = Date.now();
1245
1444
  for (const ex of applicableExtractors) {
1246
1445
  const qualified = qualifiedExtensionId(ex.pluginId, ex.id);
@@ -1263,6 +1462,7 @@ async function walkAndExtract(opts) {
1263
1462
  filesWalked,
1264
1463
  enrichments: [...enrichmentBuffer.values()],
1265
1464
  extractorRuns,
1465
+ contributions: contributionsBuffer,
1266
1466
  orphanSidecars,
1267
1467
  sidecarRoots
1268
1468
  };
@@ -1293,30 +1493,77 @@ function reuseCachedLink(link, shortIdToQualified, cachedQualifiedIds, applicabl
1293
1493
  if (obsoleteSources.length === 0) return link;
1294
1494
  return { ...link, sources: cachedSources };
1295
1495
  }
1296
- async function runRules(rules, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, emitter, hookDispatcher) {
1496
+ async function runRules(rules, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, emitter, hookDispatcher) {
1297
1497
  const issues = [];
1498
+ const contributions = [];
1499
+ const validators = loadSchemaValidators();
1298
1500
  const ruleOrphans = orphanSidecars.map((o) => ({
1299
1501
  relativePath: o.relativePath,
1300
1502
  expectedMdPath: o.expectedMdPath
1301
1503
  }));
1302
1504
  for (const rule of rules) {
1505
+ const qualifiedId = qualifiedExtensionId(rule.pluginId, rule.id);
1506
+ const declaredContributions = readDeclaredContributions(rule);
1507
+ const emitContribution = (nodePath, contributionId, payload) => {
1508
+ const declared = declaredContributions.get(contributionId);
1509
+ if (!declared) {
1510
+ emitExtensionError(emitter, qualifiedId, nodePath, {
1511
+ phase: "emitContribution",
1512
+ contributionId,
1513
+ reason: "unknown-contribution-id",
1514
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUnknownId, {
1515
+ extractorId: qualifiedId,
1516
+ contributionId,
1517
+ nodePath
1518
+ })
1519
+ });
1520
+ return;
1521
+ }
1522
+ const result = validators.validateContributionPayload(declared.contract, payload);
1523
+ if (!result.ok) {
1524
+ emitExtensionError(emitter, qualifiedId, nodePath, {
1525
+ phase: "emitContribution",
1526
+ contributionId,
1527
+ contract: declared.contract,
1528
+ reason: result.errors,
1529
+ message: tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
1530
+ extractorId: qualifiedId,
1531
+ contributionId,
1532
+ nodePath,
1533
+ contract: declared.contract,
1534
+ errors: result.errors
1535
+ })
1536
+ });
1537
+ return;
1538
+ }
1539
+ contributions.push({
1540
+ pluginId: rule.pluginId,
1541
+ extensionId: rule.id,
1542
+ nodePath,
1543
+ contributionId,
1544
+ contract: declared.contract,
1545
+ payload,
1546
+ emittedAt: Date.now()
1547
+ });
1548
+ };
1303
1549
  const emitted = await rule.evaluate({
1304
1550
  nodes,
1305
1551
  links: internalLinks,
1306
1552
  orphanSidecars: ruleOrphans,
1307
1553
  sidecarRoots,
1308
- annotationContributions
1554
+ annotationContributions,
1555
+ viewContributions,
1556
+ emitContribution
1309
1557
  });
1310
1558
  for (const issue of emitted) {
1311
1559
  const validated = validateIssue(rule, issue, emitter);
1312
1560
  if (validated) issues.push(validated);
1313
1561
  }
1314
- const ruleId = qualifiedExtensionId(rule.pluginId, rule.id);
1315
- const evt = makeEvent("rule.completed", { ruleId });
1562
+ const evt = makeEvent("rule.completed", { ruleId: qualifiedId });
1316
1563
  emitter.emit(evt);
1317
1564
  await hookDispatcher.dispatch("rule.completed", evt);
1318
1565
  }
1319
- return issues;
1566
+ return { issues, contributions };
1320
1567
  }
1321
1568
  function originatingNodeOf(link, priorNodePaths) {
1322
1569
  if (link.kind === "supersedes" && !priorNodePaths.has(link.source)) {
@@ -1546,11 +1793,7 @@ function buildNode(args) {
1546
1793
  linksOutCount: 0,
1547
1794
  linksInCount: 0,
1548
1795
  externalRefsCount: 0,
1549
- frontmatter: args.frontmatter,
1550
- title: pickString(args.frontmatter["name"]),
1551
- description: pickString(args.frontmatter["description"]),
1552
- stability: null,
1553
- version: null
1796
+ frontmatter: args.frontmatter
1554
1797
  };
1555
1798
  if (args.encoder) {
1556
1799
  node.tokens = countTokens(args.encoder, args.frontmatterRaw, args.body);
@@ -1604,12 +1847,11 @@ function resolveAndApplySidecar(node, relativePath, roots, liveBodyHash, liveFro
1604
1847
  return issues;
1605
1848
  }
1606
1849
  const status = computeDriftStatus({
1607
- storedBodyHash: result.parsed.forBodyHash,
1608
- storedFrontmatterHash: result.parsed.forFrontmatterHash,
1850
+ storedBodyHash: result.parsed.identityBodyHash,
1851
+ storedFrontmatterHash: result.parsed.identityFrontmatterHash,
1609
1852
  liveBodyHash,
1610
1853
  liveFrontmatterHash
1611
1854
  });
1612
- applyAnnotationsOverlay(node, result.parsed);
1613
1855
  node.sidecar = {
1614
1856
  present: true,
1615
1857
  status,
@@ -1619,18 +1861,6 @@ function resolveAndApplySidecar(node, relativePath, roots, liveBodyHash, liveFro
1619
1861
  sidecarRoots.set(node.path, result.parsed.raw);
1620
1862
  return issues;
1621
1863
  }
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
1864
  function resolveAbsoluteMdPath(relativePath, roots) {
1635
1865
  if (isAbsolute2(relativePath)) {
1636
1866
  return existsSync6(relativePath) ? relativePath : null;
@@ -1650,10 +1880,7 @@ function relativePathFromRoots(absolutePath, roots) {
1650
1880
  }
1651
1881
  return absolutePath;
1652
1882
  }
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) {
1883
+ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
1657
1884
  const scope = extractor.scope;
1658
1885
  return {
1659
1886
  node,
@@ -1661,6 +1888,7 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
1661
1888
  frontmatter: scope === "body" ? {} : frontmatter,
1662
1889
  emitLink,
1663
1890
  enrichNode,
1891
+ emitContribution,
1664
1892
  ...store !== void 0 ? { store } : {}
1665
1893
  };
1666
1894
  }
@@ -2149,6 +2377,7 @@ function parseLogLevel(value) {
2149
2377
  // kernel/index.ts
2150
2378
  function createKernel() {
2151
2379
  let annotationKeys = Object.freeze([]);
2380
+ let viewContributions = Object.freeze([]);
2152
2381
  return {
2153
2382
  registry: new Registry(),
2154
2383
  getRegisteredAnnotationKeys() {
@@ -2156,6 +2385,12 @@ function createKernel() {
2156
2385
  },
2157
2386
  setRegisteredAnnotationKeys(entries) {
2158
2387
  annotationKeys = Object.freeze([...entries]);
2388
+ },
2389
+ getRegisteredViewContributions() {
2390
+ return viewContributions;
2391
+ },
2392
+ setRegisteredViewContributions(entries) {
2393
+ viewContributions = Object.freeze([...entries]);
2159
2394
  }
2160
2395
  };
2161
2396
  }