@skill-map/cli 0.19.0 → 0.20.1

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 (36) hide show
  1. package/dist/cli/tutorial/sm-tutorial.md +3 -3
  2. package/dist/cli.js +7476 -6447
  3. package/dist/cli.js.map +1 -1
  4. package/dist/conformance/index.js +1 -1
  5. package/dist/conformance/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +182 -141
  8. package/dist/index.js.map +1 -1
  9. package/dist/kernel/index.d.ts +268 -109
  10. package/dist/kernel/index.js +182 -141
  11. package/dist/kernel/index.js.map +1 -1
  12. package/dist/migrations/001_initial.sql +17 -17
  13. package/dist/ui/chunk-4NLC7QD2.js +124 -0
  14. package/dist/ui/{chunk-VWAUXWQX.js → chunk-6BZZQV42.js} +1 -1
  15. package/dist/ui/chunk-6GUHSAP5.js +5 -0
  16. package/dist/ui/chunk-E4ALROJS.js +450 -0
  17. package/dist/ui/{chunk-7CAK6MVK.js → chunk-EZZF5RL5.js} +10 -10
  18. package/dist/ui/chunk-FWX4RRDF.js +125 -0
  19. package/dist/ui/chunk-GGMXMGRJ.js +1 -0
  20. package/dist/ui/chunk-K5PULFK7.js +1 -0
  21. package/dist/ui/chunk-OJ6W6OIB.js +61 -0
  22. package/dist/ui/{chunk-BORRASJB.js → chunk-PTCD42GB.js} +6 -6
  23. package/dist/ui/{chunk-LFIE4SCX.js → chunk-ZSRIBCAW.js} +13 -13
  24. package/dist/ui/index.html +2 -2
  25. package/dist/ui/main-5FJWWH5I.js +1 -0
  26. package/dist/ui/{styles-UAABA7VK.css → styles-VJ5Q6D2X.css} +1 -1
  27. package/migrations/001_initial.sql +17 -17
  28. package/package.json +2 -2
  29. package/dist/ui/chunk-CZSS4D6J.js +0 -454
  30. package/dist/ui/chunk-EQD7AYYJ.js +0 -227
  31. package/dist/ui/chunk-ETTRVTFV.js +0 -1
  32. package/dist/ui/chunk-OKO3QOH6.js +0 -1
  33. package/dist/ui/chunk-PMIMYHBM.js +0 -61
  34. package/dist/ui/chunk-UHFGCO24.js +0 -1
  35. package/dist/ui/chunk-VHIPW3TH.js +0 -1
  36. package/dist/ui/main-BSYMJKTL.js +0 -1
@@ -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
@@ -226,7 +226,7 @@ interface IssueFix {
226
226
  autofixable?: boolean;
227
227
  }
228
228
  interface Issue {
229
- ruleId: string;
229
+ analyzerId: string;
230
230
  severity: Severity;
231
231
  nodeIds: string[];
232
232
  message: string;
@@ -370,7 +370,7 @@ interface ScanResult {
370
370
  * Extension registry — six kinds, first-class, loaded through a single API.
371
371
  *
372
372
  * The `Extension` shape is aligned with `spec/schemas/extensions/base.schema.json`.
373
- * Kind-specific manifests (provider / extractor / rule / action / formatter /
373
+ * Kind-specific manifests (provider / extractor / analyzer / action / formatter /
374
374
  * hook) extend this base structurally; the registry stores the base view
375
375
  * and each kind's code carries its own fuller type where needed.
376
376
  *
@@ -386,7 +386,7 @@ interface ScanResult {
386
386
  * `kernel-empty-boot` conformance contract.
387
387
  */
388
388
 
389
- type ExtensionKind = 'provider' | 'extractor' | 'rule' | 'action' | 'formatter' | 'hook';
389
+ type ExtensionKind = 'provider' | 'extractor' | 'analyzer' | 'action' | 'formatter' | 'hook';
390
390
  declare const EXTENSION_KINDS: readonly ExtensionKind[];
391
391
  interface Extension {
392
392
  /** Short (unqualified) extension id as declared in the manifest. */
@@ -432,7 +432,7 @@ declare class Registry {
432
432
  * Step 9.6.6 — runtime annotation-contribution catalog types.
433
433
  *
434
434
  * Lives in its own module (rather than `kernel/index.ts`) so consumers
435
- * deep inside the kernel — `IRuleContext`, the BFF route factories,
435
+ * deep inside the kernel — `IAnalyzerContext`, the BFF route factories,
436
436
  * future Action contexts — can depend on the catalog shape without
437
437
  * dragging the whole kernel barrel and risking a cycle.
438
438
  */
@@ -456,7 +456,7 @@ interface IRegisteredAnnotationKey {
456
456
  * Step 11.x — runtime view-contribution catalog types.
457
457
  *
458
458
  * Lives in its own module (rather than `kernel/index.ts`) so consumers
459
- * deep inside the kernel — `IRuleContext`, the BFF route factories,
459
+ * deep inside the kernel — `IAnalyzerContext`, the BFF route factories,
460
460
  * future Action contexts — can depend on the catalog shape without
461
461
  * dragging the whole kernel barrel and risking a cycle.
462
462
  *
@@ -466,25 +466,25 @@ interface IRegisteredAnnotationKey {
466
466
  * storage or routing — see `architecture.md` §View contribution system
467
467
  * for the comparison table.
468
468
  *
469
- * **Closed catalog by design.** Both `TContractName` and `TInputTypeName`
470
- * mirror the closed enums in `spec/schemas/view-contracts.schema.json`
469
+ * **Closed catalog by design.** Both `TSlotName` and `TInputTypeName`
470
+ * mirror the closed enums in `spec/schemas/view-slots.schema.json`
471
471
  * and `spec/schemas/input-types.schema.json`. Adding a member is a
472
472
  * coordinated kernel + spec + UI + scaffolder change. The closed-enum
473
- * shape lets TypeScript surface unknown contracts at author time
473
+ * shape lets TypeScript surface unknown slots at author time
474
474
  * (in plugin authors' editors when their plugin imports `@skill-map/cli`)
475
- * AND lets the runtime exhaustively dispatch contract → renderer in the
475
+ * AND lets the runtime exhaustively dispatch slot → renderer in the
476
476
  * UI without `default:` fallbacks.
477
477
  */
478
478
  /**
479
- * Closed enum of view contract names. Mirror of
480
- * `spec/schemas/view-contracts.schema.json#/$defs/ContractName`.
479
+ * Closed enum of view slot names. Mirror of
480
+ * `spec/schemas/view-slots.schema.json#/$defs/SlotName`.
481
481
  *
482
482
  * Plugins pick one of these by name in their extension manifest's
483
- * `viewContributions[<contributionId>].contract` field. The kernel
483
+ * `viewContributions[<contributionId>].slot` field. The kernel
484
484
  * validates each pick at load time (`invalid-manifest` on miss); the
485
- * UI maps each contract to one or more slots and a renderer.
485
+ * slot fixes both the renderer and the payload shape.
486
486
  */
487
- type TContractName = 'node-counter' | 'node-tag' | 'node-breakdown' | 'node-records' | 'node-tree' | 'node-key-values' | 'node-link-list' | 'node-markdown' | 'node-alert' | 'scope-stat';
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
488
  /**
489
489
  * Closed enum of input-type names for plugin settings. Mirror of
490
490
  * `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
@@ -502,14 +502,16 @@ type TSeverity = 'info' | 'warn' | 'success' | 'danger';
502
502
  * author writes one of these per Record key in
503
503
  * `IExtensionBase.viewContributions[<contributionId>]`.
504
504
  *
505
- * Mirror of `view-contracts.schema.json#/$defs/IViewContribution`.
505
+ * Mirror of `view-slots.schema.json#/$defs/IViewContribution`.
506
506
  */
507
507
  interface IViewContribution {
508
508
  /**
509
- * Required. Closed-catalog contract name. Unknown name rejects the
510
- * extension as `invalid-manifest` at load.
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.
511
513
  */
512
- contract: TContractName;
514
+ slot: TSlotName;
513
515
  /**
514
516
  * Optional human-readable label. English-only per `AGENTS.md`
515
517
  * (`Externalized texts, not internationalized`).
@@ -521,6 +523,8 @@ interface IViewContribution {
521
523
  * Optional emoji codepoint OR PrimeIcons class id (without the
522
524
  * `pi-` prefix). The UI discriminates: matches Unicode
523
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`).
524
528
  */
525
529
  icon?: string;
526
530
  /**
@@ -532,8 +536,8 @@ interface IViewContribution {
532
536
  /**
533
537
  * When false (default), the kernel drops emissions whose payload is
534
538
  * structurally empty so the slot stays silent. When true, the
535
- * renderer surfaces an empty placeholder. Per-contract definition
536
- * of "empty" lives in the contract's payload schema.
539
+ * renderer surfaces an empty placeholder. Per-slot definition of
540
+ * "empty" lives in the slot's payload schema.
537
541
  */
538
542
  emitWhenEmpty?: boolean;
539
543
  /**
@@ -561,7 +565,7 @@ interface IRegisteredViewContribution {
561
565
  pluginId: string;
562
566
  extensionId: string;
563
567
  contributionId: string;
564
- contract: TContractName;
568
+ slot: TSlotName;
565
569
  /** Optional manifest-declared label (English-only). */
566
570
  label?: string;
567
571
  tooltip?: string;
@@ -741,16 +745,16 @@ interface IExtensionBase {
741
745
  /**
742
746
  * Plugin-contributed view contributions. Each entry maps a local
743
747
  * contribution id (kebab-case, unique within the extension) to a
744
- * `IViewContribution` declaration that picks a view contract by name
745
- * from the closed kernel catalog (`view-catalog.ts#TContractName`).
746
- * The kernel validates each `contract` pick at load time
747
- * (`invalid-manifest` on miss); the plugin emits per-node payloads
748
- * via `ctx.emitContribution(<contributionId>, payload)` during scan;
749
- * the runtime validates payloads against the contract's payload
750
- * schema. The aggregate runtime catalog is exposed via
751
- * `kernel.getRegisteredViewContributions()`. The plugin author
752
- * NEVER picks a UI slot — slot mapping is owned by the UI driving
753
- * adapter. See `architecture.md` §View contribution system.
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.
754
758
  */
755
759
  viewContributions?: Record<string, IViewContribution>;
756
760
  }
@@ -868,7 +872,7 @@ interface IPluginManifest {
868
872
  version: string;
869
873
  specCompat: string;
870
874
  /**
871
- * Optional semver range against the kernel's view-contracts +
875
+ * Optional semver range against the kernel's view-slots +
872
876
  * input-types catalog version. Independent from `specCompat` because
873
877
  * the catalog evolves on its own cadence (see `architecture.md`
874
878
  * §View contribution system → Catalog versioning). Mismatch surfaces
@@ -1154,7 +1158,7 @@ declare function makePluginStore(opts: {
1154
1158
  * In-memory contribution record buffered during scan and flushed to
1155
1159
  * `scan_contributions` by `persistScanResult`. One entry per accepted
1156
1160
  * `ctx.emitContribution(id, payload)` call. Payload validation against
1157
- * the contract's payload schema happens at emit time (orchestrator);
1161
+ * the slot's payload schema happens at emit time (orchestrator);
1158
1162
  * by the time records reach this adapter they are wire-shape clean.
1159
1163
  */
1160
1164
  interface IContributionRecord {
@@ -1163,26 +1167,26 @@ interface IContributionRecord {
1163
1167
  nodePath: string;
1164
1168
  contributionId: string;
1165
1169
  /**
1166
- * Closed enum value mirroring `view-contracts.schema.json#/$defs/ContractName`.
1170
+ * Closed enum value mirroring `view-slots.schema.json#/$defs/SlotName`.
1167
1171
  * Persisted as TEXT (no SQL CHECK by design — see migration comment).
1168
1172
  */
1169
- contract: string;
1173
+ slot: string;
1170
1174
  /** Already-validated payload. Serialised via `JSON.stringify` at write. */
1171
1175
  payload: unknown;
1172
1176
  emittedAt: number;
1173
1177
  }
1174
1178
  /**
1175
1179
  * Single contribution row as returned to callers. The payload is
1176
- * `unknown` because the contract space is open at the type layer
1177
- * (catalog evolution is a kernel + spec concern); narrow at the call
1178
- * site by reading `contract`.
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`.
1179
1183
  */
1180
1184
  interface IPersistedContribution {
1181
1185
  pluginId: string;
1182
1186
  extensionId: string;
1183
1187
  nodePath: string;
1184
1188
  contributionId: string;
1185
- contract: string;
1189
+ slot: string;
1186
1190
  payload: unknown;
1187
1191
  emittedAt: number;
1188
1192
  }
@@ -1512,15 +1516,16 @@ interface IExtractorCallbacks {
1512
1516
  * Emit a per-node view contribution. The first argument is the
1513
1517
  * extension-local Record key declared under
1514
1518
  * `extension.viewContributions[<contributionId>]`; the second is a
1515
- * payload that conforms to the contract's payload schema in
1516
- * `spec/schemas/view-contracts.schema.json#/$defs/payloads/<contract>`.
1517
- * The orchestrator validates the payload against the contract schema
1518
- * before persisting to `scan_contributions`; off-contract payloads
1519
- * are silently dropped with an `extension.error` event (mirror of
1520
- * `emitLink` rejecting off-`emitsLinkKinds` links). Calling
1521
- * `emitContribution` with a `contributionId` that is not declared in
1522
- * the manifest is also dropped with an `extension.error`. See
1523
- * `architecture.md` §View contribution system Emit path.
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.
1524
1529
  */
1525
1530
  emitContribution(contributionId: string, payload: unknown): void;
1526
1531
  }
@@ -1576,28 +1581,29 @@ interface IExtractor extends IExtensionBase {
1576
1581
  }
1577
1582
 
1578
1583
  /**
1579
- * Rule runtime contract. Runs against the whole graph after every Provider
1580
- * and extractor has completed; emits issues. Deterministic rules are pure
1581
- * (same graph in same issues out) and run synchronously inside `sm scan`
1582
- * / `sm check`. Probabilistic rules invoke an LLM through the kernel's
1583
- * `RunnerPort` and dispatch only as queued jobs they never participate
1584
- * in scan-time pipelines. Mode is declared in the manifest (default
1585
- * `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`).
1586
1592
  */
1587
1593
 
1588
1594
  /**
1589
- * 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
1590
1596
  * whose sibling `.md` does not exist on disk; the `annotation-orphan`
1591
- * built-in rule emits one warning per entry. Other rules that care
1592
- * 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.
1593
1599
  */
1594
- interface IRuleOrphanSidecar {
1600
+ interface IAnalyzerOrphanSidecar {
1595
1601
  /** Relative path (POSIX-separated) of the orphan `.sm`. */
1596
1602
  relativePath: string;
1597
1603
  /** Absolute path of the missing `.md` the sidecar was anchored to. */
1598
1604
  expectedMdPath: string;
1599
1605
  }
1600
- interface IRuleContext {
1606
+ interface IAnalyzerContext {
1601
1607
  nodes: Node[];
1602
1608
  links: Link[];
1603
1609
  /**
@@ -1605,32 +1611,32 @@ interface IRuleContext {
1605
1611
  * Empty when sidecar discovery did not run (legacy callers) or
1606
1612
  * when no orphans exist.
1607
1613
  */
1608
- orphanSidecars?: IRuleOrphanSidecar[];
1614
+ orphanSidecars?: IAnalyzerOrphanSidecar[];
1609
1615
  /**
1610
1616
  * Step 9.6.6 — raw parsed sidecar root keyed by `node.path`. Populated
1611
1617
  * by the orchestrator alongside the public `Node.sidecar` overlay so
1612
- * rules that inspect plugin namespaces (e.g. the built-in
1613
- * `core/unknown-field` Rule) can walk the full tree without re-reading
1614
- * the file from disk. Absent (or `undefined` per node) when no
1615
- * sidecar accompanies the node, or when the sidecar failed to parse.
1616
- * 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.
1617
1623
  */
1618
1624
  sidecarRoots?: ReadonlyMap<string, Record<string, unknown>>;
1619
1625
  /**
1620
1626
  * Step 9.6.6 — runtime catalog of plugin-contributed annotation keys,
1621
1627
  * as exposed by `kernel.getRegisteredAnnotationKeys()`. Threaded
1622
- * through so rules can reason about the registered-vs-unknown split
1623
- * without reaching back into the kernel. Empty array when no plugin
1624
- * declares contributions; absent for legacy callers (older runScan
1625
- * 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).
1626
1632
  */
1627
1633
  annotationContributions?: readonly IRegisteredAnnotationKey[];
1628
1634
  /**
1629
1635
  * Step 11.x — runtime catalog of plugin-contributed view contributions,
1630
1636
  * as exposed by `kernel.getRegisteredViewContributions()`. Threaded
1631
- * through so rules can reason about emissions without reaching back
1632
- * into the kernel: built-in `core/unknown-contract` walks this list to
1633
- * detect deprecated contracts in use, and `core/contribution-orphan`
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`
1634
1640
  * joins it with the live node set to flag dangling emissions. Empty
1635
1641
  * array when no extension declares view contributions; absent for
1636
1642
  * legacy callers (older runScan sites that never wired the catalog
@@ -1638,16 +1644,52 @@ interface IRuleContext {
1638
1644
  */
1639
1645
  viewContributions?: readonly IRegisteredViewContribution[];
1640
1646
  /**
1641
- * Emit a per-node view contribution declared in this rule's manifest
1642
- * `viewContributions` map. Sync, void return; the orchestrator
1643
- * validates the payload against the contract schema at call time and
1644
- * silently drops invalid emissions with a logged `extension.error`
1645
- * event (parallel to `IExtractorCallbacks.emitContribution`).
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`).
1646
1688
  *
1647
1689
  * Unlike Extractor's emit (which binds `nodePath` from `ctx.node.path`
1648
- * implicitly because Extractors run per-node), Rule's `evaluate()`
1649
- * sees the full graph at once. The rule walks `ctx.nodes` itself and
1650
- * MUST supply the target node path explicitly per emission.
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.
1651
1693
  *
1652
1694
  * Calling `emitContribution` with a `contributionId` that is not
1653
1695
  * declared in the manifest is dropped with an `extension.error`. The
@@ -1656,14 +1698,14 @@ interface IRuleContext {
1656
1698
  */
1657
1699
  emitContribution(nodePath: string, contributionId: string, payload: unknown): void;
1658
1700
  }
1659
- interface IRule extends IExtensionBase {
1660
- kind: 'rule';
1701
+ interface IAnalyzer extends IExtensionBase {
1702
+ kind: 'analyzer';
1661
1703
  /**
1662
1704
  * Execution mode. Optional in the manifest with a default of
1663
- * `deterministic` per `spec/schemas/extensions/rule.schema.json`.
1705
+ * `deterministic` per `spec/schemas/extensions/analyzer.schema.json`.
1664
1706
  */
1665
1707
  mode?: TExecutionMode;
1666
- evaluate(ctx: IRuleContext): Issue[] | Promise<Issue[]>;
1708
+ evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
1667
1709
  }
1668
1710
 
1669
1711
  /**
@@ -1893,12 +1935,16 @@ interface IFormatter extends IExtensionBase {
1893
1935
  * are notification (Slack on `job.completed`), integration glue (CI
1894
1936
  * webhook on `job.failed`), and bookkeeping (per-extractor metrics).
1895
1937
  *
1896
- * The hookable trigger set is INTENTIONALLY SMALL — eight events. The
1897
- * full `ProgressEmitterPort` catalog (per-node `scan.progress`,
1898
- * `model.delta`, `run.*`, internal job lifecycle) is deliberately not
1899
- * hookable: too verbose for a reactive surface, internal to the runner,
1900
- * or covered elsewhere. Declaring a trigger outside the curated set
1901
- * 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.
1902
1948
  *
1903
1949
  * Dual-mode (declared in manifest):
1904
1950
  *
@@ -1913,27 +1959,35 @@ interface IFormatter extends IExtensionBase {
1913
1959
  *
1914
1960
  * Curated trigger set (per spec § A.11):
1915
1961
  *
1962
+ * 0. `boot` — once per CLI process, before verb routing.
1916
1963
  * 1. `scan.started` — pre-scan setup (one per scan).
1917
1964
  * 2. `scan.completed` — post-scan reaction (one per scan).
1918
1965
  * 3. `extractor.completed` — aggregated per-Extractor outputs.
1919
- * 4. `rule.completed` — aggregated per-Rule outputs.
1966
+ * 4. `analyzer.completed` — aggregated per-Rule outputs.
1920
1967
  * 5. `action.completed` — Action executed on a node.
1921
1968
  * 6. `job.spawning` — pre-spawn of runner subprocess.
1922
1969
  * 7. `job.completed` — most common trigger.
1923
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`.
1924
1974
  */
1925
1975
 
1926
1976
  /**
1927
- * The eight hookable lifecycle events. Mirrors the `triggers[]` enum in
1928
- * `spec/schemas/extensions/hook.schema.json`. Anything outside this set
1929
- * 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`.
1930
1983
  */
1931
- 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';
1932
1985
  /**
1933
1986
  * Frozen list mirror of `THookTrigger` for runtime introspection. The
1934
1987
  * loader validates `manifest.triggers[]` against this set; the
1935
- * orchestrator's dispatcher iterates it in order when fanning an event
1936
- * 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.
1937
1991
  */
1938
1992
  declare const HOOK_TRIGGERS: readonly THookTrigger[];
1939
1993
  /**
@@ -1942,7 +1996,7 @@ declare const HOOK_TRIGGERS: readonly THookTrigger[];
1942
1996
  *
1943
1997
  * The `event` carries the raw `ProgressEvent` envelope (type, timestamp,
1944
1998
  * runId/jobId when applicable, data). Optional `node` / `extractorId`
1945
- * / `ruleId` / `actionId` are extracted from the event payload by the
1999
+ * / `analyzerId` / `actionId` are extracted from the event payload by the
1946
2000
  * dispatcher when present so authors don't have to walk `event.data`.
1947
2001
  *
1948
2002
  * Probabilistic hooks additionally receive `runner` for LLM dispatch.
@@ -1969,9 +2023,9 @@ interface IHookContext {
1969
2023
  */
1970
2024
  extractorId?: string;
1971
2025
  /**
1972
- * Set on `rule.completed` events. Qualified extension id of the Rule.
2026
+ * Set on `analyzer.completed` events. Qualified extension id of the Rule.
1973
2027
  */
1974
- ruleId?: string;
2028
+ analyzerId?: string;
1975
2029
  /**
1976
2030
  * Set on `action.completed` events. Qualified extension id of the
1977
2031
  * Action that just ran.
@@ -2039,7 +2093,7 @@ interface IHook extends IExtensionBase {
2039
2093
  }
2040
2094
 
2041
2095
  /**
2042
- * Scan orchestrator — runs the Provider → extractor → rule pipeline across
2096
+ * Scan orchestrator — runs the Provider → extractor → analyzer pipeline across
2043
2097
  * every registered extension and emits `ProgressEmitterPort` events in
2044
2098
  * canonical order. The callable extension set is injected via
2045
2099
  * `RunScanOptions.extensions` — the Registry holds manifest metadata, the
@@ -2092,7 +2146,7 @@ interface IHook extends IExtensionBase {
2092
2146
  interface IScanExtensions {
2093
2147
  providers: IProvider[];
2094
2148
  extractors: IExtractor[];
2095
- rules: IRule[];
2149
+ analyzers: IAnalyzer[];
2096
2150
  /**
2097
2151
  * Optional hooks (spec § A.11). When supplied, the orchestrator's
2098
2152
  * lifecycle dispatcher invokes deterministic hooks subscribed to one
@@ -2139,10 +2193,10 @@ interface RunScanOptions {
2139
2193
  * Runtime catalog of plugin-contributed view contributions (the same
2140
2194
  * shape `kernel.getRegisteredViewContributions()` returns). Threaded
2141
2195
  * into the rule pass so:
2142
- * - `core/unknown-contract` and `core/contribution-orphan` can
2196
+ * - `core/unknown-slot` and `core/contribution-orphan` can
2143
2197
  * introspect the catalog (read-only).
2144
2198
  * - The orchestrator's per-rule emit closure can look up each
2145
- * declared `(contributionId → contract)` pairing for AJV
2199
+ * declared `(contributionId → slot)` pairing for AJV
2146
2200
  * payload validation.
2147
2201
  * Absent → empty catalog. Rules that emit contributions silently
2148
2202
  * drop emissions when the catalog has no entry for the rule's
@@ -2242,6 +2296,40 @@ interface RunScanOptions {
2242
2296
  * mock without spinning up a DB.
2243
2297
  */
2244
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;
2245
2333
  }
2246
2334
  /**
2247
2335
  * Spec § A.9 — runs to persist into `scan_extractor_runs`. One entry
@@ -2311,6 +2399,7 @@ declare function runScanWithRenames(_kernel: Kernel, options: RunScanOptions): P
2311
2399
  extractorRuns: IExtractorRunRecord[];
2312
2400
  enrichments: IEnrichmentRecord[];
2313
2401
  contributions: IContributionRecord[];
2402
+ freshlyRunTuples: ReadonlySet<string>;
2314
2403
  }>;
2315
2404
  declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<ScanResult>;
2316
2405
  /**
@@ -2560,7 +2649,7 @@ declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatche
2560
2649
  * are presentation facets that can churn without making the link
2561
2650
  * "different" for delta purposes.
2562
2651
  *
2563
- * - **Issue**: `(ruleId, sorted nodeIds, message)`. Mirrors
2652
+ * - **Issue**: `(analyzerId, sorted nodeIds, message)`. Mirrors
2564
2653
  * `spec/job-events.md` §issue.* — same key → same issue, even when
2565
2654
  * `data` / `severity` / `linkIndices` shift. A meaningful change in
2566
2655
  * `message` (or a different set of node ids) is a different issue.
@@ -2858,6 +2947,31 @@ interface IPersistOptions {
2858
2947
  * pre-fix).
2859
2948
  */
2860
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>;
2861
2975
  }
2862
2976
  /**
2863
2977
  * Issue row as the storage layer sees it — paired with its DB-assigned
@@ -3123,7 +3237,7 @@ interface StoragePort {
3123
3237
  listAll(): Promise<Issue[]>;
3124
3238
  /**
3125
3239
  * Issue rows whose runtime `Issue` shape passes `predicate`.
3126
- * `port.issues.findActive((i) => i.ruleId === 'orphan')` is the
3240
+ * `port.issues.findActive((i) => i.analyzerId === 'orphan')` is the
3127
3241
  * canonical use; `sm orphans` consumes this. The returned shape
3128
3242
  * carries the DB-assigned `id` so a follow-up
3129
3243
  * `transaction(tx => tx.issues.deleteById(row.id))` can target
@@ -3423,6 +3537,51 @@ declare function resetLogger(): void;
3423
3537
  /** Inspect the active logger. Test-only — production code uses `log`. */
3424
3538
  declare function getActiveLogger(): LoggerPort;
3425
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
+
3426
3585
  /**
3427
3586
  * Kernel entry point. `createKernel()` returns a shell with an empty registry
3428
3587
  * and no bound ports. Driving adapters (CLI, Server, Skill) are expected to
@@ -3462,4 +3621,4 @@ interface Kernel {
3462
3621
  }
3463
3622
  declare function createKernel(): Kernel;
3464
3623
 
3465
- 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 IRegisteredViewContribution, type IRule, type IRuleContext, 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 TContractName, 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 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 };