@skill-map/cli 0.33.0 → 0.34.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.
package/dist/index.js CHANGED
@@ -93,14 +93,15 @@ var Registry = class {
93
93
  };
94
94
 
95
95
  // kernel/orchestrator/index.ts
96
- import { existsSync as existsSync10, statSync as statSync4 } from "fs";
96
+ import { existsSync as existsSync11, statSync as statSync4 } from "fs";
97
+ import { isAbsolute as isAbsolute4, resolve as resolve10 } from "path";
97
98
  import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
98
99
  import cl100k_base from "js-tiktoken/ranks/cl100k_base";
99
100
 
100
101
  // package.json
101
102
  var package_default = {
102
103
  name: "@skill-map/cli",
103
- version: "0.33.0",
104
+ version: "0.34.1",
104
105
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
105
106
  license: "MIT",
106
107
  type: "module",
@@ -693,6 +694,391 @@ var ORCHESTRATOR_TEXTS = {
693
694
  runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
694
695
  };
695
696
 
697
+ // core/config/active-provider.ts
698
+ import { existsSync as existsSync5 } from "fs";
699
+ import { join as join6 } from "path";
700
+
701
+ // core/config/helper.ts
702
+ import { homedir as osHomedir } from "os";
703
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve6 } from "path";
704
+
705
+ // kernel/config/loader.ts
706
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
707
+
708
+ // kernel/i18n/config-loader.texts.ts
709
+ var CONFIG_LOADER_TEXTS = {
710
+ readFailure: "[config:{{layer}}] failed to read {{path}}: {{message}}",
711
+ invalidJson: "[config:{{layer}}] invalid JSON in {{path}}: {{message}}",
712
+ expectedObject: "[config:{{layer}}] expected a JSON object, got {{type}}; ignored",
713
+ unknownKey: "[config:{{layer}}] unknown key {{key}} ignored",
714
+ invalidValue: "[config:{{layer}}] invalid value at {{path}}: {{message}}",
715
+ projectLocalOnlyStripped: "[config:{{layer}}] key {{key}} is project-local only; stripped from the committed project layer. Move it to .skill-map/settings.local.json (gitignored, per-checkout)."
716
+ };
717
+
718
+ // kernel/util/skill-map-paths.ts
719
+ import { join as join4 } from "path";
720
+
721
+ // core/paths/db-path.ts
722
+ import { join as join3, resolve as resolve5 } from "path";
723
+ var SKILL_MAP_DIR = ".skill-map";
724
+ var DB_FILENAME = "skill-map.db";
725
+ var LOCAL_SETTINGS_FILENAME = "settings.local.json";
726
+ var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
727
+ var GITIGNORE_ENTRIES = [
728
+ `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
729
+ `${SKILL_MAP_DIR}/${DB_FILENAME}`
730
+ ];
731
+
732
+ // kernel/util/skill-map-paths.ts
733
+ var KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;
734
+ var SETTINGS_FILENAME = "settings.json";
735
+ var LOCAL_SETTINGS_FILENAME2 = "settings.local.json";
736
+ function kernelSettingsPath(scopeRoot) {
737
+ return join4(scopeRoot, KERNEL_SKILL_MAP_DIR, SETTINGS_FILENAME);
738
+ }
739
+ function kernelLocalSettingsPath(scopeRoot) {
740
+ return join4(scopeRoot, KERNEL_SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME2);
741
+ }
742
+
743
+ // kernel/util/strip-prototype-pollution.ts
744
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
745
+ "__proto__",
746
+ "constructor",
747
+ "prototype"
748
+ ]);
749
+ function stripPrototypePollution(value) {
750
+ return strip(value);
751
+ }
752
+ function strip(value) {
753
+ if (value === null || value === void 0) return value;
754
+ if (typeof value !== "object") return value;
755
+ if (Array.isArray(value)) return value.map(strip);
756
+ const out = {};
757
+ for (const [k, v] of Object.entries(value)) {
758
+ if (FORBIDDEN_KEYS.has(k)) continue;
759
+ out[k] = strip(v);
760
+ }
761
+ return out;
762
+ }
763
+
764
+ // config/defaults.json
765
+ var defaults_default = {
766
+ schemaVersion: 1,
767
+ autoMigrate: true,
768
+ allowEditSmFiles: false,
769
+ tokenizer: "cl100k_base",
770
+ providers: [],
771
+ roots: [],
772
+ ignore: [],
773
+ scan: {
774
+ tokenize: true,
775
+ strict: false,
776
+ followSymlinks: false,
777
+ maxFileSizeBytes: 1048576,
778
+ watch: {
779
+ debounceMs: 300
780
+ },
781
+ referencePaths: []
782
+ },
783
+ plugins: {},
784
+ history: {
785
+ share: false
786
+ },
787
+ jobs: {
788
+ ttlSeconds: 3600,
789
+ graceMultiplier: 3,
790
+ minimumTtlSeconds: 60,
791
+ perActionTtl: {},
792
+ perActionPriority: {},
793
+ retention: {
794
+ completed: 2592e3,
795
+ failed: null
796
+ }
797
+ },
798
+ i18n: {
799
+ locale: "en"
800
+ }
801
+ };
802
+
803
+ // kernel/config/loader.ts
804
+ var PROJECT_LOCAL_ONLY_KEYS = /* @__PURE__ */ new Set([
805
+ "allowEditSmFiles",
806
+ "scan.referencePaths"
807
+ ]);
808
+ var DEFAULTS = defaults_default;
809
+ function loadConfig(opts) {
810
+ const cwd = opts.cwd;
811
+ const strict = opts.strict ?? false;
812
+ const warnings = [];
813
+ const sources = /* @__PURE__ */ new Map();
814
+ const validators = loadSchemaValidators();
815
+ let effective = structuredClone(DEFAULTS);
816
+ recordSources("", effective, sources, "defaults");
817
+ const filePairs = [
818
+ { path: kernelSettingsPath(cwd), layer: "project" },
819
+ { path: kernelLocalSettingsPath(cwd), layer: "project-local" }
820
+ ];
821
+ for (const { path, layer } of filePairs) {
822
+ if (!existsSync3(path)) continue;
823
+ const partial = readJsonSafe(path, layer, warnings, strict);
824
+ if (partial === null) continue;
825
+ const cleaned = validateAndStrip(validators, partial, layer, warnings, strict);
826
+ if (layer !== "project-local") {
827
+ stripProjectLocalOnlyKeys(cleaned, layer, warnings, strict);
828
+ }
829
+ effective = deepMerge(effective, cleaned);
830
+ recordSources("", cleaned, sources, layer);
831
+ }
832
+ if (opts.overrides && Object.keys(opts.overrides).length > 0) {
833
+ const cleaned = validateAndStrip(validators, opts.overrides, "override", warnings, strict);
834
+ stripProjectLocalOnlyKeys(cleaned, "override", warnings, strict);
835
+ effective = deepMerge(effective, cleaned);
836
+ recordSources("", cleaned, sources, "override");
837
+ }
838
+ return { effective, sources, warnings };
839
+ }
840
+ function readJsonSafe(path, layer, warnings, strict) {
841
+ let text;
842
+ try {
843
+ text = readFileSync5(path, "utf8");
844
+ } catch (err) {
845
+ return reportAndSkip(
846
+ tx(CONFIG_LOADER_TEXTS.readFailure, { layer, path, message: formatErrorMessage(err) }),
847
+ warnings,
848
+ strict
849
+ );
850
+ }
851
+ try {
852
+ return JSON.parse(text);
853
+ } catch (err) {
854
+ return reportAndSkip(
855
+ tx(CONFIG_LOADER_TEXTS.invalidJson, { layer, path, message: formatErrorMessage(err) }),
856
+ warnings,
857
+ strict
858
+ );
859
+ }
860
+ }
861
+ function reportAndSkip(msg, warnings, strict) {
862
+ if (strict) throw new Error(msg);
863
+ warnings.push(msg);
864
+ return null;
865
+ }
866
+ function validateAndStrip(validators, raw, layer, warnings, strict) {
867
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
868
+ const msg = tx(CONFIG_LOADER_TEXTS.expectedObject, { layer, type: describeJsonType(raw) });
869
+ if (strict) throw new Error(msg);
870
+ warnings.push(msg);
871
+ return {};
872
+ }
873
+ const cloned = structuredClone(raw);
874
+ const validator = validators.getValidator("project-config");
875
+ if (validator(cloned)) return cloned;
876
+ for (const err of validator.errors ?? []) {
877
+ applyValidationError(cloned, err, layer, warnings, strict);
878
+ }
879
+ return cloned;
880
+ }
881
+ function applyValidationError(cloned, err, layer, warnings, strict) {
882
+ const path = err.instancePath ?? "";
883
+ if (err.keyword === "additionalProperties") {
884
+ const extra = err.params.additionalProperty;
885
+ deleteAtPath(cloned, path, extra);
886
+ const msg2 = tx(CONFIG_LOADER_TEXTS.unknownKey, { layer, key: joinSegments(path, extra) });
887
+ if (strict) throw new Error(msg2);
888
+ warnings.push(msg2);
889
+ return;
890
+ }
891
+ const segments = path.split("/").filter(Boolean);
892
+ if (segments.length > 0) {
893
+ const last = segments.pop();
894
+ deleteAtPath(cloned, "/" + segments.join("/"), last);
895
+ }
896
+ const msg = tx(CONFIG_LOADER_TEXTS.invalidValue, {
897
+ layer,
898
+ path: path || "(root)",
899
+ message: err.message ?? err.keyword
900
+ });
901
+ if (strict) throw new Error(msg);
902
+ warnings.push(msg);
903
+ }
904
+ function describeJsonType(v) {
905
+ if (v === null) return "null";
906
+ if (Array.isArray(v)) return "array";
907
+ return typeof v;
908
+ }
909
+ function deleteAtPath(root, parentPath, key) {
910
+ if (containsForbidden(parentPath, key)) return;
911
+ const segments = parentPath.split("/").filter(Boolean);
912
+ let cur = root;
913
+ for (const seg of segments) {
914
+ if (!isPlainObject(cur)) return;
915
+ cur = cur[seg];
916
+ }
917
+ if (isPlainObject(cur)) delete cur[key];
918
+ }
919
+ function stripProjectLocalOnlyKeys(cloned, layer, warnings, strict) {
920
+ for (const dotKey of PROJECT_LOCAL_ONLY_KEYS) {
921
+ const segments = dotKey.split(".").filter(Boolean);
922
+ if (segments.length === 0) continue;
923
+ const leaf = segments.pop();
924
+ if (!keyPresentAtPath(cloned, segments, leaf)) continue;
925
+ const parentPath = "/" + segments.join("/");
926
+ deleteAtPath(cloned, parentPath, leaf);
927
+ const msg = tx(CONFIG_LOADER_TEXTS.projectLocalOnlyStripped, {
928
+ layer,
929
+ key: dotKey
930
+ });
931
+ if (strict) throw new Error(msg);
932
+ warnings.push(msg);
933
+ }
934
+ }
935
+ function keyPresentAtPath(root, parentSegments, leaf) {
936
+ let cur = root;
937
+ for (const seg of parentSegments) {
938
+ if (!isPlainObject(cur)) return false;
939
+ cur = cur[seg];
940
+ }
941
+ return isPlainObject(cur) && Object.prototype.hasOwnProperty.call(cur, leaf);
942
+ }
943
+ function isPlainObject(v) {
944
+ return v !== null && typeof v === "object" && !Array.isArray(v);
945
+ }
946
+ function containsForbidden(parentPath, leaf) {
947
+ if (FORBIDDEN_KEYS.has(leaf)) return true;
948
+ for (const seg of parentPath.split("/")) {
949
+ if (FORBIDDEN_KEYS.has(seg)) return true;
950
+ }
951
+ return false;
952
+ }
953
+ function joinSegments(instancePath, leaf) {
954
+ const segments = instancePath.split("/").filter(Boolean);
955
+ return [...segments, leaf].join(".");
956
+ }
957
+ function deepMerge(target, source) {
958
+ const out = { ...target };
959
+ for (const [k, v] of Object.entries(source)) {
960
+ if (FORBIDDEN_KEYS.has(k)) continue;
961
+ out[k] = mergeValue(out[k], v);
962
+ }
963
+ return out;
964
+ }
965
+ function mergeValue(target, source) {
966
+ if (source === null || typeof source !== "object" || Array.isArray(source)) {
967
+ return source;
968
+ }
969
+ const targetSlot = target !== null && typeof target === "object" && !Array.isArray(target) ? target : {};
970
+ return deepMerge(targetSlot, source);
971
+ }
972
+ function recordSources(prefix, value, map, layer) {
973
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
974
+ if (prefix) map.set(prefix, layer);
975
+ return;
976
+ }
977
+ const entries = Object.entries(value);
978
+ if (entries.length === 0 && prefix) {
979
+ map.set(prefix, layer);
980
+ return;
981
+ }
982
+ for (const [k, v] of entries) {
983
+ const next = prefix ? `${prefix}.${k}` : k;
984
+ recordSources(next, v, map, layer);
985
+ }
986
+ }
987
+
988
+ // core/config/dot-path.ts
989
+ var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
990
+ "__proto__",
991
+ "constructor",
992
+ "prototype"
993
+ ]);
994
+ var ForbiddenSegmentError = class extends Error {
995
+ constructor(segment, key) {
996
+ super(`forbidden config key segment "${segment}" in "${key}"`);
997
+ this.segment = segment;
998
+ this.key = key;
999
+ }
1000
+ segment;
1001
+ key;
1002
+ };
1003
+ function assertSafeSegments(segments, key) {
1004
+ for (const seg of segments) {
1005
+ if (FORBIDDEN_SEGMENTS.has(seg)) throw new ForbiddenSegmentError(seg, key);
1006
+ }
1007
+ }
1008
+ function getAtPath(obj, dotPath) {
1009
+ const segments = dotPath.split(".").filter(Boolean);
1010
+ assertSafeSegments(segments, dotPath);
1011
+ let cur = obj;
1012
+ for (const seg of segments) {
1013
+ if (cur && typeof cur === "object" && !Array.isArray(cur)) {
1014
+ cur = cur[seg];
1015
+ continue;
1016
+ }
1017
+ return void 0;
1018
+ }
1019
+ return cur;
1020
+ }
1021
+
1022
+ // core/config/atomic-write.ts
1023
+ import {
1024
+ closeSync,
1025
+ constants as fsConstants,
1026
+ existsSync as existsSync4,
1027
+ mkdirSync,
1028
+ openSync,
1029
+ readFileSync as readFileSync6,
1030
+ renameSync,
1031
+ unlinkSync,
1032
+ writeSync
1033
+ } from "fs";
1034
+ import { randomBytes } from "crypto";
1035
+ import { dirname as dirname3 } from "path";
1036
+
1037
+ // core/config/helper.ts
1038
+ function readConfigValue(key, opts) {
1039
+ const loaded = loadConfigForScope(opts);
1040
+ const value = getAtPath(loaded.effective, key);
1041
+ if (value === void 0) return opts.default;
1042
+ return value;
1043
+ }
1044
+ function loadConfigForScope(opts) {
1045
+ return loadConfig({
1046
+ cwd: opts.cwd,
1047
+ ...opts.strict ? { strict: true } : {}
1048
+ });
1049
+ }
1050
+
1051
+ // core/config/active-provider.ts
1052
+ var DETECTION_RULES = [
1053
+ { providerId: "claude", marker: ".claude" },
1054
+ { providerId: "gemini", marker: ".gemini" },
1055
+ { providerId: "openai", marker: ".codex" },
1056
+ { providerId: "openai", marker: "AGENTS.md" },
1057
+ { providerId: "cursor", marker: ".cursor" }
1058
+ ];
1059
+ function resolveActiveProvider(cwd) {
1060
+ const detected = detectProvidersFromFilesystem(cwd);
1061
+ const fromConfig = readConfigValue("activeProvider", { cwd });
1062
+ if (typeof fromConfig === "string" && fromConfig.length > 0) {
1063
+ return { resolved: fromConfig, source: "config", detected };
1064
+ }
1065
+ if (detected.length > 0) {
1066
+ return { resolved: detected[0], source: "autodetect", detected };
1067
+ }
1068
+ return { resolved: null, source: "none", detected };
1069
+ }
1070
+ function detectProvidersFromFilesystem(cwd) {
1071
+ const seen = /* @__PURE__ */ new Set();
1072
+ const out = [];
1073
+ for (const rule of DETECTION_RULES) {
1074
+ if (seen.has(rule.providerId)) continue;
1075
+ if (!existsSync5(join6(cwd, rule.marker))) continue;
1076
+ seen.add(rule.providerId);
1077
+ out.push(rule.providerId);
1078
+ }
1079
+ return out;
1080
+ }
1081
+
696
1082
  // kernel/types.ts
697
1083
  var ConfidenceTier = Object.freeze({
698
1084
  HIGH: 0.9,
@@ -1001,6 +1387,9 @@ function dedupeLinks(links) {
1001
1387
  existing.sources = [...existing.sources, src];
1002
1388
  }
1003
1389
  }
1390
+ if (link.confidence > existing.confidence) {
1391
+ existing.confidence = link.confidence;
1392
+ }
1004
1393
  continue;
1005
1394
  }
1006
1395
  out.set(key, link);
@@ -1195,7 +1584,7 @@ function originatingNodeOf(link, priorNodePaths) {
1195
1584
  function computeCacheDecision(opts) {
1196
1585
  const applicableExtractors = opts.extractors.filter((ex) => {
1197
1586
  if (!matchesKindPrecondition(ex, opts.kind)) return false;
1198
- if (!matchesProviderPrecondition(ex, opts.provider)) return false;
1587
+ if (!matchesProviderPrecondition(ex, opts.activeProvider)) return false;
1199
1588
  return true;
1200
1589
  });
1201
1590
  const applicableQualifiedIds = new Set(
@@ -1219,10 +1608,11 @@ function matchesKindPrecondition(ex, kind) {
1219
1608
  return kindOnly === kind;
1220
1609
  });
1221
1610
  }
1222
- function matchesProviderPrecondition(ex, provider) {
1611
+ function matchesProviderPrecondition(ex, activeProvider) {
1223
1612
  const providers = ex.precondition?.provider;
1224
1613
  if (!providers || providers.length === 0) return true;
1225
- return providers.includes(provider);
1614
+ if (activeProvider === null) return false;
1615
+ return providers.includes(activeProvider);
1226
1616
  }
1227
1617
  function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
1228
1618
  const cachedQualifiedIds = /* @__PURE__ */ new Set();
@@ -1323,6 +1713,75 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
1323
1713
  return "obsolete";
1324
1714
  }
1325
1715
 
1716
+ // kernel/trigger-normalize.ts
1717
+ function normalizeTrigger(source) {
1718
+ let out = source.normalize("NFD");
1719
+ out = out.replace(new RegExp("\\p{Mn}+", "gu"), "");
1720
+ out = out.toLowerCase();
1721
+ out = out.replace(/[-_\s]+/g, " ");
1722
+ out = out.replace(/ +/g, " ");
1723
+ return out.trim();
1724
+ }
1725
+
1726
+ // kernel/orchestrator/lift-mention-confidence.ts
1727
+ function liftMentionConfidence(links, nodes) {
1728
+ if (!links.some((l) => l.kind === "mentions")) return;
1729
+ const byPath2 = /* @__PURE__ */ new Set();
1730
+ for (const node of nodes) byPath2.add(node.path);
1731
+ const byNormalizedName = indexByNormalizedName(nodes);
1732
+ for (const link of links) {
1733
+ if (link.kind !== "mentions") continue;
1734
+ if (isResolved(link, byPath2, byNormalizedName)) {
1735
+ link.confidence = 1;
1736
+ }
1737
+ }
1738
+ }
1739
+ function isResolved(link, byPath2, byNormalizedName) {
1740
+ const normalized = link.trigger?.normalizedTrigger;
1741
+ if (normalized) {
1742
+ const withoutSigil = normalized.replace(/^[/@]/, "").trim();
1743
+ if (byNormalizedName.has(withoutSigil)) return true;
1744
+ }
1745
+ if (byPath2.has(link.target)) return true;
1746
+ return false;
1747
+ }
1748
+ function indexByNormalizedName(nodes) {
1749
+ const out = /* @__PURE__ */ new Map();
1750
+ for (const node of nodes) {
1751
+ const raw = node.frontmatter?.["name"];
1752
+ const name = typeof raw === "string" ? raw : "";
1753
+ if (!name) continue;
1754
+ out.set(normalizeTrigger(name), true);
1755
+ }
1756
+ return out;
1757
+ }
1758
+
1759
+ // kernel/orchestrator/post-walk-transforms.ts
1760
+ var POST_WALK_TRANSFORMS = [
1761
+ {
1762
+ id: "dedupe-links",
1763
+ description: "Collapse identical (source, target, kind, normalizedTrigger) edges across extractors; union sources[] and pick max confidence on merge.",
1764
+ run(links) {
1765
+ return dedupeLinks(links);
1766
+ }
1767
+ },
1768
+ {
1769
+ id: "lift-mention-confidence",
1770
+ description: "Bump resolved `mentions` links to confidence 1.0 once the full node graph is known (post-merge polish).",
1771
+ run(links, nodes) {
1772
+ liftMentionConfidence(links, nodes);
1773
+ }
1774
+ }
1775
+ ];
1776
+ function applyPostWalkTransforms(links, nodes, transforms = POST_WALK_TRANSFORMS) {
1777
+ let current = links;
1778
+ for (const transform of transforms) {
1779
+ const next = transform.run(current, nodes);
1780
+ if (next) current = next;
1781
+ }
1782
+ return current;
1783
+ }
1784
+
1326
1785
  // kernel/orchestrator/renames.ts
1327
1786
  function findHighConfidenceRenames(opts) {
1328
1787
  const ops = [];
@@ -1457,11 +1916,11 @@ function detectRenamesAndOrphans(prior, current, issues, silenced) {
1457
1916
 
1458
1917
  // kernel/scan/walk-content.ts
1459
1918
  import { readFile, readdir, lstat } from "fs/promises";
1460
- import { join as join3, relative as relative2, sep } from "path";
1919
+ import { join as join7, relative as relative2, sep } from "path";
1461
1920
 
1462
1921
  // kernel/scan/ignore.ts
1463
- import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
1464
- import { dirname as dirname3, resolve as resolve5 } from "path";
1922
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1923
+ import { dirname as dirname4, resolve as resolve7 } from "path";
1465
1924
  import { fileURLToPath } from "url";
1466
1925
  import ignoreFactory from "ignore";
1467
1926
  function buildIgnoreFilter(opts = {}) {
@@ -1493,18 +1952,18 @@ function loadDefaultsText() {
1493
1952
  return cachedDefaults;
1494
1953
  }
1495
1954
  function readDefaultsFromDisk() {
1496
- const here = dirname3(fileURLToPath(import.meta.url));
1955
+ const here = dirname4(fileURLToPath(import.meta.url));
1497
1956
  const candidates = [
1498
- resolve5(here, "../../config/defaults/skillmapignore"),
1957
+ resolve7(here, "../../config/defaults/skillmapignore"),
1499
1958
  // src/kernel/scan/ → src/config/defaults/
1500
- resolve5(here, "../config/defaults/skillmapignore"),
1959
+ resolve7(here, "../config/defaults/skillmapignore"),
1501
1960
  // dist/cli.js → dist/config/defaults/ (siblings)
1502
- resolve5(here, "config/defaults/skillmapignore")
1961
+ resolve7(here, "config/defaults/skillmapignore")
1503
1962
  ];
1504
1963
  for (const candidate of candidates) {
1505
- if (existsSync3(candidate)) {
1964
+ if (existsSync6(candidate)) {
1506
1965
  try {
1507
- return readFileSync5(candidate, "utf8");
1966
+ return readFileSync7(candidate, "utf8");
1508
1967
  } catch {
1509
1968
  }
1510
1969
  }
@@ -1514,29 +1973,6 @@ function readDefaultsFromDisk() {
1514
1973
 
1515
1974
  // plugins/core/parsers/frontmatter-yaml/index.ts
1516
1975
  import yaml from "js-yaml";
1517
-
1518
- // kernel/util/strip-prototype-pollution.ts
1519
- var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
1520
- "__proto__",
1521
- "constructor",
1522
- "prototype"
1523
- ]);
1524
- function stripPrototypePollution(value) {
1525
- return strip(value);
1526
- }
1527
- function strip(value) {
1528
- if (value === null || value === void 0) return value;
1529
- if (typeof value !== "object") return value;
1530
- if (Array.isArray(value)) return value.map(strip);
1531
- const out = {};
1532
- for (const [k, v] of Object.entries(value)) {
1533
- if (FORBIDDEN_KEYS.has(k)) continue;
1534
- out[k] = strip(v);
1535
- }
1536
- return out;
1537
- }
1538
-
1539
- // plugins/core/parsers/frontmatter-yaml/index.ts
1540
1976
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
1541
1977
  var frontmatterYamlParser = {
1542
1978
  id: "frontmatter-yaml",
@@ -1668,7 +2104,7 @@ async function* walkRoot(root, current, filter, extensions) {
1668
2104
  }
1669
2105
  for (const entry of entries) {
1670
2106
  const name = entry.name;
1671
- const full = join3(current, name);
2107
+ const full = join7(current, name);
1672
2108
  const rel = relative2(root, full).split(sep).join("/");
1673
2109
  if (filter.ignores(rel)) continue;
1674
2110
  if (entry.isSymbolicLink()) continue;
@@ -1712,19 +2148,19 @@ function resolveProviderWalk(provider) {
1712
2148
  }
1713
2149
 
1714
2150
  // kernel/sidecar/parse.ts
1715
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
1716
- import { dirname as dirname4, resolve as resolve6 } from "path";
2151
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
2152
+ import { dirname as dirname5, resolve as resolve8 } from "path";
1717
2153
  import { createRequire as createRequire3 } from "module";
1718
2154
  import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
1719
2155
  import yaml2 from "js-yaml";
1720
2156
  function readSidecarFor(mdAbsolutePath) {
1721
2157
  const sidecarPath = sidecarPathFor(mdAbsolutePath);
1722
- if (!existsSync4(sidecarPath)) {
2158
+ if (!existsSync7(sidecarPath)) {
1723
2159
  return { parsed: null, present: false, issues: [] };
1724
2160
  }
1725
2161
  let raw;
1726
2162
  try {
1727
- raw = readFileSync6(sidecarPath, "utf8");
2163
+ raw = readFileSync8(sidecarPath, "utf8");
1728
2164
  } catch (err) {
1729
2165
  return {
1730
2166
  parsed: null,
@@ -1743,7 +2179,7 @@ function readSidecarFor(mdAbsolutePath) {
1743
2179
  };
1744
2180
  }
1745
2181
  parsedYaml = stripPrototypePollution(parsedYaml);
1746
- if (!isPlainObject(parsedYaml)) {
2182
+ if (!isPlainObject2(parsedYaml)) {
1747
2183
  return {
1748
2184
  parsed: null,
1749
2185
  present: true,
@@ -1762,7 +2198,7 @@ function readSidecarFor(mdAbsolutePath) {
1762
2198
  const root = parsedYaml;
1763
2199
  const identityBlock = root["identity"];
1764
2200
  const annotationsRaw = root["annotations"];
1765
- const annotations = isPlainObject(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
2201
+ const annotations = isPlainObject2(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
1766
2202
  return {
1767
2203
  parsed: {
1768
2204
  filePath: sidecarPath,
@@ -1782,7 +2218,7 @@ function sidecarPathFor(mdAbsolutePath) {
1782
2218
  }
1783
2219
  return `${mdAbsolutePath}.sm`;
1784
2220
  }
1785
- function isPlainObject(value) {
2221
+ function isPlainObject2(value) {
1786
2222
  return value !== null && typeof value === "object" && !Array.isArray(value);
1787
2223
  }
1788
2224
  var cachedSidecarValidator = null;
@@ -1792,10 +2228,10 @@ function getSidecarValidator() {
1792
2228
  applyAjvFormats(ajv);
1793
2229
  const specRoot = resolveSpecRoot2();
1794
2230
  const annotationsSchema = JSON.parse(
1795
- readFileSync6(resolve6(specRoot, "schemas/annotations.schema.json"), "utf8")
2231
+ readFileSync8(resolve8(specRoot, "schemas/annotations.schema.json"), "utf8")
1796
2232
  );
1797
2233
  const sidecarSchema = JSON.parse(
1798
- readFileSync6(resolve6(specRoot, "schemas/sidecar.schema.json"), "utf8")
2234
+ readFileSync8(resolve8(specRoot, "schemas/sidecar.schema.json"), "utf8")
1799
2235
  );
1800
2236
  ajv.addSchema(annotationsSchema);
1801
2237
  cachedSidecarValidator = ajv.compile(sidecarSchema);
@@ -1805,7 +2241,7 @@ function resolveSpecRoot2() {
1805
2241
  const require2 = createRequire3(import.meta.url);
1806
2242
  try {
1807
2243
  const indexPath = require2.resolve("@skill-map/spec/index.json");
1808
- return dirname4(indexPath);
2244
+ return dirname5(indexPath);
1809
2245
  } catch {
1810
2246
  throw new Error(
1811
2247
  "@skill-map/spec not resolvable: sidecar reader cannot load schemas."
@@ -1824,8 +2260,8 @@ function computeDriftStatus(args) {
1824
2260
  }
1825
2261
 
1826
2262
  // kernel/sidecar/discover-orphans.ts
1827
- import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1828
- import { join as join4, relative as relative3, sep as sep2 } from "path";
2263
+ import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2264
+ import { join as join8, relative as relative3, sep as sep2 } from "path";
1829
2265
  function discoverOrphanSidecars(roots, shouldSkip) {
1830
2266
  const out = [];
1831
2267
  for (const root of roots) {
@@ -1841,7 +2277,7 @@ function walk(root, current, shouldSkip, out) {
1841
2277
  return;
1842
2278
  }
1843
2279
  for (const entry of entries) {
1844
- const full = join4(current, entry.name);
2280
+ const full = join8(current, entry.name);
1845
2281
  const rel = relative3(root, full).split(sep2).join("/");
1846
2282
  if (shouldSkip(rel)) continue;
1847
2283
  if (entry.isSymbolicLink()) continue;
@@ -1852,7 +2288,7 @@ function walk(root, current, shouldSkip, out) {
1852
2288
  if (!entry.isFile()) continue;
1853
2289
  if (!entry.name.endsWith(".sm")) continue;
1854
2290
  const expectedMd = `${full.slice(0, -".sm".length)}.md`;
1855
- if (existsSync5(expectedMd) && safeIsFile(expectedMd)) continue;
2291
+ if (existsSync8(expectedMd) && safeIsFile(expectedMd)) continue;
1856
2292
  out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
1857
2293
  }
1858
2294
  }
@@ -1865,51 +2301,15 @@ function safeIsFile(path) {
1865
2301
  }
1866
2302
 
1867
2303
  // kernel/sidecar/store.ts
1868
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
2304
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1869
2305
  import { dirname as dirname6, resolve as resolve9 } from "path";
1870
2306
  import { createRequire as createRequire4 } from "module";
1871
2307
  import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
1872
2308
  import yaml3 from "js-yaml";
1873
2309
 
1874
- // core/config/atomic-write.ts
1875
- import {
1876
- closeSync,
1877
- constants as fsConstants,
1878
- existsSync as existsSync6,
1879
- mkdirSync,
1880
- openSync,
1881
- readFileSync as readFileSync7,
1882
- renameSync,
1883
- unlinkSync,
1884
- writeSync
1885
- } from "fs";
1886
- import { randomBytes } from "crypto";
1887
- import { dirname as dirname5 } from "path";
1888
-
1889
- // core/config/helper.ts
1890
- import { homedir as osHomedir } from "os";
1891
- import { isAbsolute as isAbsolute2, join as join7, resolve as resolve8 } from "path";
1892
-
1893
- // kernel/config/loader.ts
1894
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
1895
-
1896
- // kernel/util/skill-map-paths.ts
1897
- import { join as join6 } from "path";
1898
-
1899
- // core/paths/db-path.ts
1900
- import { join as join5, resolve as resolve7 } from "path";
1901
- var SKILL_MAP_DIR = ".skill-map";
1902
- var DB_FILENAME = "skill-map.db";
1903
- var LOCAL_SETTINGS_FILENAME = "settings.local.json";
1904
- var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
1905
- var GITIGNORE_ENTRIES = [
1906
- `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
1907
- `${SKILL_MAP_DIR}/${DB_FILENAME}`
1908
- ];
1909
-
1910
2310
  // kernel/orchestrator/node-build.ts
1911
2311
  import { createHash } from "crypto";
1912
- import { existsSync as existsSync9 } from "fs";
2312
+ import { existsSync as existsSync10 } from "fs";
1913
2313
  import { isAbsolute as isAbsolute3, resolve as resolvePath } from "path";
1914
2314
  import "js-tiktoken/lite";
1915
2315
  import yaml4 from "js-yaml";
@@ -2073,11 +2473,11 @@ function resolveSidecarOverlay(relativePath, nodePathForIssue, roots, liveBodyHa
2073
2473
  }
2074
2474
  function resolveAbsoluteMdPath(relativePath, roots) {
2075
2475
  if (isAbsolute3(relativePath)) {
2076
- return existsSync9(relativePath) ? relativePath : null;
2476
+ return existsSync10(relativePath) ? relativePath : null;
2077
2477
  }
2078
2478
  for (const root of roots) {
2079
2479
  const candidate = resolvePath(root, relativePath);
2080
- if (existsSync9(candidate)) return candidate;
2480
+ if (existsSync10(candidate)) return candidate;
2081
2481
  }
2082
2482
  return null;
2083
2483
  }
@@ -2229,7 +2629,7 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
2229
2629
  const cacheDecision = computeCacheDecision({
2230
2630
  extractors: wctx.opts.extractors,
2231
2631
  kind,
2232
- provider: provider.id,
2632
+ activeProvider: wctx.opts.activeProvider,
2233
2633
  nodePath: raw.path,
2234
2634
  bodyHash,
2235
2635
  sidecarAnnotationsHash,
@@ -2443,9 +2843,10 @@ async function runScanInternal(_kernel, options) {
2443
2843
  priorIndex: setup.priorIndex,
2444
2844
  priorExtractorRuns: setup.priorExtractorRuns,
2445
2845
  providerFrontmatter: setup.providerFrontmatter,
2446
- pluginStores: options.pluginStores
2846
+ pluginStores: options.pluginStores,
2847
+ activeProvider: resolveActiveProviderOption(options.activeProvider, options.roots)
2447
2848
  });
2448
- walked.internalLinks = dedupeLinks(walked.internalLinks);
2849
+ walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes);
2449
2850
  recomputeLinkCounts(walked.nodes, walked.internalLinks);
2450
2851
  recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
2451
2852
  await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
@@ -2561,17 +2962,27 @@ function validateRoots(roots) {
2561
2962
  throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
2562
2963
  }
2563
2964
  for (const root of roots) {
2564
- if (!existsSync10(root) || !statSync4(root).isDirectory()) {
2965
+ if (!existsSync11(root) || !statSync4(root).isDirectory()) {
2565
2966
  throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
2566
2967
  }
2567
2968
  }
2568
2969
  }
2970
+ function resolveActiveProviderOption(optionValue, roots) {
2971
+ if (optionValue !== void 0) return optionValue;
2972
+ for (const root of roots) {
2973
+ const absRoot = isAbsolute4(root) ? root : resolve10(root);
2974
+ if (!existsSync11(absRoot)) continue;
2975
+ const detected = resolveActiveProvider(absRoot).resolved;
2976
+ if (detected !== null) return detected;
2977
+ }
2978
+ return null;
2979
+ }
2569
2980
 
2570
2981
  // kernel/scan/watcher.ts
2571
- import { resolve as resolve10, relative as relative4, sep as sep3 } from "path";
2982
+ import { resolve as resolve11, relative as relative4, sep as sep3 } from "path";
2572
2983
  import chokidar from "chokidar";
2573
2984
  function createChokidarWatcher(opts) {
2574
- const absRoots = opts.roots.map((r) => resolve10(opts.cwd, r));
2985
+ const absRoots = opts.roots.map((r) => resolve11(opts.cwd, r));
2575
2986
  const ignoreFilterOpt = opts.ignoreFilter;
2576
2987
  const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
2577
2988
  const ignored = getFilter ? (path) => {