@skill-map/cli 0.55.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.
@@ -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]="912d41a1-01f9-59a0-81d9-5e20e8ed3de6")}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]="12d40092-5030-5aa9-ba9b-939af94ed740")}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.55.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",
@@ -745,6 +745,10 @@ function matchesFilter(hook, event) {
745
745
  function buildHookContext(_hook, trigger, event) {
746
746
  const data = event.data ?? {};
747
747
  const ctx = {
748
+ // `settings` is always populated (possibly empty) so hooks can read
749
+ // `ctx.settings.<id>` without a presence check. The composer
750
+ // populated `resolvedSettings` on each composed hook.
751
+ settings: _hook.resolvedSettings ?? {},
748
752
  event: {
749
753
  type: trigger,
750
754
  timestamp: event.timestamp,
@@ -1265,8 +1269,49 @@ function runActionProjections(actions, nodes, links, emitter) {
1265
1269
  return { contributions, contributionErrors };
1266
1270
  }
1267
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
+
1268
1313
  // kernel/orchestrator/analyzers.ts
1269
- async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals, seedIssues = []) {
1314
+ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, brokenLinks, nameCollisions, signals, seedIssues = []) {
1270
1315
  const issues = [...seedIssues];
1271
1316
  const contributions = [];
1272
1317
  const contributionErrors = [];
@@ -1277,7 +1322,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1277
1322
  expectedMdPath: o.expectedMdPath
1278
1323
  }));
1279
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
+ };
1280
1333
  for (const analyzer of scheduled) {
1334
+ if (analyzer.phase !== "score") foldScores();
1281
1335
  const qualifiedId = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
1282
1336
  const declaredContributions = readDeclaredContributionRefs(analyzer);
1283
1337
  const emitContribution = (nodePath, ref, payload) => {
@@ -1340,14 +1394,27 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1340
1394
  emittedAt: Date.now()
1341
1395
  });
1342
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;
1343
1407
  const emitted = await analyzer.evaluate({
1344
1408
  nodes,
1345
1409
  links: internalLinks,
1410
+ // `settings` is always populated (possibly empty) so analyzers can
1411
+ // read `ctx.settings.<id>` without a presence check. The composer
1412
+ // populated `resolvedSettings` on each composed analyzer.
1413
+ settings: analyzer.resolvedSettings ?? {},
1346
1414
  orphanSidecars: analyzerOrphans,
1347
1415
  sidecarRoots,
1348
1416
  annotationContributions,
1349
1417
  viewContributions,
1350
- orphanJobFiles,
1351
1418
  // `issues` is the live accumulator, mutated by `issues.push(...)`
1352
1419
  // below as each analyzer's emission lands. Late-phase analyzers
1353
1420
  // (`core/issue-counter`) read it to compute cross-analyzer
@@ -1356,7 +1423,10 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1356
1423
  ...referenceablePaths ? { referenceablePaths } : {},
1357
1424
  ...cwd ? { cwd } : {},
1358
1425
  ...reservedNodePaths ? { reservedNodePaths } : {},
1426
+ ...brokenLinks ? { brokenLinks } : {},
1427
+ ...nameCollisions && nameCollisions.size > 0 ? { nameCollisions } : {},
1359
1428
  ...signals && signals.length > 0 ? { signals } : {},
1429
+ ...adjustConfidence ? { adjustConfidence } : {},
1360
1430
  emitContribution
1361
1431
  });
1362
1432
  for (const issue of emitted) {
@@ -1367,13 +1437,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
1367
1437
  emitter.emit(evt);
1368
1438
  await hookDispatcher.dispatch("analyzer.completed", evt);
1369
1439
  }
1370
- return { issues, contributions, contributionErrors };
1440
+ foldScores();
1441
+ return { issues, contributions, contributionErrors, linkScores: scoreAdjustments };
1371
1442
  }
1372
1443
  function orderAnalyzersByPhase(analyzers) {
1373
1444
  return analyzers.slice().sort((a, b) => phaseRank(a) - phaseRank(b));
1374
1445
  }
1375
1446
  function phaseRank(a) {
1376
- return a.phase === "aggregate" ? 1 : 0;
1447
+ if (a.phase === "score") return 0;
1448
+ if (a.phase === "aggregate") return 2;
1449
+ return 1;
1377
1450
  }
1378
1451
  function validateIssue(analyzer, issue, emitter) {
1379
1452
  const severity = issue.severity;
@@ -1396,6 +1469,13 @@ function validateIssue(analyzer, issue, emitter) {
1396
1469
  return { ...issue, analyzerId: issue.analyzerId || analyzer.id };
1397
1470
  }
1398
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
+
1399
1479
  // kernel/orchestrator/cache.ts
1400
1480
  function indexPriorSnapshot(prior) {
1401
1481
  const priorNodesByPath = /* @__PURE__ */ new Map();
@@ -1424,17 +1504,6 @@ function indexPriorLinks(links, priorNodePaths, byOriginating) {
1424
1504
  else byOriginating.set(key, [link]);
1425
1505
  }
1426
1506
  }
1427
- var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
1428
- "frontmatter-invalid",
1429
- "frontmatter-malformed",
1430
- // Audit L1: parser parse-error is emitted by
1431
- // `buildFreshNodeAndValidateFrontmatter` from `raw.parseIssues`. The
1432
- // raw.parseIssues only flows through the non-cache path; a cached
1433
- // node skips the rebuild, so the prior issue MUST survive the
1434
- // incremental scan or the warning silently disappears on a clean
1435
- // re-scan of an unchanged file.
1436
- "frontmatter-parse-error"
1437
- ]);
1438
1507
  function indexPriorFrontmatterIssues(issues, byNode) {
1439
1508
  for (const issue of issues) {
1440
1509
  if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
@@ -1632,28 +1701,62 @@ function readDirname(node) {
1632
1701
  const base = pathPosix.basename(dir);
1633
1702
  return base.length > 0 ? base : null;
1634
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
+ }
1635
1737
 
1636
1738
  // kernel/orchestrator/lift-resolved-link-confidence.ts
1637
- var RESERVED_TARGET_CONFIDENCE = 0.1;
1638
- var BROKEN_TARGET_CONFIDENCE = 0.5;
1639
1739
  function liftResolvedLinkConfidence(links, nodes, ctx) {
1640
- if (!links.some((l) => l.confidence < 1)) return;
1740
+ if (links.length === 0) return;
1641
1741
  const indexes = buildIndexes(nodes, ctx);
1642
1742
  for (const link of links) {
1643
- if (link.confidence < 1) applyResolution(link, indexes, ctx);
1743
+ link.confidence = 1;
1744
+ applyResolution(link, indexes, ctx);
1644
1745
  }
1645
1746
  }
1747
+ function collectBrokenLinks(links, nodes, ctx) {
1748
+ const broken = /* @__PURE__ */ new Set();
1749
+ if (links.length === 0) return broken;
1750
+ const indexes = buildIndexes(nodes, ctx);
1751
+ for (const link of links) {
1752
+ if (isGenuinelyBroken(link, indexes)) broken.add(link);
1753
+ }
1754
+ return broken;
1755
+ }
1646
1756
  function applyResolution(link, indexes, ctx) {
1647
1757
  const resolution = resolve5(link, indexes, ctx);
1648
- if (resolution === "none") {
1649
- if (isGenuinelyBroken(link, indexes)) {
1650
- link.confidence = Math.min(link.confidence, BROKEN_TARGET_CONFIDENCE);
1651
- }
1652
- return;
1653
- }
1758
+ if (resolution === "none") return;
1654
1759
  link.resolvedTarget = resolution;
1655
- if (indexes.nodeByPath.get(resolution)?.virtual) return;
1656
- link.confidence = ctx.reservedNodePaths.has(resolution) ? RESERVED_TARGET_CONFIDENCE : 1;
1657
1760
  }
1658
1761
  function buildIndexes(nodes, ctx) {
1659
1762
  const byPath2 = /* @__PURE__ */ new Set();
@@ -3124,6 +3227,8 @@ async function runScanInternal(_kernel, options) {
3124
3227
  walked.signals = resolved.resolvedSignals;
3125
3228
  const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes, activeProviderId);
3126
3229
  walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
3230
+ const brokenLinks = collectBrokenLinks(walked.internalLinks, walked.nodes, postWalkCtx);
3231
+ const nameCollisions = collectNameCollisions(walked.nodes, postWalkCtx.kindRegistry);
3127
3232
  recomputeLinkCounts(walked.nodes, walked.internalLinks);
3128
3233
  recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
3129
3234
  await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
@@ -3138,13 +3243,14 @@ async function runScanInternal(_kernel, options) {
3138
3243
  walked.sidecarRoots,
3139
3244
  options.annotationContributions ?? [],
3140
3245
  options.viewContributions ?? [],
3141
- options.orphanJobFiles ?? [],
3142
3246
  options.referenceablePaths,
3143
3247
  options.cwd,
3144
3248
  registeredActionIds,
3145
3249
  emitter,
3146
3250
  hookDispatcher,
3147
3251
  postWalkCtx.reservedNodePaths,
3252
+ brokenLinks,
3253
+ nameCollisions,
3148
3254
  walked.signals,
3149
3255
  // Seed the accumulator with orchestrator-emitted frontmatter
3150
3256
  // issues so the aggregate phase (`core/issue-counter`) counts
@@ -3167,7 +3273,7 @@ async function runScanInternal(_kernel, options) {
3167
3273
  const scanCompletedEvent = makeEvent("scan.completed", { stats });
3168
3274
  emitter.emit(scanCompletedEvent);
3169
3275
  await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
3170
- return buildScanReturn(walked, issues, renameOps, stats, options, setup);
3276
+ return buildScanReturn(walked, issues, renameOps, stats, options, setup, analyzerResult.linkScores);
3171
3277
  }
3172
3278
  function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
3173
3279
  const { kindRegistry, providerResolution, reservedNamesByProviderKind } = buildProviderIndexes(providers);
@@ -3295,7 +3401,7 @@ function buildScanStats(walked, issues, start) {
3295
3401
  durationMs: Date.now() - start
3296
3402
  };
3297
3403
  }
3298
- function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
3404
+ function buildScanReturn(walked, issues, renameOps, stats, options, setup, linkScores) {
3299
3405
  return {
3300
3406
  result: {
3301
3407
  schemaVersion: 1,
@@ -3317,6 +3423,7 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
3317
3423
  enrichments: walked.enrichments,
3318
3424
  contributions: walked.contributions,
3319
3425
  contributionErrors: walked.contributionErrors,
3426
+ linkScores,
3320
3427
  freshlyRunTuples: walked.freshlyRunTuples
3321
3428
  };
3322
3429
  }
@@ -3746,4 +3853,4 @@ export {
3746
3853
  runScanWithRenames
3747
3854
  };
3748
3855
  //# sourceMappingURL=index.js.map
3749
- //# debugId=912d41a1-01f9-59a0-81d9-5e20e8ed3de6
3856
+ //# debugId=12d40092-5030-5aa9-ba9b-939af94ed740
@@ -472,6 +472,51 @@ CREATE TABLE scan_contribution_errors (
472
472
  CREATE INDEX ix_scan_contribution_errors_plugin_id ON scan_contribution_errors(plugin_id);
473
473
  CREATE INDEX ix_scan_contribution_errors_node_path ON scan_contribution_errors(node_path);
474
474
 
475
+ -- scan_link_scores: per-op confidence-attribution audit trail. One row
476
+ -- per attributed `ctx.adjustConfidence(link, op)` call buffered by a
477
+ -- `score`-phase analyzer during the scan (the kernel's own built-in
478
+ -- `core/name-reserved` / `core/reference-broken` detectors dogfood the
479
+ -- API, applying penalty deltas on top of the kernel's 1.0 baseline). Lets
480
+ -- an operator answer "why is this link at 0.3?" by listing the plugin /
481
+ -- extension / op that moved it, with the FOLDED final value denormalised
482
+ -- onto every row (`result_confidence` mirrors `scan_links.confidence`).
483
+ --
484
+ -- The link is identified by its structural identity fields
485
+ -- (`source_path`, `target`, `kind`, `normalized_trigger`), the same key
486
+ -- `scan_links` dedups on; `normalized_trigger` is NULL for path-style
487
+ -- links that carry no trigger. `op_kind` is the algebra bucket
488
+ -- (`set` / `delta` / `ceil` / `floor`) and `op_value` its operand.
489
+ --
490
+ -- Belongs to the `scan_*` family. Plain REPLACE-ALL per scan (delete
491
+ -- all, then insert), the same posture as `scan_issues` and
492
+ -- `scan_contribution_errors` — NOT the sweep model `scan_contributions`
493
+ -- uses. A score adjustment is a transient scan finding re-derived in
494
+ -- full on every analyzer pass, so there is no cached-node row to
495
+ -- preserve and no compound PK (multiple ops can land on one link). Index
496
+ -- on `source_path` for the per-node "why this link?" lookup + the rename
497
+ -- heuristic-adjacent reads.
498
+ CREATE TABLE scan_link_scores (
499
+ plugin_id TEXT NOT NULL,
500
+ extension_id TEXT NOT NULL,
501
+ source_path TEXT NOT NULL,
502
+ target TEXT NOT NULL,
503
+ kind TEXT NOT NULL,
504
+ -- NULL for path-style links that carry no trigger (the structural
505
+ -- identity key mirrors `scan_links`'s dedup tuple).
506
+ normalized_trigger TEXT,
507
+ -- Confidence-algebra bucket: `set` / `delta` / `ceil` / `floor`. Kept
508
+ -- open at the SQL layer (no CHECK) so the op catalog can evolve as a
509
+ -- kernel + spec change without a DDL migration.
510
+ op_kind TEXT NOT NULL,
511
+ op_value REAL NOT NULL,
512
+ -- Denormalised FOLDED final `link.confidence` after every op for this
513
+ -- link was applied. Equal across all rows for one link; carried per
514
+ -- row so the audit read needs no join back to `scan_links`.
515
+ result_confidence REAL NOT NULL,
516
+ emitted_at INTEGER NOT NULL
517
+ );
518
+ CREATE INDEX ix_scan_link_scores_source_path ON scan_link_scores(source_path);
519
+
475
520
  -- scan_node_tags: tag system. One row per (node_path, tag) pair.
476
521
  -- Projected at persist time from `sidecar.annotations.tags`. Tags are
477
522
  -- a skill-map concept (no vendor carries `tags` in frontmatter), so the