@skill-map/cli 0.38.0 → 0.40.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.
@@ -7,7 +7,7 @@
7
7
  * and each kind's code carries its own fuller type where needed.
8
8
  *
9
9
  * **Spec § A.6, qualified ids.** Every extension is keyed in the registry
10
- * by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash`,
10
+ * by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash-command`,
11
11
  * `my-plugin/my-extractor`). `IExtension.id` carries the **short** id as authored;
12
12
  * `IExtension.pluginId` carries the namespace; the registry composes the
13
13
  * qualifier internally and exposes lookup APIs that operate on either form
@@ -347,6 +347,14 @@ interface IAnnotationContribution {
347
347
  * Runtime extension descriptor as seen by the registry / orchestrator.
348
348
  * Authors writing a manifest on disk do NOT declare `id`, `kind`, or
349
349
  * `pluginId`; the loader injects all three from the filesystem layout.
350
+ *
351
+ * `version` is the runtime invariant: every loaded extension carries a
352
+ * string. External plugins MUST declare it in their manifest (enforced
353
+ * by AJV via `spec/schemas/extensions/base.schema.json#/required`).
354
+ * Built-in extensions inside this repo OMIT `version` from the manifest
355
+ * file (see `IBuiltInManifest` below) because the codegen at
356
+ * `scripts/generate-built-ins.js` stamps the CLI version onto every
357
+ * built-in at build time, alongside the `pluginId` stamp.
350
358
  */
351
359
  interface IExtensionBase {
352
360
  /**
@@ -442,7 +450,7 @@ interface IExtensionBase {
442
450
  * 5. **Internal type aliases**, anything declared as `type` (string-
443
451
  * literal unions, function types, mapped/derived types) that lives
444
452
  * only in TS: `TLogLevel`, `TLogMethodLevel`, `TProgressListener`,
445
- * `TLogFormatter`, `TActionWrite`, `TExecutionMode`, `TGranularity`,
453
+ * `TLogFormatter`, `TActionWrite`, `TExecutionMode`,
446
454
  * `THookFilter`, `THookTrigger`, `TNodeChangeReason`,
447
455
  * `TPluginLoadStatus`, `TPluginStorage`, `TWatchEventKind`. **`T`
448
456
  * prefix.** Use this bucket when `interface` is the wrong shape
@@ -490,7 +498,7 @@ interface IExtensionBase {
490
498
  * This alias survives because:
491
499
  * - claude-specific code legitimately wants to switch on the four
492
500
  * hard-coded values (filter widgets, kind-aware UI cards, the
493
- * `validate-all` built-in rule that maps each kind to its
501
+ * `schema-violation` built-in rule that maps each kind to its
494
502
  * frontmatter schema);
495
503
  * - sorting helpers want a stable `KIND_ORDER` for the canonical
496
504
  * catalog;
@@ -560,7 +568,7 @@ interface LinkLocation {
560
568
  * target), or when the same extractor walks an extractor-internal
561
569
  * dedup boundary. Today the merged edge's `trigger` / `location`
562
570
  * mirror the FIRST occurrence; the array carries every site so the
563
- * `core/redundant-target-reference` analyzer can flag multi-form
571
+ * `core/reference-redundant` analyzer can flag multi-form
564
572
  * references and rename operations can find every author surface.
565
573
  */
566
574
  interface LinkOccurrence {
@@ -722,7 +730,7 @@ interface Link {
722
730
  * same `(source, target, kind, normalizedTrigger)` key. Empty / absent
723
731
  * for legacy emits or for synthetic links (frontmatter-driven
724
732
  * references, sidecar annotations) that have no body position. The
725
- * `core/redundant-target-reference` analyzer walks this array to
733
+ * `core/reference-redundant` analyzer walks this array to
726
734
  * detect multi-form references to the same target from one body.
727
735
  */
728
736
  occurrences?: LinkOccurrence[];
@@ -1059,24 +1067,6 @@ type TPluginStorage = {
1059
1067
  migrations: string[];
1060
1068
  schemas?: Record<string, string>;
1061
1069
  };
1062
- /**
1063
- * Toggle granularity for a plugin / built-in bundle.
1064
- *
1065
- * - `'bundle'` , the plugin id is the only enable/disable key. The whole
1066
- * bundle of extensions follows the toggle; the user cannot
1067
- * enable some extensions of the bundle and disable others.
1068
- * Default for plugins (and for the built-in `claude`
1069
- * bundle, where the provider and its kind-aware extractors
1070
- * form a coherent provider).
1071
- * - `'extension'`, each extension is independently toggle-able under its
1072
- * qualified id `<plugin-id>/<extension-id>`. Used for
1073
- * the built-in `core` bundle (every kernel built-in
1074
- * rule / formatter is removable per spec
1075
- * "no extension is privileged"). Plugin authors opt in
1076
- * only when the plugin ships several orthogonal
1077
- * capabilities a user might reasonably want piecemeal.
1078
- */
1079
- type TGranularity = 'bundle' | 'extension';
1080
1070
  /**
1081
1071
  * Raw `plugin.json` shape after successful AJV validation.
1082
1072
  *
@@ -1099,13 +1089,6 @@ interface IPluginManifest {
1099
1089
  /** Required short description shown in `sm plugins list` and the UI. */
1100
1090
  description: string;
1101
1091
  storage?: TPluginStorage;
1102
- /**
1103
- * Toggle granularity for this plugin. Optional with default
1104
- * `'extension'` since the structure-as-truth refactor (more
1105
- * permissive default: each extension is independently toggleable
1106
- * unless the author opts into bundle-level coupling).
1107
- */
1108
- granularity?: TGranularity;
1109
1092
  author?: string;
1110
1093
  license?: string;
1111
1094
  homepage?: string;
@@ -1186,12 +1169,6 @@ interface IDiscoveredPlugin {
1186
1169
  manifest?: IPluginManifest;
1187
1170
  /** Only present when status === 'enabled'. */
1188
1171
  extensions?: ILoadedExtension[];
1189
- /**
1190
- * Resolved granularity for this plugin. Always populated from
1191
- * `manifest.granularity` (default `'bundle'`) when the manifest parsed;
1192
- * absent for `invalid-manifest` paths where the manifest never validated.
1193
- */
1194
- granularity?: TGranularity;
1195
1172
  /**
1196
1173
  * Runtime-only, never persisted, never spec-modeled.
1197
1174
  *
@@ -2156,7 +2133,7 @@ interface IProvider extends IExtensionBase {
2156
2133
  *
2157
2134
  * Two consumers share the catalog:
2158
2135
  *
2159
- * 1. The `core/reserved-name` analyzer scans every user node and
2136
+ * 1. The `core/name-reserved` analyzer scans every user node and
2160
2137
  * emits a `warn` issue when the node's normalised identifiers
2161
2138
  * (per `IProviderKind.identifiers`) intersect the reserved list
2162
2139
  * for its provider + kind. The user file is silently shadowed
@@ -2433,7 +2410,7 @@ interface IAnalyzerContext {
2433
2410
  * Step 9.6.6, raw parsed sidecar root keyed by `node.path`. Populated
2434
2411
  * by the orchestrator alongside the public `Node.sidecar` overlay so
2435
2412
  * analyzers that inspect plugin namespaces (e.g. the built-in
2436
- * `core/unknown-field` Analyzer) can walk the full tree without
2413
+ * `core/annotation-field-unknown` Analyzer) can walk the full tree without
2437
2414
  * re-reading the file from disk. Absent (or `undefined` per node)
2438
2415
  * when no sidecar accompanies the node, or when the sidecar failed
2439
2416
  * to parse. Treat as read-only.
@@ -2463,7 +2440,7 @@ interface IAnalyzerContext {
2463
2440
  /**
2464
2441
  * Absolute paths of `*.md` files under the project's
2465
2442
  * `.skill-map/jobs/` that no `state_jobs.filePath` references, the
2466
- * built-in `core/job-orphan-file` analyzer projects each as a `warn`
2443
+ * built-in `core/job-file-orphan` analyzer projects each as a `warn`
2467
2444
  * issue. Pre-computed by the driving adapter (CLI / BFF) inside its
2468
2445
  * already-open storage transaction (mirrors the `orphanSidecars`
2469
2446
  * pattern: detection lives outside the analyzer, the analyzer only
@@ -2472,13 +2449,26 @@ interface IAnalyzerContext {
2472
2449
  * orphan files exist. Treat as read-only.
2473
2450
  */
2474
2451
  orphanJobFiles?: readonly string[];
2452
+ /**
2453
+ * Issues emitted by analyzers that already ran in the current pass.
2454
+ * Lets a late-phase analyzer (`core/issue-counter`) compute
2455
+ * cross-analyzer aggregates (per-node severity totals) without
2456
+ * scanning the persisted DB. The orchestrator threads the live
2457
+ * accumulator on every call so any analyzer can opt-in; only the
2458
+ * aggregator reads it today, the rest treat it as inert.
2459
+ *
2460
+ * Treat as read-only, the accumulator is shared with downstream
2461
+ * analyzers and a mutation here would corrupt their view of the
2462
+ * scan. Absent (or empty) on legacy callers that never wired it.
2463
+ */
2464
+ accumulatedIssues?: readonly Issue[];
2475
2465
  /**
2476
2466
  * Set of absolute file paths the operator has opted into for
2477
2467
  * link-validation purposes via `scan.referencePaths`. The driving
2478
2468
  * adapter walks each configured path before the scan and collects
2479
2469
  * every existing file's absolute path here. Files in this set are
2480
2470
  * NOT indexed as graph nodes, the only consumer is
2481
- * `core/broken-ref`, which suppresses its `warn` issue when a
2471
+ * `core/reference-broken`, which suppresses its `warn` issue when a
2482
2472
  * path-style link target falls into the set. Absent / empty when
2483
2473
  * the operator left `scan.referencePaths` empty or when the
2484
2474
  * adapter does not maintain the side index. Treat as read-only.
@@ -2492,7 +2482,7 @@ interface IAnalyzerContext {
2492
2482
  * computed once per scan by the orchestrator (mirroring the same
2493
2483
  * set threaded to the post-walk confidence-lift transform), so
2494
2484
  * analyzers consume it without re-deriving every node's
2495
- * identifiers. The single consumer today is `core/reserved-name`,
2485
+ * identifiers. The single consumer today is `core/name-reserved`,
2496
2486
  * which projects one warn issue per entry; future analyzers MAY
2497
2487
  * read the set for cross-rule cohesion (e.g. an action that
2498
2488
  * suggests rename targets). Absent for legacy callers (older
@@ -2503,7 +2493,7 @@ interface IAnalyzerContext {
2503
2493
  * Absolute path of the scan's project root (cwd of the invocation).
2504
2494
  * Threaded into the analyzer pass so an analyzer that needs to
2505
2495
  * resolve a relative `link.target` to an absolute filesystem path
2506
- * (today only `core/broken-ref`, when consulting
2496
+ * (today only `core/reference-broken`, when consulting
2507
2497
  * `referenceablePaths`) does not have to derive it from
2508
2498
  * `nodes[0].path` heuristics. Absent for legacy callers (older
2509
2499
  * `runScan` sites that never wired the field through). Always an
@@ -2563,6 +2553,26 @@ interface IAnalyzer extends IExtensionBase {
2563
2553
  * "Resolve this issue" affordances.
2564
2554
  */
2565
2555
  precondition?: IExtensionPrecondition;
2556
+ /**
2557
+ * Execution phase. Drives the order the orchestrator schedules
2558
+ * analyzers in:
2559
+ *
2560
+ * - `'detect'` (default), the main pass. Walks nodes / links and
2561
+ * emits its own findings. Most analyzers live here.
2562
+ * - `'aggregate'`, runs strictly AFTER every `detect`-phase
2563
+ * analyzer has finished. The orchestrator passes the full
2564
+ * issue accumulator on `ctx.accumulatedIssues`, so an
2565
+ * aggregator can compute cross-analyzer summaries (per-node
2566
+ * severity totals, etc.) without re-reading the persisted DB.
2567
+ * Aggregators emit contributions; emitting issues is allowed
2568
+ * but uncommon.
2569
+ *
2570
+ * Two-phase scheduling is the clean alternative to ordering
2571
+ * analyzers by hand in the built-ins registry: filesystem-sorted
2572
+ * generators can keep their alphabetical output, the orchestrator
2573
+ * applies the phase sort at run-time.
2574
+ */
2575
+ phase?: 'detect' | 'aggregate';
2566
2576
  evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
2567
2577
  }
2568
2578
 
@@ -3153,7 +3163,7 @@ interface RunScanOptions {
3153
3163
  /**
3154
3164
  * Step 9.6.6, runtime catalog of plugin-contributed annotation keys
3155
3165
  * (the same shape `kernel.getRegisteredAnnotationKeys()` returns).
3156
- * Threaded into the rule pass so `core/unknown-field` can
3166
+ * Threaded into the rule pass so `core/annotation-field-unknown` can
3157
3167
  * legitimise registered plugin namespaces / root keys without
3158
3168
  * re-walking the manifests. Absent → empty catalog (every plugin
3159
3169
  * key is treated as unknown). Built-in catalog from
@@ -3271,7 +3281,7 @@ interface RunScanOptions {
3271
3281
  * Pre-computed absolute paths of orphan job MD files (files under
3272
3282
  * `.skill-map/jobs/` whose absolute path appears nowhere in
3273
3283
  * `state_jobs.filePath`). Threaded into the rule pass so the
3274
- * built-in `core/job-orphan-file` rule can project each as a `warn`
3284
+ * built-in `core/job-file-orphan` rule can project each as a `warn`
3275
3285
  * issue without the kernel reaching for the storage port or doing
3276
3286
  * its own FS walk. The driving adapter (CLI, BFF) computes this
3277
3287
  * inside its already-open storage transaction via
@@ -3286,7 +3296,7 @@ interface RunScanOptions {
3286
3296
  * Side set of absolute file paths the operator opted into for
3287
3297
  * link-validation purposes via `scan.referencePaths`. Threaded
3288
3298
  * through to `IAnalyzerContext.referenceablePaths` so the built-in
3289
- * `core/broken-ref` rule can suppress its `warn` for path-style
3299
+ * `core/reference-broken` rule can suppress its `warn` for path-style
3290
3300
  * links whose target lands in the set. Files are NOT walked by
3291
3301
  * the kernel, the driving adapter populates the set before
3292
3302
  * calling `runScan`. Absent / empty when the operator left
@@ -3747,7 +3757,7 @@ interface IUpdateCheckCache {
3747
3757
  * any drift a compile error).
3748
3758
  *
3749
3759
  * Domain types (`IPluginManifest`, `ILoadedExtension`, `IDiscoveredPlugin`,
3750
- * `TPluginStorage`, `TPluginLoadStatus`, `TGranularity`) live in
3760
+ * `TPluginStorage`, `TPluginLoadStatus`) live in
3751
3761
  * `kernel/types/plugin.ts` because they are spec-mirroring DTOs, not
3752
3762
  * port-shape types. The port re-exports them for callers that import
3753
3763
  * from the ports barrel.
@@ -4307,4 +4317,4 @@ interface Kernel {
4307
4317
  }
4308
4318
  declare function createKernel(): Kernel;
4309
4319
 
4310
- export { type Confidence, DuplicateExtensionError, EXTENSION_KINDS, type ExecutionFailureReason, type ExecutionKind, type ExecutionRecord, type ExecutionRunner, type ExecutionStatus, ExportQueryError, 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 IExtension, type IExtensionBase, type IExternalRef, 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 IProvider, type IRawNode, type IRegisteredAnnotationKey, type IRegisteredViewContribution, type IRunOptions, type IRunResult, type IScanDelta, 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 LinkOccurrence, 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 TPluginStore, type TProgressListener, type TSettingDeclaration, 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 };
4320
+ export { type Confidence, DuplicateExtensionError, EXTENSION_KINDS, type ExecutionFailureReason, type ExecutionKind, type ExecutionRecord, type ExecutionRunner, type ExecutionStatus, ExportQueryError, 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 IExtension, type IExtensionBase, type IExternalRef, 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 IProvider, type IRawNode, type IRegisteredAnnotationKey, type IRegisteredViewContribution, type IRunOptions, type IRunResult, type IScanDelta, 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 LinkOccurrence, 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 THookFilter, type THookTrigger, type TInputTypeName, type TLogLevel, type TLogMethodLevel, type TNodeChangeReason, type TPluginLoadStatus, type TPluginStorage, type TPluginStore, type TProgressListener, type TSettingDeclaration, 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 };
@@ -101,7 +101,7 @@ import cl100k_base from "js-tiktoken/ranks/cl100k_base";
101
101
  // package.json
102
102
  var package_default = {
103
103
  name: "@skill-map/cli",
104
- version: "0.38.0",
104
+ version: "0.40.0",
105
105
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
106
106
  license: "MIT",
107
107
  type: "module",
@@ -1426,7 +1426,8 @@ function recomputeLinkCounts(nodes, links) {
1426
1426
  for (const link of links) {
1427
1427
  const source = byPath2.get(link.source);
1428
1428
  if (source) source.linksOutCount += 1;
1429
- const target = byPath2.get(link.target);
1429
+ const targetKey = link.resolvedTarget ?? link.target;
1430
+ const target = byPath2.get(targetKey);
1430
1431
  if (target) target.linksInCount += 1;
1431
1432
  }
1432
1433
  }
@@ -1460,8 +1461,8 @@ function isExternalUrlLink(link) {
1460
1461
  }
1461
1462
 
1462
1463
  // kernel/orchestrator/analyzers.ts
1463
- async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals) {
1464
- const issues = [];
1464
+ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals, seedIssues = []) {
1465
+ const issues = [...seedIssues];
1465
1466
  const contributions = [];
1466
1467
  const validators = loadSchemaValidators();
1467
1468
  void registeredActionIds;
@@ -1469,7 +1470,8 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1469
1470
  relativePath: o.relativePath,
1470
1471
  expectedMdPath: o.expectedMdPath
1471
1472
  }));
1472
- for (const analyzer of analyzers) {
1473
+ const scheduled = orderAnalyzersByPhase(analyzers);
1474
+ for (const analyzer of scheduled) {
1473
1475
  const qualifiedId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
1474
1476
  const declaredContributions = readDeclaredContributions(analyzer);
1475
1477
  const emitContribution = (nodePath, contributionId, payload) => {
@@ -1522,6 +1524,11 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1522
1524
  annotationContributions,
1523
1525
  viewContributions,
1524
1526
  orphanJobFiles,
1527
+ // `issues` is the live accumulator, mutated by `issues.push(...)`
1528
+ // below as each analyzer's emission lands. Late-phase analyzers
1529
+ // (`core/issue-counter`) read it to compute cross-analyzer
1530
+ // aggregates. Treat as read-only on the analyzer side.
1531
+ accumulatedIssues: issues,
1525
1532
  ...referenceablePaths ? { referenceablePaths } : {},
1526
1533
  ...cwd ? { cwd } : {},
1527
1534
  ...reservedNodePaths ? { reservedNodePaths } : {},
@@ -1538,6 +1545,12 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1538
1545
  }
1539
1546
  return { issues, contributions };
1540
1547
  }
1548
+ function orderAnalyzersByPhase(analyzers) {
1549
+ return analyzers.slice().sort((a, b) => phaseRank(a) - phaseRank(b));
1550
+ }
1551
+ function phaseRank(a) {
1552
+ return a.phase === "aggregate" ? 1 : 0;
1553
+ }
1541
1554
  function validateIssue(analyzer, issue, emitter) {
1542
1555
  const severity = issue.severity;
1543
1556
  if (severity !== "error" && severity !== "warn" && severity !== "info") {
@@ -1834,10 +1847,9 @@ function resolveByName(link, indexes, ctx) {
1834
1847
  const winner = candidates.find((c) => allowedKinds.includes(c.kind));
1835
1848
  return winner ? winner.path : "none";
1836
1849
  }
1837
- function lookupAllowedKinds(link, indexes, ctx) {
1838
- const sourceNode = indexes.nodeByPath.get(link.source);
1839
- if (!sourceNode) return void 0;
1840
- return ctx.providerResolution.get(sourceNode.provider)?.[link.kind];
1850
+ function lookupAllowedKinds(link, _indexes, ctx) {
1851
+ if (ctx.activeProvider === null) return void 0;
1852
+ return ctx.providerResolution.get(ctx.activeProvider)?.[link.kind];
1841
1853
  }
1842
1854
  function stripTriggerSigil(normalized) {
1843
1855
  if (!normalized) return null;
@@ -3155,7 +3167,7 @@ async function runScanInternal(_kernel, options) {
3155
3167
  else walked.internalLinks.push(link);
3156
3168
  }
3157
3169
  walked.signals = resolved.resolvedSignals;
3158
- const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes);
3170
+ const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes, activeProviderId);
3159
3171
  walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
3160
3172
  recomputeLinkCounts(walked.nodes, walked.internalLinks);
3161
3173
  recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
@@ -3178,11 +3190,15 @@ async function runScanInternal(_kernel, options) {
3178
3190
  emitter,
3179
3191
  hookDispatcher,
3180
3192
  postWalkCtx.reservedNodePaths,
3181
- walked.signals
3193
+ walked.signals,
3194
+ // Seed the accumulator with orchestrator-emitted frontmatter
3195
+ // issues so the aggregate phase (`core/issue-counter`) counts
3196
+ // them on the per-node chip. The seeds are echoed back on
3197
+ // `analyzerResult.issues`, no explicit push is needed below.
3198
+ walked.frontmatterIssues
3182
3199
  );
3183
3200
  mergeAnalyzerEmissions(walked, analyzerResult, exts.analyzers);
3184
3201
  const issues = analyzerResult.issues;
3185
- for (const issue of walked.frontmatterIssues) issues.push(issue);
3186
3202
  const silenced = options.ignoreFilter ? (path) => options.ignoreFilter.ignores(path) : void 0;
3187
3203
  const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues, silenced) : [];
3188
3204
  const stats = buildScanStats(walked, issues, start);
@@ -3191,14 +3207,14 @@ async function runScanInternal(_kernel, options) {
3191
3207
  await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
3192
3208
  return buildScanReturn(walked, issues, renameOps, stats, options, setup);
3193
3209
  }
3194
- function buildPostWalkTransformCtx(providers, nodes) {
3210
+ function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
3195
3211
  const { kindRegistry, providerResolution, reservedNamesByProviderKind } = buildProviderIndexes(providers);
3196
3212
  const reservedNodePaths = buildReservedNodePaths(
3197
3213
  nodes,
3198
3214
  kindRegistry,
3199
3215
  reservedNamesByProviderKind
3200
3216
  );
3201
- return { kindRegistry, providerResolution, reservedNodePaths };
3217
+ return { kindRegistry, providerResolution, activeProvider, reservedNodePaths };
3202
3218
  }
3203
3219
  function buildProviderIndexes(providers) {
3204
3220
  const kindRegistry = /* @__PURE__ */ new Map();