@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
@@ -25,7 +25,7 @@
25
25
  * implements `StoragePort`).
26
26
  *
27
27
  * 3. **Runtime extension contracts** — what a plugin author
28
- * implements: `IProvider`, `IExtractor`, `IRule`, `IFormatter`,
28
+ * implements: `IProvider`, `IExtractor`, `IAnalyzer`, `IFormatter`,
29
29
  * `IExtensionBase`. **`I` prefix.** The prefix flags "this is a
30
30
  * contract you supply, not a value the kernel hands you" — same
31
31
  * reading as the rest of TypeScript's plugin ecosystems where a
@@ -151,17 +151,6 @@ interface Node {
151
151
  linksOutCount: number;
152
152
  linksInCount: number;
153
153
  externalRefsCount: number;
154
- title?: string | null;
155
- description?: string | null;
156
- stability?: Stability | null;
157
- /**
158
- * Monotonic version counter sourced from the sidecar's
159
- * `annotations.version` (Step 9.6.2). `null` when no sidecar accompanies
160
- * the node, or when the sidecar omits `version`. Pre-9.6.2 the field
161
- * was a semver string sourced from `frontmatter.metadata.version`;
162
- * see migration `002_sidecar_columns.sql` and Decision #125.
163
- */
164
- version?: number | null;
165
154
  frontmatter?: Record<string, unknown>;
166
155
  tokens?: TripleSplit;
167
156
  /**
@@ -237,7 +226,7 @@ interface IssueFix {
237
226
  autofixable?: boolean;
238
227
  }
239
228
  interface Issue {
240
- ruleId: string;
229
+ analyzerId: string;
241
230
  severity: Severity;
242
231
  nodeIds: string[];
243
232
  message: string;
@@ -381,12 +370,12 @@ interface ScanResult {
381
370
  * Extension registry — six kinds, first-class, loaded through a single API.
382
371
  *
383
372
  * The `Extension` shape is aligned with `spec/schemas/extensions/base.schema.json`.
384
- * Kind-specific manifests (provider / extractor / rule / action / formatter /
373
+ * Kind-specific manifests (provider / extractor / analyzer / action / formatter /
385
374
  * hook) extend this base structurally; the registry stores the base view
386
375
  * and each kind's code carries its own fuller type where needed.
387
376
  *
388
377
  * **Spec § A.6 — qualified ids.** Every extension is keyed in the registry
389
- * by `<pluginId>/<id>` (e.g. `core/frontmatter`, `claude/slash`,
378
+ * by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash`,
390
379
  * `hello-world/greet`). `Extension.id` carries the **short** id as authored;
391
380
  * `Extension.pluginId` carries the namespace; the registry composes the
392
381
  * qualifier internally and exposes lookup APIs that operate on either form
@@ -397,7 +386,7 @@ interface ScanResult {
397
386
  * `kernel-empty-boot` conformance contract.
398
387
  */
399
388
 
400
- type ExtensionKind = 'provider' | 'extractor' | 'rule' | 'action' | 'formatter' | 'hook';
389
+ type ExtensionKind = 'provider' | 'extractor' | 'analyzer' | 'action' | 'formatter' | 'hook';
401
390
  declare const EXTENSION_KINDS: readonly ExtensionKind[];
402
391
  interface Extension {
403
392
  /** Short (unqualified) extension id as declared in the manifest. */
@@ -443,7 +432,7 @@ declare class Registry {
443
432
  * Step 9.6.6 — runtime annotation-contribution catalog types.
444
433
  *
445
434
  * Lives in its own module (rather than `kernel/index.ts`) so consumers
446
- * deep inside the kernel — `IRuleContext`, the BFF route factories,
435
+ * deep inside the kernel — `IAnalyzerContext`, the BFF route factories,
447
436
  * future Action contexts — can depend on the catalog shape without
448
437
  * dragging the whole kernel barrel and risking a cycle.
449
438
  */
@@ -463,6 +452,230 @@ interface IRegisteredAnnotationKey {
463
452
  schema: Record<string, unknown>;
464
453
  }
465
454
 
455
+ /**
456
+ * Step 11.x — runtime view-contribution catalog types.
457
+ *
458
+ * Lives in its own module (rather than `kernel/index.ts`) so consumers
459
+ * deep inside the kernel — `IAnalyzerContext`, the BFF route factories,
460
+ * future Action contexts — can depend on the catalog shape without
461
+ * dragging the whole kernel barrel and risking a cycle.
462
+ *
463
+ * Mirrors `annotation-catalog.ts` for the annotation contribution side
464
+ * (Step 9.6.6). The two systems share the "plugin contributes data,
465
+ * kernel exposes catalog, UI renders" pattern but never overlap in
466
+ * storage or routing — see `architecture.md` §View contribution system
467
+ * for the comparison table.
468
+ *
469
+ * **Closed catalog by design.** Both `TSlotName` and `TInputTypeName`
470
+ * mirror the closed enums in `spec/schemas/view-slots.schema.json`
471
+ * and `spec/schemas/input-types.schema.json`. Adding a member is a
472
+ * coordinated kernel + spec + UI + scaffolder change. The closed-enum
473
+ * shape lets TypeScript surface unknown slots at author time
474
+ * (in plugin authors' editors when their plugin imports `@skill-map/cli`)
475
+ * AND lets the runtime exhaustively dispatch slot → renderer in the
476
+ * UI without `default:` fallbacks.
477
+ */
478
+ /**
479
+ * Closed enum of view slot names. Mirror of
480
+ * `spec/schemas/view-slots.schema.json#/$defs/SlotName`.
481
+ *
482
+ * Plugins pick one of these by name in their extension manifest's
483
+ * `viewContributions[<contributionId>].slot` field. The kernel
484
+ * validates each pick at load time (`invalid-manifest` on miss); the
485
+ * slot fixes both the renderer and the payload shape.
486
+ */
487
+ type TSlotName = 'card.title.right' | 'card.subtitle.left' | 'card.footer.left.counter' | 'card.footer.right' | 'graph.node.alert' | 'inspector.header.badge.counter' | 'inspector.header.badge.tag' | 'inspector.body.panel.breakdown' | 'inspector.body.panel.records' | 'inspector.body.panel.tree' | 'inspector.body.panel.key-values' | 'inspector.body.panel.link-list' | 'inspector.body.panel.markdown' | 'topbar.actions.indicator';
488
+ /**
489
+ * Closed enum of input-type names for plugin settings. Mirror of
490
+ * `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
491
+ *
492
+ * Plugins pick one of these by name in their plugin manifest's
493
+ * `settings[<settingId>].type` field. The kernel exposes the resolved
494
+ * value via `ctx.settings.<settingId>` typed per the input-type's
495
+ * value-type promise.
496
+ */
497
+ type TInputTypeName = 'string-list' | 'single-string' | 'boolean-flag' | 'integer' | 'enum-pick' | 'enum-multipick' | 'path-glob' | 'regex' | 'secret' | 'key-value-list';
498
+ /** Closed severity palette aligned with PrimeNG `<p-tag>` / `<p-message>`. */
499
+ type TSeverity = 'info' | 'warn' | 'success' | 'danger';
500
+ /**
501
+ * Manifest-side declaration of a single view contribution. The plugin
502
+ * author writes one of these per Record key in
503
+ * `IExtensionBase.viewContributions[<contributionId>]`.
504
+ *
505
+ * Mirror of `view-slots.schema.json#/$defs/IViewContribution`.
506
+ */
507
+ interface IViewContribution {
508
+ /**
509
+ * Required. Closed-catalog slot name. Unknown name rejects the
510
+ * extension as `invalid-manifest` at load. The slot fixes both the
511
+ * renderer and the payload shape; there is no separate "contract"
512
+ * abstraction.
513
+ */
514
+ slot: TSlotName;
515
+ /**
516
+ * Optional human-readable label. English-only per `AGENTS.md`
517
+ * (`Externalized texts, not internationalized`).
518
+ */
519
+ label?: string;
520
+ /** Optional hover tooltip. English-only. */
521
+ tooltip?: string;
522
+ /**
523
+ * Optional emoji codepoint OR PrimeIcons class id (without the
524
+ * `pi-` prefix). The UI discriminates: matches Unicode
525
+ * `\p{Extended_Pictographic}` → emoji text, otherwise → PrimeIcon.
526
+ * Required for counter slots and `card.title.right` (enforced by
527
+ * the manifest-side conditional in `view-slots.schema.json`).
528
+ */
529
+ icon?: string;
530
+ /**
531
+ * Optional empty placeholder text shown when the payload is empty
532
+ * AND `emitWhenEmpty` is true. Falls back to a UI-supplied generic
533
+ * 'No data.' string. English-only.
534
+ */
535
+ emptyText?: string;
536
+ /**
537
+ * When false (default), the kernel drops emissions whose payload is
538
+ * structurally empty so the slot stays silent. When true, the
539
+ * renderer surfaces an empty placeholder. Per-slot definition of
540
+ * "empty" lives in the slot's payload schema.
541
+ */
542
+ emitWhenEmpty?: boolean;
543
+ /**
544
+ * Optional ordering hint (default 100). Slots configured with
545
+ * `order: 'priority'` sort contributions ASC by this value, with
546
+ * alphabetical tie-break by qualified id. The plugin uses this to
547
+ * suggest where its contribution belongs relative to others sharing
548
+ * the same slot — the slot has the final say.
549
+ */
550
+ priority?: number;
551
+ }
552
+ /**
553
+ * Single row of the runtime view-contribution catalog surfaced by
554
+ * `kernel.getRegisteredViewContributions()`. One row per
555
+ * `(pluginId × extensionId × contributionId)` tuple. Composed at boot
556
+ * by `loadPluginRuntime` from every loaded extension's
557
+ * `viewContributions` map.
558
+ *
559
+ * The qualified id is `<pluginId>/<extensionId>/<contributionId>` —
560
+ * matches the qualified id pattern used elsewhere in the kernel
561
+ * (`<pluginId>/<extensionId>` for extensions; this adds the third
562
+ * segment for per-contribution identity).
563
+ */
564
+ interface IRegisteredViewContribution {
565
+ pluginId: string;
566
+ extensionId: string;
567
+ contributionId: string;
568
+ slot: TSlotName;
569
+ /** Optional manifest-declared label (English-only). */
570
+ label?: string;
571
+ tooltip?: string;
572
+ icon?: string;
573
+ emptyText?: string;
574
+ emitWhenEmpty: boolean;
575
+ /** Manifest-declared ordering hint (default 100). See `IViewContribution.priority`. */
576
+ priority?: number;
577
+ }
578
+ /**
579
+ * Common fields on every setting declaration. The discriminated union
580
+ * `ISettingDeclaration` extends one of these per `type` value.
581
+ */
582
+ interface ISettingCommon {
583
+ /** Required. Short human-readable label. English-only. */
584
+ label: string;
585
+ /** Optional helper text shown below the control. English-only. */
586
+ description?: string;
587
+ }
588
+ interface ISetting_StringList extends ISettingCommon {
589
+ type: 'string-list';
590
+ default?: string[];
591
+ min?: number;
592
+ max?: number;
593
+ itemMaxLength?: number;
594
+ }
595
+ interface ISetting_SingleString extends ISettingCommon {
596
+ type: 'single-string';
597
+ default?: string;
598
+ minLength?: number;
599
+ maxLength?: number;
600
+ /** Optional ECMAScript regex pattern (no flags). */
601
+ pattern?: string;
602
+ }
603
+ interface ISetting_BooleanFlag extends ISettingCommon {
604
+ type: 'boolean-flag';
605
+ default?: boolean;
606
+ }
607
+ interface ISetting_Integer extends ISettingCommon {
608
+ type: 'integer';
609
+ default?: number;
610
+ min?: number;
611
+ max?: number;
612
+ step?: number;
613
+ }
614
+ interface ISetting_EnumOption {
615
+ value: string;
616
+ label: string;
617
+ }
618
+ interface ISetting_EnumPick extends ISettingCommon {
619
+ type: 'enum-pick';
620
+ options: ISetting_EnumOption[];
621
+ default?: string;
622
+ }
623
+ interface ISetting_EnumMultipick extends ISettingCommon {
624
+ type: 'enum-multipick';
625
+ options: ISetting_EnumOption[];
626
+ default?: string[];
627
+ min?: number;
628
+ max?: number;
629
+ }
630
+ interface ISetting_PathGlob extends ISettingCommon {
631
+ type: 'path-glob';
632
+ default?: string;
633
+ /** When true, accepts string[]; when false (default), single string. */
634
+ multiple?: boolean;
635
+ }
636
+ interface ISetting_Regex extends ISettingCommon {
637
+ type: 'regex';
638
+ default?: string;
639
+ /** Subset of `gimsuy`. Default `''`. */
640
+ flags?: string;
641
+ }
642
+ interface ISetting_Secret extends ISettingCommon {
643
+ type: 'secret';
644
+ /**
645
+ * Optional uppercase-ASCII identifier. When set in the process
646
+ * environment, that value wins over any stored value (lets CI
647
+ * inject without writing to disk).
648
+ */
649
+ envVar?: string;
650
+ }
651
+ interface ISetting_KeyValueListEntry {
652
+ key: string;
653
+ value: string;
654
+ }
655
+ interface ISetting_KeyValueList extends ISettingCommon {
656
+ type: 'key-value-list';
657
+ keyLabel?: string;
658
+ valueLabel?: string;
659
+ default?: ISetting_KeyValueListEntry[];
660
+ min?: number;
661
+ max?: number;
662
+ }
663
+ /**
664
+ * Discriminated union of every setting declaration shape. The plugin
665
+ * author NEVER writes JSON Schema for settings — they pick one of
666
+ * these `type` values and supply per-type parameters.
667
+ *
668
+ * Mirror of `input-types.schema.json#/$defs/ISettingDeclaration`.
669
+ */
670
+ type ISettingDeclaration = ISetting_StringList | ISetting_SingleString | ISetting_BooleanFlag | ISetting_Integer | ISetting_EnumPick | ISetting_EnumMultipick | ISetting_PathGlob | ISetting_Regex | ISetting_Secret | ISetting_KeyValueList;
671
+ /**
672
+ * Runtime value type for a setting, derived from its declaration. The
673
+ * kernel exposes settings to extractors as `Record<string, TSettingValue>`
674
+ * via `ctx.settings.<settingId>`; consumers that want narrow typing
675
+ * narrow at the call site by reading `manifest.settings[id].type`.
676
+ */
677
+ type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueListEntry[];
678
+
466
679
  /**
467
680
  * Base manifest shape shared by every extension kind. Mirrors
468
681
  * `spec/schemas/extensions/base.schema.json` at the TypeScript level.
@@ -529,6 +742,21 @@ interface IExtensionBase {
529
742
  * `plugin-author-guide.md` §Annotation contributions for examples.
530
743
  */
531
744
  annotationContributions?: Record<string, IAnnotationContribution>;
745
+ /**
746
+ * Plugin-contributed view contributions. Each entry maps a local
747
+ * contribution id (kebab-case, unique within the extension) to a
748
+ * `IViewContribution` declaration that picks a view slot by name
749
+ * from the closed kernel catalog (`view-catalog.ts#TSlotName`).
750
+ * The slot fixes both the renderer and the payload shape — there
751
+ * is no separate "contract" abstraction. The kernel validates each
752
+ * `slot` pick at load time (`invalid-manifest` on miss); the plugin
753
+ * emits per-node payloads via `ctx.emitContribution(<contributionId>,
754
+ * payload)` during scan; the runtime validates payloads against
755
+ * the slot's payload schema. The aggregate runtime catalog is
756
+ * exposed via `kernel.getRegisteredViewContributions()`. See
757
+ * `architecture.md` §View contribution system.
758
+ */
759
+ viewContributions?: Record<string, IViewContribution>;
532
760
  }
533
761
 
534
762
  /**
@@ -643,6 +871,16 @@ interface IPluginManifest {
643
871
  id: string;
644
872
  version: string;
645
873
  specCompat: string;
874
+ /**
875
+ * Optional semver range against the kernel's view-slots +
876
+ * input-types catalog version. Independent from `specCompat` because
877
+ * the catalog evolves on its own cadence (see `architecture.md`
878
+ * §View contribution system → Catalog versioning). Mismatch surfaces
879
+ * as `incompatible-catalog`. Absent = the plugin opts out of catalog
880
+ * checking; `sm plugins doctor` warns if such a plugin actually
881
+ * declares `viewContributions` or `settings`.
882
+ */
883
+ catalogCompat?: string;
646
884
  extensions: string[];
647
885
  description?: string;
648
886
  storage?: TPluginStorage;
@@ -652,6 +890,18 @@ interface IPluginManifest {
652
890
  * the default.
653
891
  */
654
892
  granularity?: TGranularity;
893
+ /**
894
+ * Plugin user-configurable settings. Each entry picks an `input-type`
895
+ * from the closed catalog at
896
+ * `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
897
+ * The plugin author NEVER writes JSON Schema — they pick `type` by
898
+ * name and supply per-type parameters. The kernel exposes resolved
899
+ * settings to extractors via `ctx.settings.<settingId>`; settings
900
+ * are read once at extractor invocation; changing a setting requires
901
+ * `sm scan` to re-emit. See `architecture.md` §View contribution
902
+ * system → Settings.
903
+ */
904
+ settings?: Record<string, ISettingDeclaration>;
655
905
  author?: string;
656
906
  license?: string;
657
907
  homepage?: string;
@@ -692,7 +942,7 @@ interface IPluginManifest {
692
942
  * precedence rule applies. The user resolves
693
943
  * by renaming one of them and rerunning.
694
944
  */
695
- type TPluginLoadStatus = 'enabled' | 'disabled' | 'incompatible-spec' | 'invalid-manifest' | 'load-error' | 'id-collision';
945
+ type TPluginLoadStatus = 'enabled' | 'disabled' | 'incompatible-spec' | 'incompatible-catalog' | 'invalid-manifest' | 'load-error' | 'id-collision';
696
946
  interface ILoadedExtension {
697
947
  kind: ExtensionKind;
698
948
  id: string;
@@ -884,6 +1134,63 @@ declare function makePluginStore(opts: {
884
1134
  persistDedicated?: IDedicatedStorePersist;
885
1135
  }): IPluginStore | undefined;
886
1136
 
1137
+ /**
1138
+ * `scan_contributions` adapter — replace-all writer used by
1139
+ * `persistScanResult`, plus read helpers consumed by the BFF
1140
+ * (`/api/contributions/...`) and rules (`core/contribution-orphan`).
1141
+ *
1142
+ * One row per `(plugin_id, extension_id, node_path, contribution_id)`
1143
+ * tuple. See `spec/architecture.md` § View contribution system →
1144
+ * Persistence and `migrations/001_initial.sql` § View contribution
1145
+ * layer for the normative shape.
1146
+ *
1147
+ * Replace-all semantics mirror the rest of the `scan_*` zone: every
1148
+ * scan is a fresh snapshot, so prior rows are deleted before insert.
1149
+ * Wrapped in the same transaction `persistScanResult` opens.
1150
+ *
1151
+ * The rename heuristic does NOT need to migrate `node_path` here —
1152
+ * because of replace-all, every contribution is re-emitted on the new
1153
+ * path automatically. Keeping the rename path lighter than `state_*`
1154
+ * (which IS rename-migrated because state survives across scans).
1155
+ */
1156
+
1157
+ /**
1158
+ * In-memory contribution record buffered during scan and flushed to
1159
+ * `scan_contributions` by `persistScanResult`. One entry per accepted
1160
+ * `ctx.emitContribution(id, payload)` call. Payload validation against
1161
+ * the slot's payload schema happens at emit time (orchestrator);
1162
+ * by the time records reach this adapter they are wire-shape clean.
1163
+ */
1164
+ interface IContributionRecord {
1165
+ pluginId: string;
1166
+ extensionId: string;
1167
+ nodePath: string;
1168
+ contributionId: string;
1169
+ /**
1170
+ * Closed enum value mirroring `view-slots.schema.json#/$defs/SlotName`.
1171
+ * Persisted as TEXT (no SQL CHECK by design — see migration comment).
1172
+ */
1173
+ slot: string;
1174
+ /** Already-validated payload. Serialised via `JSON.stringify` at write. */
1175
+ payload: unknown;
1176
+ emittedAt: number;
1177
+ }
1178
+ /**
1179
+ * Single contribution row as returned to callers. The payload is
1180
+ * `unknown` because the slot space is open at the type layer (catalog
1181
+ * evolution is a kernel + spec concern); narrow at the call site by
1182
+ * reading `slot`.
1183
+ */
1184
+ interface IPersistedContribution {
1185
+ pluginId: string;
1186
+ extensionId: string;
1187
+ nodePath: string;
1188
+ contributionId: string;
1189
+ slot: string;
1190
+ payload: unknown;
1191
+ emittedAt: number;
1192
+ }
1193
+
887
1194
  /**
888
1195
  * Provider runtime contract. Walks filesystem roots and emits raw node
889
1196
  * records; classification maps path conventions to a node kind.
@@ -1157,6 +1464,11 @@ interface IProviderReadConfig {
1157
1464
  * a return value. Extractors run in isolation: they MUST NOT read other
1158
1465
  * nodes, the graph, or the DB. Cross-node reasoning lives in rules.
1159
1466
  *
1467
+ * Extractors are deterministic-only. They run synchronously inside the
1468
+ * scan loop; LLM-driven enrichment of a node is an Action concern, not
1469
+ * an Extractor concern. The Extractor context therefore exposes no
1470
+ * `RunnerPort` — see spec `architecture.md` §Execution modes.
1471
+ *
1160
1472
  * Output channels (all on the context):
1161
1473
  *
1162
1474
  * - `ctx.emitLink(link)` — persist a link in the kernel's `links` table.
@@ -1164,14 +1476,12 @@ interface IProviderReadConfig {
1164
1476
  * kind drops the link and surfaces an `extension.error` event.
1165
1477
  * - `ctx.enrichNode(partial)` — merge canonical, kernel-curated properties
1166
1478
  * onto the node. Strictly separate from the author-supplied frontmatter
1167
- * (the latter remains immutable and survives verbatim). Persistence and
1168
- * stale-tracking are spec'd in § A.8.
1479
+ * (the latter remains immutable and survives verbatim). Persistence
1480
+ * is spec'd in § A.8.
1169
1481
  * - `ctx.store` — plugin-scoped persistence. Present only when the
1170
1482
  * plugin declares `storage.mode` in `plugin.json`; shape depends on the
1171
1483
  * mode (`KvStore` for mode A, scoped `Database` for mode B). See
1172
1484
  * `plugin-kv-api.md` for the contract.
1173
- * - `ctx.runner` — `RunnerPort` injection for `probabilistic` extractors.
1174
- * `undefined` for the default `deterministic` mode.
1175
1485
  *
1176
1486
  * The manifest's `scope` field tells the orchestrator which parts to feed:
1177
1487
  * `frontmatter` extractors receive an empty string for body and vice versa.
@@ -1202,6 +1512,22 @@ interface IExtractorCallbacks {
1202
1512
  * partials and `persistScanResult` upserts them.
1203
1513
  */
1204
1514
  enrichNode(partial: Partial<Node>): void;
1515
+ /**
1516
+ * Emit a per-node view contribution. The first argument is the
1517
+ * extension-local Record key declared under
1518
+ * `extension.viewContributions[<contributionId>]`; the second is a
1519
+ * payload that conforms to the slot's payload schema in
1520
+ * `spec/schemas/view-slots.schema.json#/$defs/payloads/<slot>` —
1521
+ * where `<slot>` is the slot the manifest declared for this
1522
+ * contribution. The orchestrator validates the payload against the
1523
+ * slot's schema before persisting to `scan_contributions`; off-shape
1524
+ * payloads are silently dropped with an `extension.error` event
1525
+ * (mirror of `emitLink` rejecting off-`emitsLinkKinds` links).
1526
+ * Calling `emitContribution` with a `contributionId` that is not
1527
+ * declared in the manifest is also dropped with an `extension.error`.
1528
+ * See `architecture.md` §View contribution system → Emit path.
1529
+ */
1530
+ emitContribution(contributionId: string, payload: unknown): void;
1205
1531
  }
1206
1532
  interface IExtractorContext extends IExtractorCallbacks {
1207
1533
  node: Node;
@@ -1222,33 +1548,17 @@ interface IExtractorContext extends IExtractorCallbacks {
1222
1548
  * it here.
1223
1549
  */
1224
1550
  store?: unknown;
1225
- /**
1226
- * `RunnerPort` injection for `probabilistic` extractors. `undefined`
1227
- * for `deterministic` mode (the default). The kernel rejects
1228
- * probabilistic extractors that try to register scan-time hooks at
1229
- * load time.
1230
- */
1231
- runner?: unknown;
1232
1551
  }
1233
1552
  interface IExtractor extends IExtensionBase {
1234
1553
  kind: 'extractor';
1235
- /**
1236
- * Execution mode. Optional in the manifest with a default of
1237
- * `deterministic` per `spec/schemas/extensions/extractor.schema.json`.
1238
- * `probabilistic` extractors invoke an LLM through the kernel's
1239
- * `RunnerPort` and never participate in scan-time pipelines —
1240
- * they dispatch only as queued jobs.
1241
- */
1242
- mode?: TExecutionMode;
1243
1554
  emitsLinkKinds: LinkKind[];
1244
1555
  defaultConfidence: Confidence;
1245
1556
  scope: 'frontmatter' | 'body' | 'both';
1246
1557
  /**
1247
1558
  * Optional opt-in filter on `node.kind`. When declared, the orchestrator
1248
1559
  * skips invocation of `extract()` for any node whose `kind` is NOT in
1249
- * this list — fail-fast, before context construction, so a
1250
- * probabilistic extractor wastes zero LLM cost on inapplicable nodes
1251
- * and a deterministic extractor wastes zero CPU.
1560
+ * this list — fail-fast, before context construction, so the extractor
1561
+ * wastes zero CPU on inapplicable nodes.
1252
1562
  *
1253
1563
  * Absent (`undefined`) is the default: the extractor applies to every
1254
1564
  * kind. There are no wildcards — the absence of the field already
@@ -1271,28 +1581,29 @@ interface IExtractor extends IExtensionBase {
1271
1581
  }
1272
1582
 
1273
1583
  /**
1274
- * Rule runtime contract. Runs against the whole graph after every Provider
1275
- * and extractor has completed; emits issues. Deterministic rules are pure
1276
- * (same graph in same issues out) and run synchronously inside `sm scan`
1277
- * / `sm check`. Probabilistic rules invoke an LLM through the kernel's
1278
- * `RunnerPort` and dispatch only as queued jobs they never participate
1279
- * in scan-time pipelines. Mode is declared in the manifest (default
1280
- * `deterministic`).
1584
+ * Analyzer runtime contract. Runs against the whole graph after every
1585
+ * Provider and extractor has completed; emits issues and MAY project
1586
+ * findings into the UI via view contributions. Deterministic analyzers
1587
+ * are pure (same graph in same issues out) and run synchronously
1588
+ * inside `sm scan` / `sm check`. Probabilistic analyzers invoke an LLM
1589
+ * through the kernel's `RunnerPort` and dispatch only as queued jobs —
1590
+ * they never participate in scan-time pipelines. Mode is declared in
1591
+ * the manifest (default `deterministic`).
1281
1592
  */
1282
1593
 
1283
1594
  /**
1284
- * Step 9.6.2 — orphan sidecar entry surfaced to rules. A `.sm` file
1595
+ * Step 9.6.2 — orphan sidecar entry surfaced to analyzers. A `.sm` file
1285
1596
  * whose sibling `.md` does not exist on disk; the `annotation-orphan`
1286
- * built-in rule emits one warning per entry. Other rules that care
1287
- * about orphan sidecars MAY consume the list too.
1597
+ * built-in analyzer emits one warning per entry. Other analyzers that
1598
+ * care about orphan sidecars MAY consume the list too.
1288
1599
  */
1289
- interface IRuleOrphanSidecar {
1600
+ interface IAnalyzerOrphanSidecar {
1290
1601
  /** Relative path (POSIX-separated) of the orphan `.sm`. */
1291
1602
  relativePath: string;
1292
1603
  /** Absolute path of the missing `.md` the sidecar was anchored to. */
1293
1604
  expectedMdPath: string;
1294
1605
  }
1295
- interface IRuleContext {
1606
+ interface IAnalyzerContext {
1296
1607
  nodes: Node[];
1297
1608
  links: Link[];
1298
1609
  /**
@@ -1300,35 +1611,101 @@ interface IRuleContext {
1300
1611
  * Empty when sidecar discovery did not run (legacy callers) or
1301
1612
  * when no orphans exist.
1302
1613
  */
1303
- orphanSidecars?: IRuleOrphanSidecar[];
1614
+ orphanSidecars?: IAnalyzerOrphanSidecar[];
1304
1615
  /**
1305
1616
  * Step 9.6.6 — raw parsed sidecar root keyed by `node.path`. Populated
1306
1617
  * by the orchestrator alongside the public `Node.sidecar` overlay so
1307
- * rules that inspect plugin namespaces (e.g. the built-in
1308
- * `core/unknown-field` Rule) can walk the full tree without re-reading
1309
- * the file from disk. Absent (or `undefined` per node) when no
1310
- * sidecar accompanies the node, or when the sidecar failed to parse.
1311
- * Treat as read-only.
1618
+ * analyzers that inspect plugin namespaces (e.g. the built-in
1619
+ * `core/unknown-field` Analyzer) can walk the full tree without
1620
+ * re-reading the file from disk. Absent (or `undefined` per node)
1621
+ * when no sidecar accompanies the node, or when the sidecar failed
1622
+ * to parse. Treat as read-only.
1312
1623
  */
1313
1624
  sidecarRoots?: ReadonlyMap<string, Record<string, unknown>>;
1314
1625
  /**
1315
1626
  * Step 9.6.6 — runtime catalog of plugin-contributed annotation keys,
1316
1627
  * as exposed by `kernel.getRegisteredAnnotationKeys()`. Threaded
1317
- * through so rules can reason about the registered-vs-unknown split
1318
- * without reaching back into the kernel. Empty array when no plugin
1319
- * declares contributions; absent for legacy callers (older runScan
1320
- * sites that never wired the catalog through).
1628
+ * through so analyzers can reason about the registered-vs-unknown
1629
+ * split without reaching back into the kernel. Empty array when no
1630
+ * plugin declares contributions; absent for legacy callers (older
1631
+ * runScan sites that never wired the catalog through).
1321
1632
  */
1322
1633
  annotationContributions?: readonly IRegisteredAnnotationKey[];
1634
+ /**
1635
+ * Step 11.x — runtime catalog of plugin-contributed view contributions,
1636
+ * as exposed by `kernel.getRegisteredViewContributions()`. Threaded
1637
+ * through so analyzers can reason about emissions without reaching
1638
+ * back into the kernel: built-in `core/unknown-slot` walks this list
1639
+ * to detect deprecated slots in use, and `core/contribution-orphan`
1640
+ * joins it with the live node set to flag dangling emissions. Empty
1641
+ * array when no extension declares view contributions; absent for
1642
+ * legacy callers (older runScan sites that never wired the catalog
1643
+ * through).
1644
+ */
1645
+ viewContributions?: readonly IRegisteredViewContribution[];
1646
+ /**
1647
+ * Absolute paths of `*.md` files under the project's
1648
+ * `.skill-map/jobs/` that no `state_jobs.filePath` references — the
1649
+ * built-in `core/job-orphan-file` analyzer projects each as a `warn`
1650
+ * issue. Pre-computed by the driving adapter (CLI / BFF) inside its
1651
+ * already-open storage transaction (mirrors the `orphanSidecars`
1652
+ * pattern: detection lives outside the analyzer, the analyzer only
1653
+ * projects). Absent (or empty) when the caller does not maintain a
1654
+ * jobs directory, when the storage path is unavailable, or when no
1655
+ * orphan files exist. Treat as read-only.
1656
+ */
1657
+ orphanJobFiles?: readonly string[];
1658
+ /**
1659
+ * Set of absolute file paths the operator has opted into for
1660
+ * link-validation purposes via `scan.referencePaths`. The driving
1661
+ * adapter walks each configured path before the scan and collects
1662
+ * every existing file's absolute path here. Files in this set are
1663
+ * NOT indexed as graph nodes — the only consumer is
1664
+ * `core/broken-ref`, which suppresses its `warn` issue when a
1665
+ * path-style link target falls into the set. Absent / empty when
1666
+ * the operator left `scan.referencePaths` empty or when the
1667
+ * adapter does not maintain the side index. Treat as read-only.
1668
+ */
1669
+ referenceablePaths?: ReadonlySet<string>;
1670
+ /**
1671
+ * Absolute path of the scan's project root (cwd of the invocation).
1672
+ * Threaded into the analyzer pass so an analyzer that needs to
1673
+ * resolve a relative `link.target` to an absolute filesystem path
1674
+ * (today only `core/broken-ref`, when consulting
1675
+ * `referenceablePaths`) does not have to derive it from
1676
+ * `nodes[0].path` heuristics. Absent for legacy callers (older
1677
+ * `runScan` sites that never wired the field through). Always an
1678
+ * absolute path when present.
1679
+ */
1680
+ cwd?: string;
1681
+ /**
1682
+ * Emit a per-node view contribution declared in this analyzer's
1683
+ * manifest `viewContributions` map. Sync, void return; the
1684
+ * orchestrator validates the payload against the slot's schema at
1685
+ * call time and silently drops invalid emissions with a logged
1686
+ * `extension.error` event (parallel to
1687
+ * `IExtractorCallbacks.emitContribution`).
1688
+ *
1689
+ * Unlike Extractor's emit (which binds `nodePath` from `ctx.node.path`
1690
+ * implicitly because Extractors run per-node), Analyzer's `evaluate()`
1691
+ * sees the full graph at once. The analyzer walks `ctx.nodes` itself
1692
+ * and MUST supply the target node path explicitly per emission.
1693
+ *
1694
+ * Calling `emitContribution` with a `contributionId` that is not
1695
+ * declared in the manifest is dropped with an `extension.error`. The
1696
+ * kernel routes emitted contributions to the same persistence
1697
+ * pipeline as Extractor emissions (`scan_contributions`).
1698
+ */
1699
+ emitContribution(nodePath: string, contributionId: string, payload: unknown): void;
1323
1700
  }
1324
- interface IRule extends IExtensionBase {
1325
- kind: 'rule';
1701
+ interface IAnalyzer extends IExtensionBase {
1702
+ kind: 'analyzer';
1326
1703
  /**
1327
1704
  * Execution mode. Optional in the manifest with a default of
1328
- * `deterministic` per `spec/schemas/extensions/rule.schema.json`.
1705
+ * `deterministic` per `spec/schemas/extensions/analyzer.schema.json`.
1329
1706
  */
1330
1707
  mode?: TExecutionMode;
1331
- evaluate(ctx: IRuleContext): Issue[] | Promise<Issue[]>;
1708
+ evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
1332
1709
  }
1333
1710
 
1334
1711
  /**
@@ -1558,12 +1935,16 @@ interface IFormatter extends IExtensionBase {
1558
1935
  * are notification (Slack on `job.completed`), integration glue (CI
1559
1936
  * webhook on `job.failed`), and bookkeeping (per-extractor metrics).
1560
1937
  *
1561
- * The hookable trigger set is INTENTIONALLY SMALL — eight events. The
1562
- * full `ProgressEmitterPort` catalog (per-node `scan.progress`,
1563
- * `model.delta`, `run.*`, internal job lifecycle) is deliberately not
1564
- * hookable: too verbose for a reactive surface, internal to the runner,
1565
- * or covered elsewhere. Declaring a trigger outside the curated set
1566
- * yields `invalid-manifest` at load time.
1938
+ * The hookable trigger set is INTENTIONALLY SMALL — ten events. Eight
1939
+ * are pipeline-driven (emitted from inside `runScan`); two
1940
+ * (`boot`, `shutdown`) are CLI-process-driven (emitted by the driving
1941
+ * binary before / after the verb runs, fire-and-forget so
1942
+ * `process.exit` is never blocked). The full `ProgressEmitterPort`
1943
+ * catalog (per-node `scan.progress`, `model.delta`, `run.*`, internal
1944
+ * job lifecycle) is deliberately not hookable: too verbose for a
1945
+ * reactive surface, internal to the runner, or covered elsewhere.
1946
+ * Declaring a trigger outside the curated set yields
1947
+ * `invalid-manifest` at load time.
1567
1948
  *
1568
1949
  * Dual-mode (declared in manifest):
1569
1950
  *
@@ -1578,27 +1959,35 @@ interface IFormatter extends IExtensionBase {
1578
1959
  *
1579
1960
  * Curated trigger set (per spec § A.11):
1580
1961
  *
1962
+ * 0. `boot` — once per CLI process, before verb routing.
1581
1963
  * 1. `scan.started` — pre-scan setup (one per scan).
1582
1964
  * 2. `scan.completed` — post-scan reaction (one per scan).
1583
1965
  * 3. `extractor.completed` — aggregated per-Extractor outputs.
1584
- * 4. `rule.completed` — aggregated per-Rule outputs.
1966
+ * 4. `analyzer.completed` — aggregated per-Rule outputs.
1585
1967
  * 5. `action.completed` — Action executed on a node.
1586
1968
  * 6. `job.spawning` — pre-spawn of runner subprocess.
1587
1969
  * 7. `job.completed` — most common trigger.
1588
1970
  * 8. `job.failed` — alerts, retry triggers.
1971
+ * 9. `shutdown` — once per CLI process, after the verb's
1972
+ * exit code resolves and before
1973
+ * `process.exit`.
1589
1974
  */
1590
1975
 
1591
1976
  /**
1592
- * The eight hookable lifecycle events. Mirrors the `triggers[]` enum in
1593
- * `spec/schemas/extensions/hook.schema.json`. Anything outside this set
1594
- * is rejected at load time as `invalid-manifest`.
1977
+ * The ten hookable lifecycle events. Mirrors the `triggers[]` enum in
1978
+ * `spec/schemas/extensions/hook.schema.json`. Eight are pipeline-driven
1979
+ * (emitted from inside `runScan`); two (`boot`, `shutdown`) are
1980
+ * CLI-process-driven (emitted by the driving binary before / after the
1981
+ * verb runs). Anything outside this set is rejected at load time as
1982
+ * `invalid-manifest`.
1595
1983
  */
1596
- type THookTrigger = 'scan.started' | 'scan.completed' | 'extractor.completed' | 'rule.completed' | 'action.completed' | 'job.spawning' | 'job.completed' | 'job.failed';
1984
+ type THookTrigger = 'boot' | 'scan.started' | 'scan.completed' | 'extractor.completed' | 'analyzer.completed' | 'action.completed' | 'job.spawning' | 'job.completed' | 'job.failed' | 'shutdown';
1597
1985
  /**
1598
1986
  * Frozen list mirror of `THookTrigger` for runtime introspection. The
1599
1987
  * loader validates `manifest.triggers[]` against this set; the
1600
- * orchestrator's dispatcher iterates it in order when fanning an event
1601
- * out to subscribed hooks.
1988
+ * dispatcher iterates it in order when fanning an event out to
1989
+ * subscribed hooks. `boot` first / `shutdown` last so a debug log of
1990
+ * the array reads in lifecycle order.
1602
1991
  */
1603
1992
  declare const HOOK_TRIGGERS: readonly THookTrigger[];
1604
1993
  /**
@@ -1607,7 +1996,7 @@ declare const HOOK_TRIGGERS: readonly THookTrigger[];
1607
1996
  *
1608
1997
  * The `event` carries the raw `ProgressEvent` envelope (type, timestamp,
1609
1998
  * runId/jobId when applicable, data). Optional `node` / `extractorId`
1610
- * / `ruleId` / `actionId` are extracted from the event payload by the
1999
+ * / `analyzerId` / `actionId` are extracted from the event payload by the
1611
2000
  * dispatcher when present so authors don't have to walk `event.data`.
1612
2001
  *
1613
2002
  * Probabilistic hooks additionally receive `runner` for LLM dispatch.
@@ -1634,9 +2023,9 @@ interface IHookContext {
1634
2023
  */
1635
2024
  extractorId?: string;
1636
2025
  /**
1637
- * Set on `rule.completed` events. Qualified extension id of the Rule.
2026
+ * Set on `analyzer.completed` events. Qualified extension id of the Rule.
1638
2027
  */
1639
- ruleId?: string;
2028
+ analyzerId?: string;
1640
2029
  /**
1641
2030
  * Set on `action.completed` events. Qualified extension id of the
1642
2031
  * Action that just ran.
@@ -1704,7 +2093,7 @@ interface IHook extends IExtensionBase {
1704
2093
  }
1705
2094
 
1706
2095
  /**
1707
- * Scan orchestrator — runs the Provider → extractor → rule pipeline across
2096
+ * Scan orchestrator — runs the Provider → extractor → analyzer pipeline across
1708
2097
  * every registered extension and emits `ProgressEmitterPort` events in
1709
2098
  * canonical order. The callable extension set is injected via
1710
2099
  * `RunScanOptions.extensions` — the Registry holds manifest metadata, the
@@ -1757,7 +2146,7 @@ interface IHook extends IExtensionBase {
1757
2146
  interface IScanExtensions {
1758
2147
  providers: IProvider[];
1759
2148
  extractors: IExtractor[];
1760
- rules: IRule[];
2149
+ analyzers: IAnalyzer[];
1761
2150
  /**
1762
2151
  * Optional hooks (spec § A.11). When supplied, the orchestrator's
1763
2152
  * lifecycle dispatcher invokes deterministic hooks subscribed to one
@@ -1800,6 +2189,20 @@ interface RunScanOptions {
1800
2189
  * inside the rule.
1801
2190
  */
1802
2191
  annotationContributions?: readonly IRegisteredAnnotationKey[];
2192
+ /**
2193
+ * Runtime catalog of plugin-contributed view contributions (the same
2194
+ * shape `kernel.getRegisteredViewContributions()` returns). Threaded
2195
+ * into the rule pass so:
2196
+ * - `core/unknown-slot` and `core/contribution-orphan` can
2197
+ * introspect the catalog (read-only).
2198
+ * - The orchestrator's per-rule emit closure can look up each
2199
+ * declared `(contributionId → slot)` pairing for AJV
2200
+ * payload validation.
2201
+ * Absent → empty catalog. Rules that emit contributions silently
2202
+ * drop emissions when the catalog has no entry for the rule's
2203
+ * declared contributionId.
2204
+ */
2205
+ viewContributions?: readonly IRegisteredViewContribution[];
1803
2206
  /**
1804
2207
  * Scan scope. Defaults to `'project'`. The CLI flag wiring lands in
1805
2208
  * the config layer wiring; `runScan` already accepts the override
@@ -1893,6 +2296,40 @@ interface RunScanOptions {
1893
2296
  * mock without spinning up a DB.
1894
2297
  */
1895
2298
  pluginStores?: ReadonlyMap<string, IPluginStore>;
2299
+ /**
2300
+ * Pre-computed absolute paths of orphan job MD files (files under
2301
+ * `.skill-map/jobs/` whose absolute path appears nowhere in
2302
+ * `state_jobs.filePath`). Threaded into the rule pass so the
2303
+ * built-in `core/job-orphan-file` rule can project each as a `warn`
2304
+ * issue without the kernel reaching for the storage port or doing
2305
+ * its own FS walk. The driving adapter (CLI, BFF) computes this
2306
+ * inside its already-open storage transaction via
2307
+ * `findOrphanJobFiles(jobsDir, await port.jobs.listReferencedFilePaths())`
2308
+ * — mirrors the `orphanSidecars` model where detection lives
2309
+ * outside the rule and the rule only projects. Absent / empty when
2310
+ * the caller has no jobs context (out-of-band tests, fresh DB,
2311
+ * `--no-built-ins`).
2312
+ */
2313
+ orphanJobFiles?: readonly string[];
2314
+ /**
2315
+ * Side set of absolute file paths the operator opted into for
2316
+ * link-validation purposes via `scan.referencePaths`. Threaded
2317
+ * through to `IAnalyzerContext.referenceablePaths` so the built-in
2318
+ * `core/broken-ref` rule can suppress its `warn` for path-style
2319
+ * links whose target lands in the set. Files are NOT walked by
2320
+ * the kernel — the driving adapter populates the set before
2321
+ * calling `runScan`. Absent / empty when the operator left
2322
+ * `scan.referencePaths` unconfigured.
2323
+ */
2324
+ referenceablePaths?: ReadonlySet<string>;
2325
+ /**
2326
+ * Absolute path of the scan's cwd / project root. Threaded onto
2327
+ * `IAnalyzerContext.cwd` so rules that need to resolve a relative
2328
+ * `link.target` to an absolute filesystem path can do so without
2329
+ * heuristics. Absent for callers that don't track a cwd
2330
+ * concept (out-of-band tests, embedders).
2331
+ */
2332
+ cwd?: string;
1896
2333
  }
1897
2334
  /**
1898
2335
  * Spec § A.9 — runs to persist into `scan_extractor_runs`. One entry
@@ -1919,8 +2356,6 @@ interface IExtractorRunRecord {
1919
2356
  *
1920
2357
  * - upsert a single row per pair (stable PRIMARY KEY conflict on
1921
2358
  * re-extract);
1922
- * - flag probabilistic rows `stale = 1` when the body changes between
1923
- * scans (preserving the prior LLM cost);
1924
2359
  * - feed `mergeNodeWithEnrichments` with `enrichedAt`-sorted partials
1925
2360
  * for last-write-wins per field at read time.
1926
2361
  *
@@ -1930,10 +2365,12 @@ interface IExtractorRunRecord {
1930
2365
  * fold into a single row, but two different Extractors hitting the
1931
2366
  * same node yield two distinct rows.
1932
2367
  *
1933
- * `isProbabilistic` is denormalised so the persistence layer's stale
1934
- * flag query stays a single-table read; recomputing from the live
1935
- * registry would force every read-path to thread the runtime extension
1936
- * set through.
2368
+ * `isProbabilistic` is reserved: Extractors are deterministic-only, so
2369
+ * every record produced by the orchestrator sets it to `false`. The
2370
+ * field is kept on the record (and the row in `node_enrichments`) so a
2371
+ * future Action-issued enrichment can populate it without reshaping
2372
+ * the persistence contract — see spec `architecture.md`
2373
+ * §Extractor · enrichment layer.
1937
2374
  */
1938
2375
  interface IEnrichmentRecord {
1939
2376
  nodePath: string;
@@ -1961,6 +2398,8 @@ declare function runScanWithRenames(_kernel: Kernel, options: RunScanOptions): P
1961
2398
  renameOps: RenameOp[];
1962
2399
  extractorRuns: IExtractorRunRecord[];
1963
2400
  enrichments: IEnrichmentRecord[];
2401
+ contributions: IContributionRecord[];
2402
+ freshlyRunTuples: ReadonlySet<string>;
1964
2403
  }>;
1965
2404
  declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<ScanResult>;
1966
2405
  /**
@@ -1997,6 +2436,7 @@ declare function runExtractorsForNode(opts: {
1997
2436
  internalLinks: Link[];
1998
2437
  externalLinks: Link[];
1999
2438
  enrichments: IEnrichmentRecord[];
2439
+ contributions: IContributionRecord[];
2000
2440
  }>;
2001
2441
  /**
2002
2442
  * Pure rename / orphan classification per `spec/db-schema.md` §Rename
@@ -2037,10 +2477,11 @@ declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], iss
2037
2477
  * Algorithm:
2038
2478
  *
2039
2479
  * 1. Filter `enrichments` down to rows targeting this node AND not
2040
- * flagged `stale`. Stale rows (probabilistic enrichments whose
2041
- * body changed since their last run) are excluded by default —
2042
- * stale visibility belongs to the UI layer where the marker is
2043
- * shown next to the value.
2480
+ * flagged `stale`. With Extractors deterministic-only no row is
2481
+ * stale-flagged in this revision; the filter is preserved for the
2482
+ * future Action-issued enrichment revision (queued LLM jobs whose
2483
+ * output must survive body changes), where stale visibility
2484
+ * belongs to the UI layer next to the value.
2044
2485
  * 2. Sort the survivors by `enrichedAt` ASC so iteration order is
2045
2486
  * "oldest first". This makes the spread merge below
2046
2487
  * last-write-wins per field — the freshest Extractor's value
@@ -2208,7 +2649,7 @@ declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatche
2208
2649
  * are presentation facets that can churn without making the link
2209
2650
  * "different" for delta purposes.
2210
2651
  *
2211
- * - **Issue**: `(ruleId, sorted nodeIds, message)`. Mirrors
2652
+ * - **Issue**: `(analyzerId, sorted nodeIds, message)`. Mirrors
2212
2653
  * `spec/job-events.md` §issue.* — same key → same issue, even when
2213
2654
  * `data` / `severity` / `linkIndices` shift. A meaningful change in
2214
2655
  * `message` (or a different set of node ids) is a different issue.
@@ -2334,6 +2775,72 @@ declare function applyExportQuery(scan: {
2334
2775
  issues: Issue[];
2335
2776
  }, query: IExportQuery): IExportSubset;
2336
2777
 
2778
+ /**
2779
+ * `scan_node_tags` adapter — tags · dual-source persistence layer.
2780
+ *
2781
+ * One row per `(node_path, tag, source)` triple. Projected at persist
2782
+ * time from BOTH `frontmatter.tags` (with `source='author'`) and
2783
+ * `sidecar.annotations.tags` (with `source='user'`). The same tag
2784
+ * string MAY appear under both sources for the same node — the PK
2785
+ * accepts the pair; search returns the node once via DISTINCT, the
2786
+ * UI renders both chips with their attribution.
2787
+ *
2788
+ * Belongs to the `scan_*` family — replaced wholesale per scan.
2789
+ * Cached nodes' tag rows are projected from the cached
2790
+ * `node.frontmatter.tags` / `node.sidecar.annotations.tags` (both
2791
+ * already in memory at persist time), so the rebuild is cheap
2792
+ * regardless of cache hit / miss. See `spec/db-schema.md`
2793
+ * § scan_node_tags for the normative shape and replace-all semantics.
2794
+ */
2795
+
2796
+ /**
2797
+ * In-memory tag record buffered during scan and flushed to
2798
+ * `scan_node_tags` by `persistScanResult`. One entry per
2799
+ * `(node_path, tag, source)` projected from a node's frontmatter tags
2800
+ * (`source: 'author'`) or sidecar annotations tags
2801
+ * (`source: 'user'`).
2802
+ */
2803
+ interface ITagRecord {
2804
+ nodePath: string;
2805
+ tag: string;
2806
+ source: 'author' | 'user';
2807
+ }
2808
+
2809
+ /**
2810
+ * Pure helpers for the "update available" notification feature.
2811
+ *
2812
+ * Three responsibilities:
2813
+ * - `fetchLatestVersion` — query `https://registry.npmjs.org/<pkg>/latest`
2814
+ * with `AbortController` + timeout. Throws on
2815
+ * non-200 / parse failure / abort.
2816
+ * - `compareVersions` — semver compare (-1 / 0 / 1). Pre-1.0 aware:
2817
+ * treats prereleases via the standard rules
2818
+ * (release > prerelease at the same triple).
2819
+ * - `isOutdated` — sugar over `compareVersions` for the common
2820
+ * "is `latest` strictly greater than `current`"
2821
+ * check the banner runs against.
2822
+ *
2823
+ * Pure kernel module — NO `process.env` reads, NO Node globals beyond the
2824
+ * built-in `fetch` / `AbortController` (Node 22+). Every env / settings
2825
+ * lookup happens in `src/cli/util/update-check-banner.ts`, the CLI-side
2826
+ * adapter that owns side effects.
2827
+ *
2828
+ * The shared cache type (`IUpdateCheckCache`) is used by the storage
2829
+ * helpers under `kernel/storage/update-check.ts` and by the BFF's
2830
+ * `GET /api/update-status` projection. A second type
2831
+ * (`IUpdateStatus`) shapes the BFF response — it merges `current`
2832
+ * (from `VERSION`) into the cache so the UI can render without a
2833
+ * second lookup. Both stay flat — no nested objects — so JSON
2834
+ * serialization is trivial.
2835
+ */
2836
+ interface IUpdateCheckCache {
2837
+ latestVersion: string;
2838
+ /** Epoch ms — when the registry was last successfully probed. */
2839
+ checkedAt: number;
2840
+ /** Epoch ms — when the banner was last printed; null = never shown yet. */
2841
+ shownAt: number | null;
2842
+ }
2843
+
2337
2844
  /**
2338
2845
  * `PluginLoaderPort` — discovers plugin directories and loads their
2339
2846
  * extensions. The shape mirrors what the concrete loader actually
@@ -2427,6 +2934,44 @@ interface IPersistOptions {
2427
2934
  renameOps?: RenameOp[];
2428
2935
  extractorRuns?: IExtractorRunRecord[];
2429
2936
  enrichments?: IEnrichmentRecord[];
2937
+ contributions?: IContributionRecord[];
2938
+ /**
2939
+ * Phase 3 / View contribution system — active runtime catalog of
2940
+ * registered view contributions, keyed by qualified id
2941
+ * `<pluginId>/<extensionId>/<contributionId>`. Passed to the
2942
+ * `scan_contributions` upsert so the catalog sweep can drop rows
2943
+ * belonging to plugins / extensions that are no longer in the
2944
+ * catalog (uninstalled plugins, disabled bundles, removed
2945
+ * contributions). Empty / absent set = no catalog sweep (legacy
2946
+ * behaviour, leaves disabled-plugin rows stale per design F24
2947
+ * pre-fix).
2948
+ */
2949
+ registeredContributionKeys?: ReadonlySet<string>;
2950
+ /**
2951
+ * Phase 3 / View contribution system — set of `(plugin, extension,
2952
+ * node)` tuples where the extension actually RAN against that node
2953
+ * in this scan. Format: `<pluginId>/<extensionId>/<nodePath>` (no
2954
+ * contribution-id segment — the sweep operates at the (plugin,
2955
+ * extension, node) level and inspects the buffer to decide which
2956
+ * contribution-ids survive).
2957
+ *
2958
+ * Membership rules:
2959
+ * - Extractor + cache miss: tuple INCLUDED (extract() ran).
2960
+ * - Extractor + cache hit: tuple OMITTED (extract() skipped, prior
2961
+ * rows must be preserved).
2962
+ * - Rule, every node in `ctx.nodes`: tuple INCLUDED (rules always
2963
+ * run and see the full graph).
2964
+ *
2965
+ * Drives the per-tuple sweep documented in `spec/architecture.md`
2966
+ * §View contribution system → Persistence (sweep #3): rows whose
2967
+ * `(plugin_id, extension_id, node_path)` is in this set but whose
2968
+ * `(plugin_id, extension_id, node_path, contribution_id)` is NOT in
2969
+ * the buffer get DELETEd before the upsert. Catches the "extractor
2970
+ * used to emit, now does not" case (e.g. body change removes the
2971
+ * trigger). Empty / absent set = no per-tuple sweep (legacy
2972
+ * callers preserve the pre-fix behaviour where stale rows linger).
2973
+ */
2974
+ freshlyRunTuples?: ReadonlySet<string>;
2430
2975
  }
2431
2976
  /**
2432
2977
  * Issue row as the storage layer sees it — paired with its DB-assigned
@@ -2567,28 +3112,6 @@ interface IPluginApplyResult {
2567
3112
  intrusions: string[];
2568
3113
  }
2569
3114
 
2570
- /**
2571
- * `StoragePort` — the kernel's persistence boundary. Driving adapters
2572
- * (CLI, future server, in-memory test harness) consume this surface
2573
- * exclusively; nothing in `cli/**` should reach into the SQLite
2574
- * adapter's internal helpers (free functions on
2575
- * `kernel/adapters/sqlite/*`) directly. Phase F of the
2576
- * storage-port-promotion refactor finishes that hardening; A-E grow
2577
- * the port enough that the CLI has somewhere to land.
2578
- *
2579
- * The port is namespaced by domain (`scans`, `issues`, `enrichments`,
2580
- * etc.) — explicitly NOT a generic `port.query<T>(sql)`. Each
2581
- * namespace's methods name an operation the kernel cares about; the
2582
- * adapter translates to its persistence engine's idioms.
2583
- *
2584
- * Phase A lands the **scans / issues / enrichments / transaction**
2585
- * namespaces — the core scan pipeline. The remaining namespaces
2586
- * (history / jobs / pluginConfig / migrations / pluginMigrations)
2587
- * arrive in subsequent phases. The port shape declared here is the
2588
- * Phase A subset; later phases extend it without reshaping what
2589
- * lands today.
2590
- */
2591
-
2592
3115
  /**
2593
3116
  * Subset of `StoragePort` exposed inside a `transaction(fn)` callback.
2594
3117
  * Lifecycle methods are intentionally omitted — a transaction that
@@ -2665,12 +3188,56 @@ interface StoragePort {
2665
3188
  */
2666
3189
  findNode(path: string): Promise<INodeBundle | null>;
2667
3190
  };
3191
+ /**
3192
+ * Phase 3 / View contribution system — read access to
3193
+ * `scan_contributions`. Writes happen exclusively via
3194
+ * `scans.persist({ contributions })` to keep the replace-all
3195
+ * semantics intact; this namespace is read-only.
3196
+ */
3197
+ contributions: {
3198
+ /** Every contribution row for a single node. Stable order. */
3199
+ listForNode(nodePath: string): Promise<IPersistedContribution[]>;
3200
+ /**
3201
+ * Bulk variant for the BFF nodes-list route. Returns rows for
3202
+ * every path in `paths`, sorted `nodePath` ASC, then qualified-id
3203
+ * ASC. Empty `paths` returns `[]` without a query.
3204
+ */
3205
+ listForPaths(paths: readonly string[]): Promise<IPersistedContribution[]>;
3206
+ /**
3207
+ * Lookup by qualified id + path. Used by
3208
+ * `GET /api/contributions/:pluginId/:contributionId?path=...`.
3209
+ */
3210
+ lookup(pluginId: string, contributionId: string, nodePath: string, extensionId?: string): Promise<IPersistedContribution[]>;
3211
+ };
3212
+ /**
3213
+ * Read-only access to `scan_node_tags`. Writes happen exclusively
3214
+ * via `scans.persist({...})` (the persistence layer projects from
3215
+ * `node.frontmatter.tags` and `node.sidecar.annotations.tags`); this
3216
+ * namespace is read-only.
3217
+ */
3218
+ tags: {
3219
+ /** Every tag row for a single node. Author entries first, then user. */
3220
+ listForNode(nodePath: string): Promise<ITagRecord[]>;
3221
+ /**
3222
+ * Bulk variant for the BFF nodes-list route. Returns rows for every
3223
+ * path in `paths`, sorted `nodePath` ASC, then `source` ASC, then
3224
+ * `tag` ASC. Empty `paths` returns `[]` without a query.
3225
+ */
3226
+ listForPaths(paths: readonly string[]): Promise<ITagRecord[]>;
3227
+ /**
3228
+ * Find every node carrying `tag`. Optional `source` narrows to one
3229
+ * side of the dual surface (matches `sm list --tag <name>
3230
+ * --tag-source author|user`); absent matches the union (default
3231
+ * `sm list --tag`).
3232
+ */
3233
+ findNodes(tag: string, source?: 'author' | 'user'): Promise<string[]>;
3234
+ };
2668
3235
  issues: {
2669
3236
  /** Every issue from the latest scan, in insertion order. */
2670
3237
  listAll(): Promise<Issue[]>;
2671
3238
  /**
2672
3239
  * Issue rows whose runtime `Issue` shape passes `predicate`.
2673
- * `port.issues.findActive((i) => i.ruleId === 'orphan')` is the
3240
+ * `port.issues.findActive((i) => i.analyzerId === 'orphan')` is the
2674
3241
  * canonical use; `sm orphans` consumes this. The returned shape
2675
3242
  * carries the DB-assigned `id` so a follow-up
2676
3243
  * `transaction(tx => tx.issues.deleteById(row.id))` can target
@@ -2721,6 +3288,36 @@ interface StoragePort {
2721
3288
  */
2722
3289
  listReferencedFilePaths(): Promise<Set<string>>;
2723
3290
  };
3291
+ /**
3292
+ * Generic key/value preferences keyed by a stable string. Backs the
3293
+ * `config_preferences` table — one row per `key`, `value_json` is a
3294
+ * single JSON blob the caller serialises. Keys with the `_kernel.`
3295
+ * prefix are reserved for kernel-managed entries (today: the
3296
+ * update-check cache); user-set preferences land under unprefixed
3297
+ * keys when those ship.
3298
+ *
3299
+ * Read-only by design at the port level — the only writer is the
3300
+ * CLI's post-run hook (`cli/util/update-check-banner.ts`), which
3301
+ * reaches the persistence helpers directly. The port surfaces the
3302
+ * read so the BFF's `GET /api/update-status` projection can stay
3303
+ * inside the abstract contract.
3304
+ */
3305
+ preferences: {
3306
+ /**
3307
+ * Load the update-check cache row. Returns `null` when the row
3308
+ * is absent, malformed JSON, or fails the shape guard. Never
3309
+ * throws — read failures degrade silently because the banner is
3310
+ * a non-essential surface.
3311
+ */
3312
+ loadUpdateCheckCache(): Promise<IUpdateCheckCache | null>;
3313
+ /**
3314
+ * Upsert the update-check cache row. Always overwrites the
3315
+ * existing JSON blob in place. `updated_at` tracks wall-clock
3316
+ * now — separate from the embedded `checkedAt` field, which
3317
+ * the caller controls.
3318
+ */
3319
+ saveUpdateCheckCache(cache: IUpdateCheckCache): Promise<void>;
3320
+ };
2724
3321
  favorites: {
2725
3322
  /**
2726
3323
  * Mark `path` as favorited. Idempotent — a second call refreshes
@@ -2940,6 +3537,51 @@ declare function resetLogger(): void;
2940
3537
  /** Inspect the active logger. Test-only — production code uses `log`. */
2941
3538
  declare function getActiveLogger(): LoggerPort;
2942
3539
 
3540
+ /**
3541
+ * Hook lifecycle dispatcher (spec § A.11). Indexes the supplied hooks
3542
+ * by trigger and fans the matching event out to every subscribed
3543
+ * deterministic hook in registration order. Probabilistic hooks are
3544
+ * skipped here with a stderr advisory; they will dispatch via the job
3545
+ * subsystem once it ships (Decision #114).
3546
+ *
3547
+ * Filter handling: when the hook declares a `filter` map, the dispatcher
3548
+ * walks `event.data` for each declared key and short-circuits the
3549
+ * invocation when any value disagrees. Top-level fields only in v0.x
3550
+ * (deep-path matching is deferred until a real use case justifies the
3551
+ * complexity).
3552
+ *
3553
+ * Error policy: a hook that throws is caught here, logged through a
3554
+ * synthetic `extension.error` event with kind `hook-error`, and the
3555
+ * caller continues. A buggy hook MUST NOT block the main pipeline (or
3556
+ * the CLI exit path) — that would invert the design intent (hooks
3557
+ * REACT to events, they never steer them).
3558
+ *
3559
+ * The module lives under `kernel/extensions/` (alongside the `IHook`
3560
+ * contract itself) so two callers share it: `runScan` for the eight
3561
+ * pipeline-driven triggers (`scan.*`, `extractor.completed`,
3562
+ * `analyzer.completed`, `action.completed`, `job.*`) and the CLI entry
3563
+ * for the two CLI-process-driven triggers (`boot`, `shutdown`).
3564
+ * Pulling the dispatcher out of the orchestrator keeps both consumers
3565
+ * symmetric — same indexing, same filter semantics, same error
3566
+ * policy.
3567
+ */
3568
+
3569
+ interface IHookDispatcher {
3570
+ /**
3571
+ * Fan the event out to every hook subscribed to `trigger`. Awaits each
3572
+ * hook's `on(ctx)` in registration order. Errors are caught and
3573
+ * logged via `extension.error`; they never propagate.
3574
+ */
3575
+ dispatch(trigger: THookTrigger, event: ProgressEvent): Promise<void>;
3576
+ }
3577
+ /**
3578
+ * Build a dispatcher over the given hooks. Empty `hooks` returns a
3579
+ * cheap no-op shape so the call sites can dispatch unconditionally.
3580
+ */
3581
+ declare function makeHookDispatcher(hooks: IHook[], emitter: ProgressEmitterPort): IHookDispatcher;
3582
+ /** Construct a `ProgressEvent` envelope. Mirrors the orchestrator helper. */
3583
+ declare function makeEvent(type: string, data: unknown): ProgressEvent;
3584
+
2943
3585
  /**
2944
3586
  * Kernel entry point. `createKernel()` returns a shell with an empty registry
2945
3587
  * and no bound ports. Driving adapters (CLI, Server, Skill) are expected to
@@ -2961,7 +3603,22 @@ interface Kernel {
2961
3603
  * MUST treat the resulting array as immutable.
2962
3604
  */
2963
3605
  setRegisteredAnnotationKeys: (entries: readonly IRegisteredAnnotationKey[]) => void;
3606
+ /**
3607
+ * Step 11.x — read-only catalog of plugin-contributed view
3608
+ * contributions, keyed by `(pluginId, extensionId, contributionId)`.
3609
+ * Populated at plugin-load time; pure read with no side effects.
3610
+ * Mirror of `getRegisteredAnnotationKeys` for the view contribution
3611
+ * surface (see `architecture.md` §View contribution system →
3612
+ * Runtime catalog).
3613
+ */
3614
+ getRegisteredViewContributions: () => readonly IRegisteredViewContribution[];
3615
+ /**
3616
+ * Internal — replace the frozen view-contribution catalog. Called
3617
+ * once by the plugin runtime composer after every plugin has loaded;
3618
+ * consumers MUST treat the resulting array as immutable.
3619
+ */
3620
+ setRegisteredViewContributions: (entries: readonly IRegisteredViewContribution[]) => void;
2964
3621
  }
2965
3622
  declare function createKernel(): Kernel;
2966
3623
 
2967
- export { type Confidence, DuplicateExtensionError, EXTENSION_KINDS, type ExecutionFailureReason, type ExecutionKind, type ExecutionRecord, type ExecutionRunner, type ExecutionStatus, ExportQueryError, type Extension, type ExtensionKind, type FilesystemPort, HOOK_TRIGGERS, type HistoryStats, type HistoryStatsErrorRates, type HistoryStatsExecutionsPerPeriod, type HistoryStatsPerActionRate, type HistoryStatsTokensPerAction, type HistoryStatsTopNode, type HistoryStatsTotals, type IAction, type IActionContext, type IActionPrecondition, type IActionResult, type IAnnotationContribution, type ICreateFsWatcherOptions, type IDedicatedStorePersist, type IDedicatedStoreWrapper, type IDiscoveredPlugin, type IEnrichmentRecord, type IExportQuery, type IExportSubset, type IExtensionBase, type IExtractor, type IExtractorCallbacks, type IExtractorContext, type IExtractorRunRecord, type IFormatter, type IFormatterContext, type IFsWatcher, type IHook, type IHookContext, type IIssueRow, type IKvStorePersist, type IKvStoreWrapper, type ILoadedExtension, type INodeBundle, type INodeChange, type INodeCounts, type INodeFilter, type IPersistOptions, type IPersistedEnrichment, type IPluginManifest, type IPluginStorageSchema, type IPluginStore, type IProvider, type IRawNode, type IRegisteredAnnotationKey, type IRule, type IRuleContext, type IRunOptions, type IRunResult, type IScanDelta, type ITransactionalStorage, type IWalkOptions, type IWatchBatch, type IWatchEvent, InMemoryProgressEmitter, type Issue, type IssueFix, KV_SCHEMA_KEY, type Kernel, LOG_LEVELS, type Link, type LinkKind, type LinkLocation, type LinkTrigger, type LogRecord, type LoggerPort, type Node, type NodeKind, type NodeStat, type PluginLoaderPort, type ProgressEmitterPort, type ProgressEvent, Registry, type RenameOp, type RunScanOptions, type RunnerPort, type ScanResult, type ScanScannedBy, type ScanStats, type Severity, SilentLogger, type Stability, type StoragePort, type TActionWrite, type TExecutionMode, type TGranularity, type THookFilter, type THookTrigger, type TLogLevel, type TLogMethodLevel, type TNodeChangeReason, type TPluginLoadStatus, type TPluginStorage, type TProgressListener, type TWatchEventKind, type TripleSplit, applyExportQuery, computeScanDelta, configureLogger, createChokidarWatcher, createKernel, detectRenamesAndOrphans, getActiveLogger, isEmptyDelta, isLogLevel, log, logLevelRank, makeDedicatedStoreWrapper, makeKvStoreWrapper, makePluginStore, mergeNodeWithEnrichments, parseExportQuery, parseLogLevel, qualifiedExtensionId, resetLogger, runExtractorsForNode, runScan, runScanWithRenames };
3624
+ export { type Confidence, DuplicateExtensionError, EXTENSION_KINDS, type ExecutionFailureReason, type ExecutionKind, type ExecutionRecord, type ExecutionRunner, type ExecutionStatus, ExportQueryError, type Extension, type ExtensionKind, type FilesystemPort, HOOK_TRIGGERS, type HistoryStats, type HistoryStatsErrorRates, type HistoryStatsExecutionsPerPeriod, type HistoryStatsPerActionRate, type HistoryStatsTokensPerAction, type HistoryStatsTopNode, type HistoryStatsTotals, type IAction, type IActionContext, type IActionPrecondition, type IActionResult, type IAnalyzer, type IAnalyzerContext, type IAnnotationContribution, type ICreateFsWatcherOptions, type IDedicatedStorePersist, type IDedicatedStoreWrapper, type IDiscoveredPlugin, type IEnrichmentRecord, type IExportQuery, type IExportSubset, type IExtensionBase, type IExtractor, type IExtractorCallbacks, type IExtractorContext, type IExtractorRunRecord, type IFormatter, type IFormatterContext, type IFsWatcher, type IHook, type IHookContext, type IHookDispatcher, type IIssueRow, type IKvStorePersist, type IKvStoreWrapper, type ILoadedExtension, type INodeBundle, type INodeChange, type INodeCounts, type INodeFilter, type IPersistOptions, type IPersistedEnrichment, type IPluginManifest, type IPluginStorageSchema, type IPluginStore, type IProvider, type IRawNode, type IRegisteredAnnotationKey, type IRegisteredViewContribution, type IRunOptions, type IRunResult, type IScanDelta, type ISettingDeclaration, type ITransactionalStorage, type IViewContribution, type IWalkOptions, type IWatchBatch, type IWatchEvent, InMemoryProgressEmitter, type Issue, type IssueFix, KV_SCHEMA_KEY, type Kernel, LOG_LEVELS, type Link, type LinkKind, type LinkLocation, type LinkTrigger, type LogRecord, type LoggerPort, type Node, type NodeKind, type NodeStat, type PluginLoaderPort, type ProgressEmitterPort, type ProgressEvent, Registry, type RenameOp, type RunScanOptions, type RunnerPort, type ScanResult, type ScanScannedBy, type ScanStats, type Severity, SilentLogger, type Stability, type StoragePort, type TActionWrite, type TExecutionMode, type TGranularity, type THookFilter, type THookTrigger, type TInputTypeName, type TLogLevel, type TLogMethodLevel, type TNodeChangeReason, type TPluginLoadStatus, type TPluginStorage, type TProgressListener, type TSettingValue, type TSeverity, type TSlotName, type TWatchEventKind, type TripleSplit, applyExportQuery, computeScanDelta, configureLogger, createChokidarWatcher, createKernel, detectRenamesAndOrphans, getActiveLogger, isEmptyDelta, isLogLevel, log, logLevelRank, makeDedicatedStoreWrapper, makeEvent, makeHookDispatcher, makeKvStoreWrapper, makePluginStore, mergeNodeWithEnrichments, parseExportQuery, parseLogLevel, qualifiedExtensionId, resetLogger, runExtractorsForNode, runScan, runScanWithRenames };