@skill-map/cli 0.56.0 → 0.57.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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // kernel/i18n/registry.texts.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2dc805c3-48a5-5cde-8fdd-5ba75f99be02")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2d36a27f-0bac-51aa-8fb3-3b3e43eff4e8")}catch(e){}}();
4
4
  var REGISTRY_TEXTS = {
5
5
  duplicateExtension: "Extension already registered: {{kind}}:{{qualifiedId}}",
6
6
  unknownKind: "Unknown extension kind: {{kind}}",
@@ -102,7 +102,7 @@ import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
102
102
  // package.json
103
103
  var package_default = {
104
104
  name: "@skill-map/cli",
105
- version: "0.56.0",
105
+ version: "0.57.0",
106
106
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
107
107
  license: "MIT",
108
108
  type: "module",
@@ -1269,8 +1269,49 @@ function runActionProjections(actions, nodes, links, emitter) {
1269
1269
  return { contributions, contributionErrors };
1270
1270
  }
1271
1271
 
1272
+ // kernel/orchestrator/confidence-score.ts
1273
+ function foldConfidence(base, ops) {
1274
+ let running = base;
1275
+ for (const op of ops) {
1276
+ if (op.kind === "set") running = op.value;
1277
+ }
1278
+ for (const op of ops) {
1279
+ if (op.kind === "delta") running += op.value;
1280
+ }
1281
+ for (const op of ops) {
1282
+ if (op.kind === "floor") running = Math.max(running, op.value);
1283
+ }
1284
+ for (const op of ops) {
1285
+ if (op.kind === "ceil") running = Math.min(running, op.value);
1286
+ }
1287
+ return clamp01(running);
1288
+ }
1289
+ function clamp01(n) {
1290
+ if (n < 0) return 0;
1291
+ if (n > 1) return 1;
1292
+ return n;
1293
+ }
1294
+ function applyConfidenceAdjustments(adjustments) {
1295
+ if (adjustments.length === 0) return;
1296
+ const byLink = /* @__PURE__ */ new Map();
1297
+ for (const adj of adjustments) {
1298
+ const bucket = byLink.get(adj.link);
1299
+ if (bucket) bucket.push(adj);
1300
+ else byLink.set(adj.link, [adj]);
1301
+ }
1302
+ for (const [link, adjs] of byLink) {
1303
+ const ops = [...adjs].sort(compareAdjustments).map((a) => a.op);
1304
+ link.confidence = foldConfidence(link.confidence, ops);
1305
+ }
1306
+ }
1307
+ function compareAdjustments(a, b) {
1308
+ if (a.pluginId !== b.pluginId) return a.pluginId < b.pluginId ? -1 : 1;
1309
+ if (a.extensionId !== b.extensionId) return a.extensionId < b.extensionId ? -1 : 1;
1310
+ return 0;
1311
+ }
1312
+
1272
1313
  // kernel/orchestrator/analyzers.ts
1273
- async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, brokenLinks, signals, seedIssues = []) {
1314
+ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, brokenLinks, nameCollisions, signals, seedIssues = []) {
1274
1315
  const issues = [...seedIssues];
1275
1316
  const contributions = [];
1276
1317
  const contributionErrors = [];
@@ -1281,7 +1322,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1281
1322
  expectedMdPath: o.expectedMdPath
1282
1323
  }));
1283
1324
  const scheduled = orderAnalyzersByPhase(analyzers);
1325
+ const scoreAdjustments = [];
1326
+ const scorableLinks = new Set(internalLinks);
1327
+ let scoresFolded = false;
1328
+ const foldScores = () => {
1329
+ if (scoresFolded) return;
1330
+ scoresFolded = true;
1331
+ applyConfidenceAdjustments(scoreAdjustments);
1332
+ };
1284
1333
  for (const analyzer of scheduled) {
1334
+ if (analyzer.phase !== "score") foldScores();
1285
1335
  const qualifiedId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
1286
1336
  const declaredContributions = readDeclaredContributionRefs(analyzer);
1287
1337
  const emitContribution = (nodePath, ref, payload) => {
@@ -1344,6 +1394,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1344
1394
  emittedAt: Date.now()
1345
1395
  });
1346
1396
  };
1397
+ const adjustConfidence = analyzer.phase === "score" ? (link, op) => {
1398
+ if (scorableLinks.has(link)) {
1399
+ scoreAdjustments.push({
1400
+ link,
1401
+ pluginId: analyzer.pluginId,
1402
+ extensionId: analyzer.id,
1403
+ op
1404
+ });
1405
+ }
1406
+ } : void 0;
1347
1407
  const emitted = await analyzer.evaluate({
1348
1408
  nodes,
1349
1409
  links: internalLinks,
@@ -1355,7 +1415,6 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1355
1415
  sidecarRoots,
1356
1416
  annotationContributions,
1357
1417
  viewContributions,
1358
- orphanJobFiles,
1359
1418
  // `issues` is the live accumulator, mutated by `issues.push(...)`
1360
1419
  // below as each analyzer's emission lands. Late-phase analyzers
1361
1420
  // (`core/issue-counter`) read it to compute cross-analyzer
@@ -1365,7 +1424,9 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1365
1424
  ...cwd ? { cwd } : {},
1366
1425
  ...reservedNodePaths ? { reservedNodePaths } : {},
1367
1426
  ...brokenLinks ? { brokenLinks } : {},
1427
+ ...nameCollisions && nameCollisions.size > 0 ? { nameCollisions } : {},
1368
1428
  ...signals && signals.length > 0 ? { signals } : {},
1429
+ ...adjustConfidence ? { adjustConfidence } : {},
1369
1430
  emitContribution
1370
1431
  });
1371
1432
  for (const issue of emitted) {
@@ -1376,13 +1437,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1376
1437
  emitter.emit(evt);
1377
1438
  await hookDispatcher.dispatch("analyzer.completed", evt);
1378
1439
  }
1379
- return { issues, contributions, contributionErrors };
1440
+ foldScores();
1441
+ return { issues, contributions, contributionErrors, linkScores: scoreAdjustments };
1380
1442
  }
1381
1443
  function orderAnalyzersByPhase(analyzers) {
1382
1444
  return analyzers.slice().sort((a, b) => phaseRank(a) - phaseRank(b));
1383
1445
  }
1384
1446
  function phaseRank(a) {
1385
- return a.phase === "aggregate" ? 1 : 0;
1447
+ if (a.phase === "score") return 0;
1448
+ if (a.phase === "aggregate") return 2;
1449
+ return 1;
1386
1450
  }
1387
1451
  function validateIssue(analyzer, issue, emitter) {
1388
1452
  const severity = issue.severity;
@@ -1405,6 +1469,13 @@ function validateIssue(analyzer, issue, emitter) {
1405
1469
  return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
1406
1470
  }
1407
1471
 
1472
+ // kernel/orchestrator/frontmatter-issue-ids.ts
1473
+ var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
1474
+ "frontmatter-invalid",
1475
+ "frontmatter-malformed",
1476
+ "frontmatter-parse-error"
1477
+ ]);
1478
+
1408
1479
  // kernel/orchestrator/cache.ts
1409
1480
  function indexPriorSnapshot(prior) {
1410
1481
  const priorNodesByPath = /* @__PURE__ */ new Map();
@@ -1433,17 +1504,6 @@ function indexPriorLinks(links, priorNodePaths, byOriginating) {
1433
1504
  else byOriginating.set(key, [link]);
1434
1505
  }
1435
1506
  }
1436
- var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
1437
- "frontmatter-invalid",
1438
- "frontmatter-malformed",
1439
- // Audit L1: parser parse-error is emitted by
1440
- // `buildFreshNodeAndValidateFrontmatter` from `raw.parseIssues`. The
1441
- // raw.parseIssues only flows through the non-cache path; a cached
1442
- // node skips the rebuild, so the prior issue MUST survive the
1443
- // incremental scan or the warning silently disappears on a clean
1444
- // re-scan of an unchanged file.
1445
- "frontmatter-parse-error"
1446
- ]);
1447
1507
  function indexPriorFrontmatterIssues(issues, byNode) {
1448
1508
  for (const issue of issues) {
1449
1509
  if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
@@ -1641,15 +1701,47 @@ function readDirname(node) {
1641
1701
  const base = pathPosix.basename(dir);
1642
1702
  return base.length > 0 ? base : null;
1643
1703
  }
1704
+ function collectNameCollisions(nodes, kindRegistry) {
1705
+ const byName = indexNameClaims(nodes, kindRegistry);
1706
+ const collisions = /* @__PURE__ */ new Map();
1707
+ for (const [name, claims] of byName) {
1708
+ const distinct = dedupeClaimsByPath(claims);
1709
+ if (distinct.length >= 2) collisions.set(name, distinct);
1710
+ }
1711
+ return collisions;
1712
+ }
1713
+ function indexNameClaims(nodes, kindRegistry) {
1714
+ const byName = /* @__PURE__ */ new Map();
1715
+ for (const node of nodes) {
1716
+ const name = resolvableName(node, kindRegistry);
1717
+ if (name === null) continue;
1718
+ const bucket = byName.get(name) ?? [];
1719
+ bucket.push({ path: node.path, kind: node.kind });
1720
+ byName.set(name, bucket);
1721
+ }
1722
+ return byName;
1723
+ }
1724
+ function resolvableName(node, kindRegistry) {
1725
+ const descriptor = kindRegistry.get(`${node.provider}/${node.kind}`);
1726
+ if (!descriptor?.identifiers?.includes("frontmatter.name")) return null;
1727
+ const raw = node.frontmatter?.["name"];
1728
+ if (typeof raw !== "string" || raw.length === 0) return null;
1729
+ const normalised = normalizeTrigger(raw);
1730
+ return normalised.length > 0 ? normalised : null;
1731
+ }
1732
+ function dedupeClaimsByPath(claims) {
1733
+ return [...new Map(claims.map((c) => [c.path, c])).values()].sort(
1734
+ (a, b) => a.path.localeCompare(b.path)
1735
+ );
1736
+ }
1644
1737
 
1645
1738
  // kernel/orchestrator/lift-resolved-link-confidence.ts
1646
- var RESERVED_TARGET_CONFIDENCE = 0.1;
1647
- var BROKEN_TARGET_CONFIDENCE = 0.5;
1648
1739
  function liftResolvedLinkConfidence(links, nodes, ctx) {
1649
- if (!links.some((l) => l.confidence < 1)) return;
1740
+ if (links.length === 0) return;
1650
1741
  const indexes = buildIndexes(nodes, ctx);
1651
1742
  for (const link of links) {
1652
- if (link.confidence < 1) applyResolution(link, indexes, ctx);
1743
+ link.confidence = 1;
1744
+ applyResolution(link, indexes, ctx);
1653
1745
  }
1654
1746
  }
1655
1747
  function collectBrokenLinks(links, nodes, ctx) {
@@ -1663,15 +1755,8 @@ function collectBrokenLinks(links, nodes, ctx) {
1663
1755
  }
1664
1756
  function applyResolution(link, indexes, ctx) {
1665
1757
  const resolution = resolve5(link, indexes, ctx);
1666
- if (resolution === "none") {
1667
- if (isGenuinelyBroken(link, indexes)) {
1668
- link.confidence = Math.min(link.confidence, BROKEN_TARGET_CONFIDENCE);
1669
- }
1670
- return;
1671
- }
1758
+ if (resolution === "none") return;
1672
1759
  link.resolvedTarget = resolution;
1673
- if (indexes.nodeByPath.get(resolution)?.virtual) return;
1674
- link.confidence = ctx.reservedNodePaths.has(resolution) ? RESERVED_TARGET_CONFIDENCE : 1;
1675
1760
  }
1676
1761
  function buildIndexes(nodes, ctx) {
1677
1762
  const byPath2 = /* @__PURE__ */ new Set();
@@ -3143,6 +3228,7 @@ async function runScanInternal(_kernel, options) {
3143
3228
  const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes, activeProviderId);
3144
3229
  walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
3145
3230
  const brokenLinks = collectBrokenLinks(walked.internalLinks, walked.nodes, postWalkCtx);
3231
+ const nameCollisions = collectNameCollisions(walked.nodes, postWalkCtx.kindRegistry);
3146
3232
  recomputeLinkCounts(walked.nodes, walked.internalLinks);
3147
3233
  recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
3148
3234
  await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
@@ -3157,7 +3243,6 @@ async function runScanInternal(_kernel, options) {
3157
3243
  walked.sidecarRoots,
3158
3244
  options.annotationContributions ?? [],
3159
3245
  options.viewContributions ?? [],
3160
- options.orphanJobFiles ?? [],
3161
3246
  options.referenceablePaths,
3162
3247
  options.cwd,
3163
3248
  registeredActionIds,
@@ -3165,6 +3250,7 @@ async function runScanInternal(_kernel, options) {
3165
3250
  hookDispatcher,
3166
3251
  postWalkCtx.reservedNodePaths,
3167
3252
  brokenLinks,
3253
+ nameCollisions,
3168
3254
  walked.signals,
3169
3255
  // Seed the accumulator with orchestrator-emitted frontmatter
3170
3256
  // issues so the aggregate phase (`core/issue-counter`) counts
@@ -3187,7 +3273,7 @@ async function runScanInternal(_kernel, options) {
3187
3273
  const scanCompletedEvent = makeEvent("scan.completed", { stats });
3188
3274
  emitter.emit(scanCompletedEvent);
3189
3275
  await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
3190
- return buildScanReturn(walked, issues, renameOps, stats, options, setup);
3276
+ return buildScanReturn(walked, issues, renameOps, stats, options, setup, analyzerResult.linkScores);
3191
3277
  }
3192
3278
  function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
3193
3279
  const { kindRegistry, providerResolution, reservedNamesByProviderKind } = buildProviderIndexes(providers);
@@ -3315,7 +3401,7 @@ function buildScanStats(walked, issues, start) {
3315
3401
  durationMs: Date.now() - start
3316
3402
  };
3317
3403
  }
3318
- function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
3404
+ function buildScanReturn(walked, issues, renameOps, stats, options, setup, linkScores) {
3319
3405
  return {
3320
3406
  result: {
3321
3407
  schemaVersion: 1,
@@ -3337,6 +3423,7 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
3337
3423
  enrichments: walked.enrichments,
3338
3424
  contributions: walked.contributions,
3339
3425
  contributionErrors: walked.contributionErrors,
3426
+ linkScores,
3340
3427
  freshlyRunTuples: walked.freshlyRunTuples
3341
3428
  };
3342
3429
  }
@@ -3766,4 +3853,4 @@ export {
3766
3853
  runScanWithRenames
3767
3854
  };
3768
3855
  //# sourceMappingURL=index.js.map
3769
- //# debugId=2dc805c3-48a5-5cde-8fdd-5ba75f99be02
3856
+ //# debugId=2d36a27f-0bac-51aa-8fb3-3b3e43eff4e8
@@ -816,6 +816,29 @@ type LinkKind = 'invokes' | 'references' | 'mentions' | 'supersedes' | 'points';
816
816
  * enum check. Missing confidence defaults to `ConfidenceTier.MEDIUM`.
817
817
  */
818
818
  type Confidence = number;
819
+ /**
820
+ * A single confidence-scoring operation a `score`-phase analyzer
821
+ * contributes via `ctx.adjustConfidence(link, op)`. The orchestrator
822
+ * folds every op on a link into the final `confidence` (see
823
+ * `orchestrator/confidence-score.ts` for the algebra):
824
+ * - `set` hard override (resolved → 1.0, reserved → 0.1)
825
+ * - `delta` additive, may be negative (third-party heuristics)
826
+ * - `ceil` upper cap, lowers only (broken → 0.5)
827
+ * - `floor` lower bound, raises only
828
+ */
829
+ type TConfidenceOp = {
830
+ readonly kind: 'set';
831
+ readonly value: number;
832
+ } | {
833
+ readonly kind: 'delta';
834
+ readonly value: number;
835
+ } | {
836
+ readonly kind: 'ceil';
837
+ readonly value: number;
838
+ } | {
839
+ readonly kind: 'floor';
840
+ readonly value: number;
841
+ };
819
842
  type Severity = 'error' | 'warn' | 'info';
820
843
  type Stability = 'experimental' | 'stable' | 'deprecated';
821
844
  /**
@@ -1132,11 +1155,10 @@ interface Signal {
1132
1155
  * raw extractor emissions (before the resolver runs). When
1133
1156
  * `outcome === 'materialised'`, `winnerIndex` points into `candidates[]`
1134
1157
  * of the candidate the resolver chose; a corresponding `Link` was added
1135
- * to the graph. When `outcome === 'rejected'`, one of `rejectedBy` /
1136
- * `extractorDisabled` / `belowFloor` is set and no Link materialised.
1137
- * Both materialised and rejected Signals remain on
1138
- * `IAnalyzerContext.signals` so the `core/signal-collision` analyzer can
1139
- * surface losers as `warn` issues. Mirrors
1158
+ * to the graph. When `outcome === 'rejected'`, `rejectedBy` is set and
1159
+ * no Link materialised. Both materialised and rejected Signals remain on
1160
+ * `IAnalyzerContext.signals` so the `core/extractor-collision` analyzer
1161
+ * can surface losers as `warn` issues. Mirrors
1140
1162
  * `signal.schema.json#/properties/resolution`.
1141
1163
  */
1142
1164
  resolution?: ISignalResolution;
@@ -1162,24 +1184,6 @@ interface ISignalResolution {
1162
1184
  extractorId: string;
1163
1185
  reason: 'kind-priority' | 'higher-confidence' | 'longer-range' | 'earlier-declaration';
1164
1186
  };
1165
- /**
1166
- * Phase 4+ stub: populated when every candidate of this Signal came from
1167
- * an extractor the operator disabled via
1168
- * `plugins.<id>.extensions.<extId>.enabled`. Today the resolver never
1169
- * sets this; documented so analyzer surfaces can be built when the filter
1170
- * lands.
1171
- */
1172
- extractorDisabled?: {
1173
- extractorId: string;
1174
- };
1175
- /**
1176
- * Phase 4+ stub: populated when every candidate's `confidence` fell below
1177
- * the configured floor. Today the resolver materialises every Signal that
1178
- * survives overlap regardless of confidence.
1179
- */
1180
- belowFloor?: {
1181
- threshold: number;
1182
- };
1183
1187
  }
1184
1188
  interface IssueFix {
1185
1189
  summary?: string;
@@ -1679,6 +1683,55 @@ declare function makePluginStore(opts: {
1679
1683
  persistDedicated?: IDedicatedStorePersist;
1680
1684
  }): TPluginStore | undefined;
1681
1685
 
1686
+ /**
1687
+ * Combination algebra for plugin-contributed link-confidence adjustments.
1688
+ *
1689
+ * Confidence ([0,1]) starts at the kernel's 1.0 baseline (seeded on every
1690
+ * link by `liftResolvedLinkConfidence`). `score`-phase analyzers then
1691
+ * contribute attributed operations via `ctx.adjustConfidence(link, op)`;
1692
+ * the orchestrator buffers them and folds all ops for a link into a final
1693
+ * value with `foldConfidence`. The kernel dogfoods this exact API through
1694
+ * two built-in score-phase detectors, each co-locating its penalty op
1695
+ * with the finding it reports: `core/name-reserved`
1696
+ * (reserved → `delta -0.9` → 0.1), `core/reference-broken`
1697
+ * (broken → `delta -0.5` → 0.5). A clean-resolved or untouched link folds
1698
+ * to `clamp(base)` and keeps the 1.0 baseline.
1699
+ *
1700
+ * The fold is deterministic and order-independent across the four
1701
+ * buckets (set / delta / floor / ceil are each commutative):
1702
+ * 1. base = the extractor-emitted confidence.
1703
+ * 2. `set`: a hard override. When more than one `set` lands on a link
1704
+ * the LAST in the caller's canonical order wins (the caller pre-
1705
+ * sorts ops by `(pluginId, extensionId)` so the winner is stable);
1706
+ * a single `set` simply replaces the base.
1707
+ * 3. `delta`: additive (may be negative), summed.
1708
+ * 4. `floor`: raise to at least the value (`max`).
1709
+ * 5. `ceil`: lower to at most the value (`min`) — today's broken cap.
1710
+ * Applied AFTER floor so a cap dominates a floor/ceil collision.
1711
+ * 6. clamp to [0,1] ONCE at the end, so opposing deltas round-trip
1712
+ * (e.g. `-0.4` then `+0.4` returns to base, never clipped midway).
1713
+ *
1714
+ * A link no scorer touches folds to `clamp(base)` (the kernel's 1.0
1715
+ * baseline), identical to a clean-resolved link. With only the built-in
1716
+ * detectors active a link is at most reserved OR broken (mutually
1717
+ * exclusive), so it gets at most one penalty delta and folds to 0.1 / 0.5
1718
+ * respectively; a clean link keeps the 1.0 base. Third-party scorers layer
1719
+ * additional ops on top, summed deterministically before the single clamp.
1720
+ */
1721
+
1722
+ /**
1723
+ * One attributed adjustment, buffered by the orchestrator as a scorer
1724
+ * calls `adjustConfidence`. `link` is held by object identity (the same
1725
+ * link objects flow through the post-walk pipeline). Persisted to
1726
+ * `scan_link_scores` for the "why is this link at X?" audit trail.
1727
+ */
1728
+ interface IConfidenceAdjustment {
1729
+ readonly link: Link;
1730
+ readonly pluginId: string;
1731
+ readonly extensionId: string;
1732
+ readonly op: TConfidenceOp;
1733
+ }
1734
+
1682
1735
  /**
1683
1736
  * Row-level filter for `port.scans.findNodes(...)` (driven by
1684
1737
  * `sm list`'s flags). All fields are optional, an empty filter
@@ -1727,11 +1780,11 @@ interface INodeCounts {
1727
1780
  issues: number;
1728
1781
  }
1729
1782
  /**
1730
- * Lightweight option bag for `port.scans.persist`. Mirrors the trailing
1731
- * arguments of the legacy `persistScanResult(db, result, renameOps,
1732
- * extractorRuns, enrichments)` free function so the adapter
1733
- * implementation is a one-line delegation today; the named-bag shape
1734
- * tomorrow lets new optional inputs land without breaking callers.
1783
+ * Lightweight option bag for `port.scans.persist`. Mirrors the optional
1784
+ * inputs of the `persistScanResult(db, result, inputs)` free function
1785
+ * (`IPersistScanInputs` in `kernel/adapters/sqlite/scan-persistence.ts`),
1786
+ * so the adapter implementation is a one-line delegation; the named-bag
1787
+ * shape lets new optional inputs land without breaking callers.
1735
1788
  */
1736
1789
  interface IPersistOptions {
1737
1790
  renameOps?: RenameOp[];
@@ -1747,6 +1800,18 @@ interface IPersistOptions {
1747
1800
  * scan clears any stale rows). Surfaced by `sm plugins doctor`.
1748
1801
  */
1749
1802
  contributionErrors?: IContributionErrorRecord[];
1803
+ /**
1804
+ * Per-op confidence-attribution audit trail for `scan_link_scores`.
1805
+ * One entry per attributed `ctx.adjustConfidence(link, op)` call a
1806
+ * `score`-phase analyzer buffered this scan; the orchestrator already
1807
+ * folded them into `link.confidence`, so these rows are the attribution
1808
+ * (which plugin / extension / op moved a given link, plus the folded
1809
+ * `result_confidence`). Plain REPLACE-ALL into `scan_link_scores`
1810
+ * (delete all, then insert), the same posture as `scan_issues`. Empty /
1811
+ * absent wipes the table (a scan whose scorers touched nothing clears
1812
+ * any stale rows).
1813
+ */
1814
+ linkScores?: IConfidenceAdjustment[];
1750
1815
  /**
1751
1816
  * Phase 3 / View contribution system, active runtime catalog of
1752
1817
  * registered view contributions, keyed by qualified id
@@ -2983,18 +3048,6 @@ interface IAnalyzerContext {
2983
3048
  * runScan sites that never wired the catalog through).
2984
3049
  */
2985
3050
  viewContributions?: readonly IRegisteredViewContribution[];
2986
- /**
2987
- * Absolute paths of `*.md` files under the project's
2988
- * `.skill-map/jobs/` that no `state_jobs.filePath` references, the
2989
- * built-in `core/job-file-orphan` analyzer projects each as a `warn`
2990
- * issue. Pre-computed by the driving adapter (CLI / BFF) inside its
2991
- * already-open storage transaction (mirrors the `orphanSidecars`
2992
- * pattern: detection lives outside the analyzer, the analyzer only
2993
- * projects). Absent (or empty) when the caller does not maintain a
2994
- * jobs directory, when the storage path is unavailable, or when no
2995
- * orphan files exist. Treat as read-only.
2996
- */
2997
- orphanJobFiles?: readonly string[];
2998
3051
  /**
2999
3052
  * Issues emitted by analyzers that already ran in the current pass.
3000
3053
  * Lets a late-phase analyzer (`core/issue-counter`) compute
@@ -3051,6 +3104,23 @@ interface IAnalyzerContext {
3051
3104
  * that never wired the field through, the rule then emits nothing.
3052
3105
  */
3053
3106
  brokenLinks?: ReadonlySet<Link>;
3107
+ /**
3108
+ * Names claimed by two or more distinct nodes, keyed by the normalised
3109
+ * name. A node contributes only when its kind declares `frontmatter.name`
3110
+ * as a resolution identifier (so plain `core/markdown` nodes, addressed
3111
+ * by path, never collide) and it carries a non-empty `name`. Names that
3112
+ * normalise to the same value (e.g. `Deploy` / `deploy`) collide, mirroring
3113
+ * how the resolver keys on the normalised identifier. Computed once per
3114
+ * scan by the orchestrator from the same kind registry the resolver uses,
3115
+ * so analyzers project it without re-deriving (the `brokenLinks` /
3116
+ * `reservedNodePaths` precompute-and-project pattern). The single consumer
3117
+ * is `core/name-collision`, which emits one `error` per entry. Absent for
3118
+ * legacy callers that never wired the field through.
3119
+ */
3120
+ nameCollisions?: ReadonlyMap<string, readonly {
3121
+ readonly path: string;
3122
+ readonly kind: string;
3123
+ }[]>;
3054
3124
  /**
3055
3125
  * Absolute path of the scan's project root (cwd of the invocation).
3056
3126
  * Threaded into the analyzer pass so an analyzer that needs to
@@ -3095,6 +3165,17 @@ interface IAnalyzerContext {
3095
3165
  * emissions (`scan_contributions`).
3096
3166
  */
3097
3167
  emitContribution<C extends IViewContribution>(nodePath: string, ref: C, payload: SlotPayload<C['slot']>): void;
3168
+ /**
3169
+ * Contribute a confidence adjustment to a link. Usable ONLY from a
3170
+ * `score`-phase analyzer; the orchestrator records it attributed to
3171
+ * the calling extension (`pluginId` / `extensionId`, like
3172
+ * `emitContribution`) and folds every op on a link into the final
3173
+ * `link.confidence` before the `detect` phase. `link` must be one of
3174
+ * `ctx.links` (matched by object identity). Present ONLY in the
3175
+ * `score` phase (absent for `detect` / `aggregate` and legacy
3176
+ * callers), mirroring the other orchestrator-injected ctx fields.
3177
+ */
3178
+ adjustConfidence?(link: Link, op: TConfidenceOp): void;
3098
3179
  }
3099
3180
  interface IAnalyzer extends IExtensionBase {
3100
3181
  /** Discriminant injected by the loader from the folder structure. */
@@ -3121,22 +3202,31 @@ interface IAnalyzer extends IExtensionBase {
3121
3202
  * Execution phase. Drives the order the orchestrator schedules
3122
3203
  * analyzers in:
3123
3204
  *
3205
+ * - `'score'`, runs strictly BEFORE every `detect`-phase analyzer.
3206
+ * The ONLY phase permitted to write: it adjusts link confidence
3207
+ * via `ctx.adjustConfidence(link, op)`. The orchestrator folds
3208
+ * every score-phase op into `link.confidence` before the read-
3209
+ * only `detect` phase runs, so the `detect` analyzers see the
3210
+ * final value. The kernel seeds the 1.0 confidence baseline on
3211
+ * every link, then dogfoods this phase via two built-in score-phase
3212
+ * detectors (`core/name-reserved`, `core/reference-broken`), each
3213
+ * co-locating its penalty `delta` with the finding it reports.
3124
3214
  * - `'detect'` (default), the main pass. Walks nodes / links and
3125
- * emits its own findings. Most analyzers live here.
3215
+ * emits its own findings. Most analyzers live here. Read-only.
3126
3216
  * - `'aggregate'`, runs strictly AFTER every `detect`-phase
3127
3217
  * analyzer has finished. The orchestrator passes the full
3128
3218
  * issue accumulator on `ctx.accumulatedIssues`, so an
3129
3219
  * aggregator can compute cross-analyzer summaries (per-node
3130
3220
  * severity totals, etc.) without re-reading the persisted DB.
3131
3221
  * Aggregators emit contributions; emitting issues is allowed
3132
- * but uncommon.
3222
+ * but uncommon. Read-only.
3133
3223
  *
3134
- * Two-phase scheduling is the clean alternative to ordering
3135
- * analyzers by hand in the built-ins registry: filesystem-sorted
3136
- * generators can keep their alphabetical output, the orchestrator
3137
- * applies the phase sort at run-time.
3224
+ * Phase scheduling is the clean alternative to ordering analyzers by
3225
+ * hand in the built-ins registry: filesystem-sorted generators can
3226
+ * keep their alphabetical output, the orchestrator applies the phase
3227
+ * sort (`score` < `detect` < `aggregate`) at run-time.
3138
3228
  */
3139
- phase?: 'detect' | 'aggregate';
3229
+ phase?: 'score' | 'detect' | 'aggregate';
3140
3230
  evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
3141
3231
  }
3142
3232
 
@@ -3921,21 +4011,6 @@ interface RunScanOptions {
3921
4011
  * mock without spinning up a DB.
3922
4012
  */
3923
4013
  pluginStores?: ReadonlyMap<string, TPluginStore>;
3924
- /**
3925
- * Pre-computed absolute paths of orphan job MD files (files under
3926
- * `.skill-map/jobs/` whose absolute path appears nowhere in
3927
- * `state_jobs.filePath`). Threaded into the rule pass so the
3928
- * built-in `core/job-file-orphan` rule can project each as a `warn`
3929
- * issue without the kernel reaching for the storage port or doing
3930
- * its own FS walk. The driving adapter (CLI, BFF) computes this
3931
- * inside its already-open storage transaction via
3932
- * `findOrphanJobFiles(jobsDir, await port.jobs.listReferencedFilePaths())`
3933
- * mirrors the `orphanSidecars` model where detection lives
3934
- * outside the rule and the rule only projects. Absent / empty when
3935
- * the caller has no jobs context (out-of-band tests, fresh DB,
3936
- * `--no-built-ins`).
3937
- */
3938
- orphanJobFiles?: readonly string[];
3939
4014
  /**
3940
4015
  * Side set of absolute file paths the operator opted into for
3941
4016
  * link-validation purposes via `scan.referencePaths`. Threaded
@@ -4019,6 +4094,7 @@ declare function runScanWithRenames(_kernel: Kernel, options: RunScanOptions): P
4019
4094
  enrichments: IEnrichmentRecord[];
4020
4095
  contributions: IContributionRecord[];
4021
4096
  contributionErrors: IContributionErrorRecord[];
4097
+ linkScores: IConfidenceAdjustment[];
4022
4098
  freshlyRunTuples: ReadonlySet<string>;
4023
4099
  }>;
4024
4100
  declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<ScanResult>;
@@ -4217,7 +4293,7 @@ declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatche
4217
4293
  * a reason narrowing what diverged.
4218
4294
  *
4219
4295
  * - **Link**: `(source, target, kind, normalizedTrigger ?? '')`. This
4220
- * mirrors the link-conflict rule and `sm show` aggregation,
4296
+ * mirrors the link-kind-conflict rule and `sm show` aggregation,
4221
4297
  * two links with identical endpoints, kind, and (optional) trigger
4222
4298
  * are the same link, even if emitted by different extractors. The
4223
4299
  * `sources[]` union and confidence are NOT part of identity; they