@skill-map/cli 0.32.0 → 0.34.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/cli/tutorial/sm-tutorial/SKILL.md +27 -7
- package/dist/cli.js +1336 -488
- package/dist/cli.js.map +1 -1
- package/dist/index.js +712 -108
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +217 -3
- package/dist/kernel/index.js +712 -108
- package/dist/kernel/index.js.map +1 -1
- package/dist/migrations/001_initial.sql +2 -2
- package/dist/ui/chunk-2QZDJSJN.js +1 -0
- package/dist/ui/chunk-5CFY2K3Y.js +135 -0
- package/dist/ui/{chunk-YQIWQVJ6.js → chunk-L3OLNVKI.js} +9 -9
- package/dist/ui/chunk-MHWM2642.js +123 -0
- package/dist/ui/{chunk-47OZB7LR.js → chunk-TKV6TXTI.js} +1 -1
- package/dist/ui/{chunk-WJLIYGWJ.js → chunk-UK5YFHL3.js} +1 -1
- package/dist/ui/{chunk-FEPH4VNB.js → chunk-UMCC32EJ.js} +3 -3
- package/dist/ui/{chunk-VDQLDTTR.js → chunk-YZ7KCL3G.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/ui/main-4X6AAGKZ.js +2 -0
- package/migrations/001_initial.sql +2 -2
- package/package.json +3 -2
- package/dist/ui/chunk-BCQZKYOD.js +0 -1
- package/dist/ui/chunk-LS2NXZQZ.js +0 -135
- package/dist/ui/chunk-WCE7MTK5.js +0 -123
- package/dist/ui/main-LJIHL73M.js +0 -2
package/dist/kernel/index.js
CHANGED
|
@@ -93,14 +93,15 @@ var Registry = class {
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
// kernel/orchestrator/index.ts
|
|
96
|
-
import { existsSync as
|
|
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.
|
|
104
|
+
version: "0.34.0",
|
|
104
105
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
105
106
|
license: "MIT",
|
|
106
107
|
type: "module",
|
|
@@ -181,6 +182,7 @@ var package_default = {
|
|
|
181
182
|
"js-yaml": "4.1.1",
|
|
182
183
|
kysely: "0.28.17",
|
|
183
184
|
semver: "7.7.4",
|
|
185
|
+
"smol-toml": "1.6.1",
|
|
184
186
|
typanion: "3.14.0",
|
|
185
187
|
ws: "8.20.0"
|
|
186
188
|
},
|
|
@@ -692,12 +694,407 @@ var ORCHESTRATOR_TEXTS = {
|
|
|
692
694
|
runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
|
|
693
695
|
};
|
|
694
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
|
+
|
|
1082
|
+
// kernel/types.ts
|
|
1083
|
+
var ConfidenceTier = Object.freeze({
|
|
1084
|
+
HIGH: 0.9,
|
|
1085
|
+
MEDIUM: 0.6,
|
|
1086
|
+
LOW: 0.3
|
|
1087
|
+
});
|
|
1088
|
+
|
|
695
1089
|
// kernel/orchestrator/extractors.ts
|
|
696
1090
|
async function runExtractorsForNode(opts) {
|
|
697
1091
|
const internalLinks = [];
|
|
698
1092
|
const externalLinks = [];
|
|
699
1093
|
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
700
1094
|
const contributions = [];
|
|
1095
|
+
const signals = [];
|
|
1096
|
+
const virtualNodes = [];
|
|
1097
|
+
const virtualNodePaths = /* @__PURE__ */ new Set();
|
|
701
1098
|
const validators = loadSchemaValidators();
|
|
702
1099
|
for (const extractor of opts.extractors) {
|
|
703
1100
|
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
@@ -769,6 +1166,18 @@ async function runExtractorsForNode(opts) {
|
|
|
769
1166
|
emittedAt: Date.now()
|
|
770
1167
|
});
|
|
771
1168
|
};
|
|
1169
|
+
const emitSignal = (signal) => {
|
|
1170
|
+
const validated = validateSignal(extractor, signal, opts.emitter);
|
|
1171
|
+
if (!validated) return;
|
|
1172
|
+
signals.push(validated);
|
|
1173
|
+
};
|
|
1174
|
+
const emitNode = (emitted) => {
|
|
1175
|
+
if (virtualNodePaths.has(emitted.path)) return;
|
|
1176
|
+
const node = buildVirtualNode(extractor, emitted, opts.emitter);
|
|
1177
|
+
if (!node) return;
|
|
1178
|
+
virtualNodePaths.add(node.path);
|
|
1179
|
+
virtualNodes.push(node);
|
|
1180
|
+
};
|
|
772
1181
|
const store = opts.pluginStores?.get(extractor.pluginId);
|
|
773
1182
|
const ctx = buildExtractorContext(
|
|
774
1183
|
extractor,
|
|
@@ -778,6 +1187,8 @@ async function runExtractorsForNode(opts) {
|
|
|
778
1187
|
emitLink,
|
|
779
1188
|
enrichNode,
|
|
780
1189
|
emitContribution,
|
|
1190
|
+
emitSignal,
|
|
1191
|
+
emitNode,
|
|
781
1192
|
store
|
|
782
1193
|
);
|
|
783
1194
|
await extractor.extract(ctx);
|
|
@@ -786,7 +1197,9 @@ async function runExtractorsForNode(opts) {
|
|
|
786
1197
|
internalLinks,
|
|
787
1198
|
externalLinks,
|
|
788
1199
|
enrichments: Array.from(enrichmentBuffer.values()),
|
|
789
|
-
contributions
|
|
1200
|
+
contributions,
|
|
1201
|
+
signals,
|
|
1202
|
+
virtualNodes
|
|
790
1203
|
};
|
|
791
1204
|
}
|
|
792
1205
|
function readDeclaredContributions(extension) {
|
|
@@ -811,7 +1224,7 @@ function emitExtensionError(emitter, qualifiedId, nodePath, data) {
|
|
|
811
1224
|
})
|
|
812
1225
|
);
|
|
813
1226
|
}
|
|
814
|
-
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
1227
|
+
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, emitSignal, emitNode, store) {
|
|
815
1228
|
const scope = extractor.scope ?? "both";
|
|
816
1229
|
const settings = extractor.resolvedSettings ?? {};
|
|
817
1230
|
return {
|
|
@@ -822,9 +1235,62 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
|
|
|
822
1235
|
emitLink,
|
|
823
1236
|
enrichNode,
|
|
824
1237
|
emitContribution,
|
|
1238
|
+
emitSignal,
|
|
1239
|
+
emitNode,
|
|
825
1240
|
...store !== void 0 ? { store } : {}
|
|
826
1241
|
};
|
|
827
1242
|
}
|
|
1243
|
+
var VIRTUAL_NODE_PLACEHOLDER_HASH = "0".repeat(64);
|
|
1244
|
+
function buildVirtualNode(extractor, emitted, emitter) {
|
|
1245
|
+
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
1246
|
+
if (typeof emitted.path !== "string" || emitted.path.length === 0) {
|
|
1247
|
+
emitter.emit(
|
|
1248
|
+
makeEvent("extension.error", {
|
|
1249
|
+
kind: "virtual-node-missing-path",
|
|
1250
|
+
extensionId: qualifiedId,
|
|
1251
|
+
message: `Extractor ${qualifiedId} emitted a virtual node with no path; dropped.`
|
|
1252
|
+
})
|
|
1253
|
+
);
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
if (typeof emitted.kind !== "string" || emitted.kind.length === 0) {
|
|
1257
|
+
emitter.emit(
|
|
1258
|
+
makeEvent("extension.error", {
|
|
1259
|
+
kind: "virtual-node-missing-kind",
|
|
1260
|
+
extensionId: qualifiedId,
|
|
1261
|
+
virtualPath: emitted.path,
|
|
1262
|
+
message: `Extractor ${qualifiedId} emitted a virtual node at '${emitted.path}' with no kind; dropped.`
|
|
1263
|
+
})
|
|
1264
|
+
);
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
if (!Array.isArray(emitted.derivedFrom) || emitted.derivedFrom.length === 0) {
|
|
1268
|
+
emitter.emit(
|
|
1269
|
+
makeEvent("extension.error", {
|
|
1270
|
+
kind: "virtual-node-missing-derived-from",
|
|
1271
|
+
extensionId: qualifiedId,
|
|
1272
|
+
virtualPath: emitted.path,
|
|
1273
|
+
message: `Extractor ${qualifiedId} emitted a virtual node at '${emitted.path}' with empty derivedFrom; dropped.`
|
|
1274
|
+
})
|
|
1275
|
+
);
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
const node = {
|
|
1279
|
+
path: emitted.path,
|
|
1280
|
+
kind: emitted.kind,
|
|
1281
|
+
provider: emitted.provider,
|
|
1282
|
+
bodyHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
1283
|
+
frontmatterHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
1284
|
+
bytes: { frontmatter: 0, body: 0, total: 0 },
|
|
1285
|
+
linksOutCount: 0,
|
|
1286
|
+
linksInCount: 0,
|
|
1287
|
+
externalRefsCount: 0,
|
|
1288
|
+
virtual: true,
|
|
1289
|
+
derivedFrom: [...emitted.derivedFrom]
|
|
1290
|
+
};
|
|
1291
|
+
if (emitted.frontmatter) node.frontmatter = emitted.frontmatter;
|
|
1292
|
+
return node;
|
|
1293
|
+
}
|
|
828
1294
|
function validateLink(extractor, link, emitter) {
|
|
829
1295
|
const knownKinds = ["invokes", "references", "mentions", "supersedes"];
|
|
830
1296
|
if (!knownKinds.includes(link.kind)) {
|
|
@@ -845,9 +1311,68 @@ function validateLink(extractor, link, emitter) {
|
|
|
845
1311
|
);
|
|
846
1312
|
return null;
|
|
847
1313
|
}
|
|
848
|
-
const
|
|
1314
|
+
const c = link.confidence;
|
|
1315
|
+
if (c !== void 0 && (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1)) {
|
|
1316
|
+
const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
|
|
1317
|
+
emitter.emit(
|
|
1318
|
+
makeEvent("extension.error", {
|
|
1319
|
+
kind: "link-confidence-out-of-range",
|
|
1320
|
+
extensionId: qualifiedId,
|
|
1321
|
+
confidence: c,
|
|
1322
|
+
message: `Extractor ${qualifiedId} emitted a Link with confidence ${String(c)} outside [0..1]; dropped.`
|
|
1323
|
+
})
|
|
1324
|
+
);
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
const confidence = c ?? ConfidenceTier.MEDIUM;
|
|
849
1328
|
return { ...link, confidence };
|
|
850
1329
|
}
|
|
1330
|
+
var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes"];
|
|
1331
|
+
function validateSignal(extractor, signal, emitter) {
|
|
1332
|
+
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
1333
|
+
if (!Array.isArray(signal.candidates) || signal.candidates.length === 0) {
|
|
1334
|
+
emitter.emit(
|
|
1335
|
+
makeEvent("extension.error", {
|
|
1336
|
+
kind: "signal-no-candidates",
|
|
1337
|
+
extensionId: qualifiedId,
|
|
1338
|
+
signal: { source: signal.source, scope: signal.scope },
|
|
1339
|
+
message: `Extractor ${qualifiedId} emitted a Signal with no candidates; dropped.`
|
|
1340
|
+
})
|
|
1341
|
+
);
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
for (const candidate of signal.candidates) {
|
|
1345
|
+
if (!isValidSignalCandidate(qualifiedId, candidate, emitter)) return null;
|
|
1346
|
+
}
|
|
1347
|
+
return signal;
|
|
1348
|
+
}
|
|
1349
|
+
function isValidSignalCandidate(qualifiedId, candidate, emitter) {
|
|
1350
|
+
if (!KNOWN_LINK_KINDS.includes(candidate.kind)) {
|
|
1351
|
+
emitter.emit(
|
|
1352
|
+
makeEvent("extension.error", {
|
|
1353
|
+
kind: "signal-candidate-kind-not-declared",
|
|
1354
|
+
extensionId: qualifiedId,
|
|
1355
|
+
candidateKind: candidate.kind,
|
|
1356
|
+
declaredKinds: KNOWN_LINK_KINDS,
|
|
1357
|
+
message: `Extractor ${qualifiedId} emitted a Signal candidate with off-enum kind '${String(candidate.kind)}'; dropped.`
|
|
1358
|
+
})
|
|
1359
|
+
);
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
const c = candidate.confidence;
|
|
1363
|
+
if (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1) {
|
|
1364
|
+
emitter.emit(
|
|
1365
|
+
makeEvent("extension.error", {
|
|
1366
|
+
kind: "signal-candidate-confidence-out-of-range",
|
|
1367
|
+
extensionId: qualifiedId,
|
|
1368
|
+
confidence: candidate.confidence,
|
|
1369
|
+
message: `Extractor ${qualifiedId} emitted a Signal candidate with confidence ${String(c)} outside [0..1]; dropped.`
|
|
1370
|
+
})
|
|
1371
|
+
);
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
return true;
|
|
1375
|
+
}
|
|
851
1376
|
function dedupeLinks(links) {
|
|
852
1377
|
const out = /* @__PURE__ */ new Map();
|
|
853
1378
|
for (const link of links) {
|
|
@@ -862,6 +1387,9 @@ function dedupeLinks(links) {
|
|
|
862
1387
|
existing.sources = [...existing.sources, src];
|
|
863
1388
|
}
|
|
864
1389
|
}
|
|
1390
|
+
if (link.confidence > existing.confidence) {
|
|
1391
|
+
existing.confidence = link.confidence;
|
|
1392
|
+
}
|
|
865
1393
|
continue;
|
|
866
1394
|
}
|
|
867
1395
|
out.set(key, link);
|
|
@@ -894,7 +1422,9 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
|
|
|
894
1422
|
}
|
|
895
1423
|
}
|
|
896
1424
|
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
1425
|
+
var VIRTUAL_NODE_SCHEME_RE = /^mcp:\/\//i;
|
|
897
1426
|
function isExternalUrlLink(link) {
|
|
1427
|
+
if (VIRTUAL_NODE_SCHEME_RE.test(link.target)) return false;
|
|
898
1428
|
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
899
1429
|
}
|
|
900
1430
|
|
|
@@ -1053,13 +1583,9 @@ function originatingNodeOf(link, priorNodePaths) {
|
|
|
1053
1583
|
}
|
|
1054
1584
|
function computeCacheDecision(opts) {
|
|
1055
1585
|
const applicableExtractors = opts.extractors.filter((ex) => {
|
|
1056
|
-
|
|
1057
|
-
if (!
|
|
1058
|
-
return
|
|
1059
|
-
const slashIdx = qualified.indexOf("/");
|
|
1060
|
-
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
1061
|
-
return kindOnly === opts.kind;
|
|
1062
|
-
});
|
|
1586
|
+
if (!matchesKindPrecondition(ex, opts.kind)) return false;
|
|
1587
|
+
if (!matchesProviderPrecondition(ex, opts.provider, opts.activeProvider)) return false;
|
|
1588
|
+
return true;
|
|
1063
1589
|
});
|
|
1064
1590
|
const applicableQualifiedIds = new Set(
|
|
1065
1591
|
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
@@ -1073,6 +1599,22 @@ function computeCacheDecision(opts) {
|
|
|
1073
1599
|
fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
|
|
1074
1600
|
};
|
|
1075
1601
|
}
|
|
1602
|
+
function matchesKindPrecondition(ex, kind) {
|
|
1603
|
+
const kinds = ex.precondition?.kind;
|
|
1604
|
+
if (!kinds || kinds.length === 0) return true;
|
|
1605
|
+
return kinds.some((qualified) => {
|
|
1606
|
+
const slashIdx = qualified.indexOf("/");
|
|
1607
|
+
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
1608
|
+
return kindOnly === kind;
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
function matchesProviderPrecondition(ex, nodeProvider, activeProvider) {
|
|
1612
|
+
const providers = ex.precondition?.provider;
|
|
1613
|
+
if (!providers || providers.length === 0) return true;
|
|
1614
|
+
if (!providers.includes(nodeProvider)) return false;
|
|
1615
|
+
if (activeProvider === null) return false;
|
|
1616
|
+
return providers.includes(activeProvider);
|
|
1617
|
+
}
|
|
1076
1618
|
function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
|
|
1077
1619
|
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1078
1620
|
const missingExtractors = [];
|
|
@@ -1172,6 +1714,75 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
|
|
|
1172
1714
|
return "obsolete";
|
|
1173
1715
|
}
|
|
1174
1716
|
|
|
1717
|
+
// kernel/trigger-normalize.ts
|
|
1718
|
+
function normalizeTrigger(source) {
|
|
1719
|
+
let out = source.normalize("NFD");
|
|
1720
|
+
out = out.replace(new RegExp("\\p{Mn}+", "gu"), "");
|
|
1721
|
+
out = out.toLowerCase();
|
|
1722
|
+
out = out.replace(/[-_\s]+/g, " ");
|
|
1723
|
+
out = out.replace(/ +/g, " ");
|
|
1724
|
+
return out.trim();
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// kernel/orchestrator/lift-mention-confidence.ts
|
|
1728
|
+
function liftMentionConfidence(links, nodes) {
|
|
1729
|
+
if (!links.some((l) => l.kind === "mentions")) return;
|
|
1730
|
+
const byPath2 = /* @__PURE__ */ new Set();
|
|
1731
|
+
for (const node of nodes) byPath2.add(node.path);
|
|
1732
|
+
const byNormalizedName = indexByNormalizedName(nodes);
|
|
1733
|
+
for (const link of links) {
|
|
1734
|
+
if (link.kind !== "mentions") continue;
|
|
1735
|
+
if (isResolved(link, byPath2, byNormalizedName)) {
|
|
1736
|
+
link.confidence = 1;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
function isResolved(link, byPath2, byNormalizedName) {
|
|
1741
|
+
const normalized = link.trigger?.normalizedTrigger;
|
|
1742
|
+
if (normalized) {
|
|
1743
|
+
const withoutSigil = normalized.replace(/^[/@]/, "").trim();
|
|
1744
|
+
if (byNormalizedName.has(withoutSigil)) return true;
|
|
1745
|
+
}
|
|
1746
|
+
if (byPath2.has(link.target)) return true;
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1749
|
+
function indexByNormalizedName(nodes) {
|
|
1750
|
+
const out = /* @__PURE__ */ new Map();
|
|
1751
|
+
for (const node of nodes) {
|
|
1752
|
+
const raw = node.frontmatter?.["name"];
|
|
1753
|
+
const name = typeof raw === "string" ? raw : "";
|
|
1754
|
+
if (!name) continue;
|
|
1755
|
+
out.set(normalizeTrigger(name), true);
|
|
1756
|
+
}
|
|
1757
|
+
return out;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// kernel/orchestrator/post-walk-transforms.ts
|
|
1761
|
+
var POST_WALK_TRANSFORMS = [
|
|
1762
|
+
{
|
|
1763
|
+
id: "dedupe-links",
|
|
1764
|
+
description: "Collapse identical (source, target, kind, normalizedTrigger) edges across extractors; union sources[] and pick max confidence on merge.",
|
|
1765
|
+
run(links) {
|
|
1766
|
+
return dedupeLinks(links);
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
id: "lift-mention-confidence",
|
|
1771
|
+
description: "Bump resolved `mentions` links to confidence 1.0 once the full node graph is known (post-merge polish).",
|
|
1772
|
+
run(links, nodes) {
|
|
1773
|
+
liftMentionConfidence(links, nodes);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
];
|
|
1777
|
+
function applyPostWalkTransforms(links, nodes, transforms = POST_WALK_TRANSFORMS) {
|
|
1778
|
+
let current = links;
|
|
1779
|
+
for (const transform of transforms) {
|
|
1780
|
+
const next = transform.run(current, nodes);
|
|
1781
|
+
if (next) current = next;
|
|
1782
|
+
}
|
|
1783
|
+
return current;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1175
1786
|
// kernel/orchestrator/renames.ts
|
|
1176
1787
|
function findHighConfidenceRenames(opts) {
|
|
1177
1788
|
const ops = [];
|
|
@@ -1182,7 +1793,7 @@ function findHighConfidenceRenames(opts) {
|
|
|
1182
1793
|
if (opts.claimedNew.has(toPath)) continue;
|
|
1183
1794
|
const toNode = opts.currentByPath.get(toPath);
|
|
1184
1795
|
if (toNode.bodyHash === fromNode.bodyHash) {
|
|
1185
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
1796
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.HIGH });
|
|
1186
1797
|
opts.claimedDeleted.add(fromPath);
|
|
1187
1798
|
opts.claimedNew.add(toPath);
|
|
1188
1799
|
break;
|
|
@@ -1217,13 +1828,13 @@ function claimSingletonRenames(opts) {
|
|
|
1217
1828
|
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1218
1829
|
if (remaining.length === 1) {
|
|
1219
1830
|
const fromPath = remaining[0];
|
|
1220
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
1831
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM });
|
|
1221
1832
|
opts.issues.push({
|
|
1222
1833
|
analyzerId: "auto-rename-medium",
|
|
1223
1834
|
severity: "warn",
|
|
1224
1835
|
nodeIds: [toPath],
|
|
1225
1836
|
message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
|
|
1226
|
-
data: { from: fromPath, to: toPath, confidence:
|
|
1837
|
+
data: { from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM }
|
|
1227
1838
|
});
|
|
1228
1839
|
opts.claimedDeleted.add(fromPath);
|
|
1229
1840
|
opts.claimedNew.add(toPath);
|
|
@@ -1306,11 +1917,11 @@ function detectRenamesAndOrphans(prior, current, issues, silenced) {
|
|
|
1306
1917
|
|
|
1307
1918
|
// kernel/scan/walk-content.ts
|
|
1308
1919
|
import { readFile, readdir, lstat } from "fs/promises";
|
|
1309
|
-
import { join as
|
|
1920
|
+
import { join as join7, relative as relative2, sep } from "path";
|
|
1310
1921
|
|
|
1311
1922
|
// kernel/scan/ignore.ts
|
|
1312
|
-
import { existsSync as
|
|
1313
|
-
import { dirname as
|
|
1923
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
1924
|
+
import { dirname as dirname4, resolve as resolve7 } from "path";
|
|
1314
1925
|
import { fileURLToPath } from "url";
|
|
1315
1926
|
import ignoreFactory from "ignore";
|
|
1316
1927
|
function buildIgnoreFilter(opts = {}) {
|
|
@@ -1342,18 +1953,18 @@ function loadDefaultsText() {
|
|
|
1342
1953
|
return cachedDefaults;
|
|
1343
1954
|
}
|
|
1344
1955
|
function readDefaultsFromDisk() {
|
|
1345
|
-
const here =
|
|
1956
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
1346
1957
|
const candidates = [
|
|
1347
|
-
|
|
1958
|
+
resolve7(here, "../../config/defaults/skillmapignore"),
|
|
1348
1959
|
// src/kernel/scan/ → src/config/defaults/
|
|
1349
|
-
|
|
1960
|
+
resolve7(here, "../config/defaults/skillmapignore"),
|
|
1350
1961
|
// dist/cli.js → dist/config/defaults/ (siblings)
|
|
1351
|
-
|
|
1962
|
+
resolve7(here, "config/defaults/skillmapignore")
|
|
1352
1963
|
];
|
|
1353
1964
|
for (const candidate of candidates) {
|
|
1354
|
-
if (
|
|
1965
|
+
if (existsSync6(candidate)) {
|
|
1355
1966
|
try {
|
|
1356
|
-
return
|
|
1967
|
+
return readFileSync7(candidate, "utf8");
|
|
1357
1968
|
} catch {
|
|
1358
1969
|
}
|
|
1359
1970
|
}
|
|
@@ -1363,29 +1974,6 @@ function readDefaultsFromDisk() {
|
|
|
1363
1974
|
|
|
1364
1975
|
// plugins/core/parsers/frontmatter-yaml/index.ts
|
|
1365
1976
|
import yaml from "js-yaml";
|
|
1366
|
-
|
|
1367
|
-
// kernel/util/strip-prototype-pollution.ts
|
|
1368
|
-
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
1369
|
-
"__proto__",
|
|
1370
|
-
"constructor",
|
|
1371
|
-
"prototype"
|
|
1372
|
-
]);
|
|
1373
|
-
function stripPrototypePollution(value) {
|
|
1374
|
-
return strip(value);
|
|
1375
|
-
}
|
|
1376
|
-
function strip(value) {
|
|
1377
|
-
if (value === null || value === void 0) return value;
|
|
1378
|
-
if (typeof value !== "object") return value;
|
|
1379
|
-
if (Array.isArray(value)) return value.map(strip);
|
|
1380
|
-
const out = {};
|
|
1381
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1382
|
-
if (FORBIDDEN_KEYS.has(k)) continue;
|
|
1383
|
-
out[k] = strip(v);
|
|
1384
|
-
}
|
|
1385
|
-
return out;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// plugins/core/parsers/frontmatter-yaml/index.ts
|
|
1389
1977
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
1390
1978
|
var frontmatterYamlParser = {
|
|
1391
1979
|
id: "frontmatter-yaml",
|
|
@@ -1427,10 +2015,45 @@ var plainParser = {
|
|
|
1427
2015
|
}
|
|
1428
2016
|
};
|
|
1429
2017
|
|
|
2018
|
+
// plugins/core/parsers/toml/index.ts
|
|
2019
|
+
import { parse as parseToml } from "smol-toml";
|
|
2020
|
+
var tomlParser = {
|
|
2021
|
+
id: "toml",
|
|
2022
|
+
parse(raw, _path) {
|
|
2023
|
+
let parsed = {};
|
|
2024
|
+
const issues = [];
|
|
2025
|
+
try {
|
|
2026
|
+
const doc = parseToml(raw);
|
|
2027
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
2028
|
+
parsed = stripPrototypePollution(doc);
|
|
2029
|
+
}
|
|
2030
|
+
} catch (err) {
|
|
2031
|
+
issues.push({
|
|
2032
|
+
code: "frontmatter-parse-error",
|
|
2033
|
+
message: sanitiseParseErrorMessage2(err)
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
const out = {
|
|
2037
|
+
frontmatterRaw: raw,
|
|
2038
|
+
frontmatter: parsed,
|
|
2039
|
+
body: ""
|
|
2040
|
+
};
|
|
2041
|
+
if (issues.length > 0) {
|
|
2042
|
+
return { ...out, issues };
|
|
2043
|
+
}
|
|
2044
|
+
return out;
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
function sanitiseParseErrorMessage2(err) {
|
|
2048
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
2049
|
+
return raw.replace(/[-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
2050
|
+
}
|
|
2051
|
+
|
|
1430
2052
|
// kernel/scan/parsers/index.ts
|
|
1431
2053
|
var REGISTRY = /* @__PURE__ */ new Map([
|
|
1432
2054
|
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
1433
|
-
[plainParser.id, plainParser]
|
|
2055
|
+
[plainParser.id, plainParser],
|
|
2056
|
+
[tomlParser.id, tomlParser]
|
|
1434
2057
|
]);
|
|
1435
2058
|
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
1436
2059
|
function getParser(id) {
|
|
@@ -1482,7 +2105,7 @@ async function* walkRoot(root, current, filter, extensions) {
|
|
|
1482
2105
|
}
|
|
1483
2106
|
for (const entry of entries) {
|
|
1484
2107
|
const name = entry.name;
|
|
1485
|
-
const full =
|
|
2108
|
+
const full = join7(current, name);
|
|
1486
2109
|
const rel = relative2(root, full).split(sep).join("/");
|
|
1487
2110
|
if (filter.ignores(rel)) continue;
|
|
1488
2111
|
if (entry.isSymbolicLink()) continue;
|
|
@@ -1526,19 +2149,19 @@ function resolveProviderWalk(provider) {
|
|
|
1526
2149
|
}
|
|
1527
2150
|
|
|
1528
2151
|
// kernel/sidecar/parse.ts
|
|
1529
|
-
import { existsSync as
|
|
1530
|
-
import { dirname as
|
|
2152
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
2153
|
+
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
1531
2154
|
import { createRequire as createRequire3 } from "module";
|
|
1532
2155
|
import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
|
|
1533
2156
|
import yaml2 from "js-yaml";
|
|
1534
2157
|
function readSidecarFor(mdAbsolutePath) {
|
|
1535
2158
|
const sidecarPath = sidecarPathFor(mdAbsolutePath);
|
|
1536
|
-
if (!
|
|
2159
|
+
if (!existsSync7(sidecarPath)) {
|
|
1537
2160
|
return { parsed: null, present: false, issues: [] };
|
|
1538
2161
|
}
|
|
1539
2162
|
let raw;
|
|
1540
2163
|
try {
|
|
1541
|
-
raw =
|
|
2164
|
+
raw = readFileSync8(sidecarPath, "utf8");
|
|
1542
2165
|
} catch (err) {
|
|
1543
2166
|
return {
|
|
1544
2167
|
parsed: null,
|
|
@@ -1557,7 +2180,7 @@ function readSidecarFor(mdAbsolutePath) {
|
|
|
1557
2180
|
};
|
|
1558
2181
|
}
|
|
1559
2182
|
parsedYaml = stripPrototypePollution(parsedYaml);
|
|
1560
|
-
if (!
|
|
2183
|
+
if (!isPlainObject2(parsedYaml)) {
|
|
1561
2184
|
return {
|
|
1562
2185
|
parsed: null,
|
|
1563
2186
|
present: true,
|
|
@@ -1576,7 +2199,7 @@ function readSidecarFor(mdAbsolutePath) {
|
|
|
1576
2199
|
const root = parsedYaml;
|
|
1577
2200
|
const identityBlock = root["identity"];
|
|
1578
2201
|
const annotationsRaw = root["annotations"];
|
|
1579
|
-
const annotations =
|
|
2202
|
+
const annotations = isPlainObject2(annotationsRaw) ? Object.keys(annotationsRaw).length === 0 ? null : annotationsRaw : null;
|
|
1580
2203
|
return {
|
|
1581
2204
|
parsed: {
|
|
1582
2205
|
filePath: sidecarPath,
|
|
@@ -1596,7 +2219,7 @@ function sidecarPathFor(mdAbsolutePath) {
|
|
|
1596
2219
|
}
|
|
1597
2220
|
return `${mdAbsolutePath}.sm`;
|
|
1598
2221
|
}
|
|
1599
|
-
function
|
|
2222
|
+
function isPlainObject2(value) {
|
|
1600
2223
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1601
2224
|
}
|
|
1602
2225
|
var cachedSidecarValidator = null;
|
|
@@ -1606,10 +2229,10 @@ function getSidecarValidator() {
|
|
|
1606
2229
|
applyAjvFormats(ajv);
|
|
1607
2230
|
const specRoot = resolveSpecRoot2();
|
|
1608
2231
|
const annotationsSchema = JSON.parse(
|
|
1609
|
-
|
|
2232
|
+
readFileSync8(resolve8(specRoot, "schemas/annotations.schema.json"), "utf8")
|
|
1610
2233
|
);
|
|
1611
2234
|
const sidecarSchema = JSON.parse(
|
|
1612
|
-
|
|
2235
|
+
readFileSync8(resolve8(specRoot, "schemas/sidecar.schema.json"), "utf8")
|
|
1613
2236
|
);
|
|
1614
2237
|
ajv.addSchema(annotationsSchema);
|
|
1615
2238
|
cachedSidecarValidator = ajv.compile(sidecarSchema);
|
|
@@ -1619,7 +2242,7 @@ function resolveSpecRoot2() {
|
|
|
1619
2242
|
const require2 = createRequire3(import.meta.url);
|
|
1620
2243
|
try {
|
|
1621
2244
|
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
1622
|
-
return
|
|
2245
|
+
return dirname5(indexPath);
|
|
1623
2246
|
} catch {
|
|
1624
2247
|
throw new Error(
|
|
1625
2248
|
"@skill-map/spec not resolvable: sidecar reader cannot load schemas."
|
|
@@ -1638,8 +2261,8 @@ function computeDriftStatus(args) {
|
|
|
1638
2261
|
}
|
|
1639
2262
|
|
|
1640
2263
|
// kernel/sidecar/discover-orphans.ts
|
|
1641
|
-
import { existsSync as
|
|
1642
|
-
import { join as
|
|
2264
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
2265
|
+
import { join as join8, relative as relative3, sep as sep2 } from "path";
|
|
1643
2266
|
function discoverOrphanSidecars(roots, shouldSkip) {
|
|
1644
2267
|
const out = [];
|
|
1645
2268
|
for (const root of roots) {
|
|
@@ -1655,7 +2278,7 @@ function walk(root, current, shouldSkip, out) {
|
|
|
1655
2278
|
return;
|
|
1656
2279
|
}
|
|
1657
2280
|
for (const entry of entries) {
|
|
1658
|
-
const full =
|
|
2281
|
+
const full = join8(current, entry.name);
|
|
1659
2282
|
const rel = relative3(root, full).split(sep2).join("/");
|
|
1660
2283
|
if (shouldSkip(rel)) continue;
|
|
1661
2284
|
if (entry.isSymbolicLink()) continue;
|
|
@@ -1666,7 +2289,7 @@ function walk(root, current, shouldSkip, out) {
|
|
|
1666
2289
|
if (!entry.isFile()) continue;
|
|
1667
2290
|
if (!entry.name.endsWith(".sm")) continue;
|
|
1668
2291
|
const expectedMd = `${full.slice(0, -".sm".length)}.md`;
|
|
1669
|
-
if (
|
|
2292
|
+
if (existsSync8(expectedMd) && safeIsFile(expectedMd)) continue;
|
|
1670
2293
|
out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
|
|
1671
2294
|
}
|
|
1672
2295
|
}
|
|
@@ -1679,51 +2302,15 @@ function safeIsFile(path) {
|
|
|
1679
2302
|
}
|
|
1680
2303
|
|
|
1681
2304
|
// kernel/sidecar/store.ts
|
|
1682
|
-
import { existsSync as
|
|
2305
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1683
2306
|
import { dirname as dirname6, resolve as resolve9 } from "path";
|
|
1684
2307
|
import { createRequire as createRequire4 } from "module";
|
|
1685
2308
|
import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
|
|
1686
2309
|
import yaml3 from "js-yaml";
|
|
1687
2310
|
|
|
1688
|
-
// core/config/atomic-write.ts
|
|
1689
|
-
import {
|
|
1690
|
-
closeSync,
|
|
1691
|
-
constants as fsConstants,
|
|
1692
|
-
existsSync as existsSync6,
|
|
1693
|
-
mkdirSync,
|
|
1694
|
-
openSync,
|
|
1695
|
-
readFileSync as readFileSync7,
|
|
1696
|
-
renameSync,
|
|
1697
|
-
unlinkSync,
|
|
1698
|
-
writeSync
|
|
1699
|
-
} from "fs";
|
|
1700
|
-
import { randomBytes } from "crypto";
|
|
1701
|
-
import { dirname as dirname5 } from "path";
|
|
1702
|
-
|
|
1703
|
-
// core/config/helper.ts
|
|
1704
|
-
import { homedir as osHomedir } from "os";
|
|
1705
|
-
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve8 } from "path";
|
|
1706
|
-
|
|
1707
|
-
// kernel/config/loader.ts
|
|
1708
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
1709
|
-
|
|
1710
|
-
// kernel/util/skill-map-paths.ts
|
|
1711
|
-
import { join as join6 } from "path";
|
|
1712
|
-
|
|
1713
|
-
// core/paths/db-path.ts
|
|
1714
|
-
import { join as join5, resolve as resolve7 } from "path";
|
|
1715
|
-
var SKILL_MAP_DIR = ".skill-map";
|
|
1716
|
-
var DB_FILENAME = "skill-map.db";
|
|
1717
|
-
var LOCAL_SETTINGS_FILENAME = "settings.local.json";
|
|
1718
|
-
var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
|
|
1719
|
-
var GITIGNORE_ENTRIES = [
|
|
1720
|
-
`${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
|
|
1721
|
-
`${SKILL_MAP_DIR}/${DB_FILENAME}`
|
|
1722
|
-
];
|
|
1723
|
-
|
|
1724
2311
|
// kernel/orchestrator/node-build.ts
|
|
1725
2312
|
import { createHash } from "crypto";
|
|
1726
|
-
import { existsSync as
|
|
2313
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1727
2314
|
import { isAbsolute as isAbsolute3, resolve as resolvePath } from "path";
|
|
1728
2315
|
import "js-tiktoken/lite";
|
|
1729
2316
|
import yaml4 from "js-yaml";
|
|
@@ -1887,11 +2474,11 @@ function resolveSidecarOverlay(relativePath, nodePathForIssue, roots, liveBodyHa
|
|
|
1887
2474
|
}
|
|
1888
2475
|
function resolveAbsoluteMdPath(relativePath, roots) {
|
|
1889
2476
|
if (isAbsolute3(relativePath)) {
|
|
1890
|
-
return
|
|
2477
|
+
return existsSync10(relativePath) ? relativePath : null;
|
|
1891
2478
|
}
|
|
1892
2479
|
for (const root of roots) {
|
|
1893
2480
|
const candidate = resolvePath(root, relativePath);
|
|
1894
|
-
if (
|
|
2481
|
+
if (existsSync10(candidate)) return candidate;
|
|
1895
2482
|
}
|
|
1896
2483
|
return null;
|
|
1897
2484
|
}
|
|
@@ -2043,6 +2630,8 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
|
|
|
2043
2630
|
const cacheDecision = computeCacheDecision({
|
|
2044
2631
|
extractors: wctx.opts.extractors,
|
|
2045
2632
|
kind,
|
|
2633
|
+
provider: provider.id,
|
|
2634
|
+
activeProvider: wctx.opts.activeProvider,
|
|
2046
2635
|
nodePath: raw.path,
|
|
2047
2636
|
bodyHash,
|
|
2048
2637
|
sidecarAnnotationsHash,
|
|
@@ -2145,6 +2734,10 @@ function mergeExtractResult(extractResult, accum) {
|
|
|
2145
2734
|
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
2146
2735
|
}
|
|
2147
2736
|
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
2737
|
+
for (const vn of extractResult.virtualNodes) {
|
|
2738
|
+
if (accum.nodes.some((n) => n.path === vn.path)) continue;
|
|
2739
|
+
accum.nodes.push(vn);
|
|
2740
|
+
}
|
|
2148
2741
|
}
|
|
2149
2742
|
function isPartialCacheHit(ctx) {
|
|
2150
2743
|
return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;
|
|
@@ -2252,9 +2845,10 @@ async function runScanInternal(_kernel, options) {
|
|
|
2252
2845
|
priorIndex: setup.priorIndex,
|
|
2253
2846
|
priorExtractorRuns: setup.priorExtractorRuns,
|
|
2254
2847
|
providerFrontmatter: setup.providerFrontmatter,
|
|
2255
|
-
pluginStores: options.pluginStores
|
|
2848
|
+
pluginStores: options.pluginStores,
|
|
2849
|
+
activeProvider: resolveActiveProviderOption(options.activeProvider, options.roots)
|
|
2256
2850
|
});
|
|
2257
|
-
walked.internalLinks =
|
|
2851
|
+
walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes);
|
|
2258
2852
|
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
2259
2853
|
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
2260
2854
|
await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
|
|
@@ -2370,17 +2964,27 @@ function validateRoots(roots) {
|
|
|
2370
2964
|
throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
|
|
2371
2965
|
}
|
|
2372
2966
|
for (const root of roots) {
|
|
2373
|
-
if (!
|
|
2967
|
+
if (!existsSync11(root) || !statSync4(root).isDirectory()) {
|
|
2374
2968
|
throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
|
|
2375
2969
|
}
|
|
2376
2970
|
}
|
|
2377
2971
|
}
|
|
2972
|
+
function resolveActiveProviderOption(optionValue, roots) {
|
|
2973
|
+
if (optionValue !== void 0) return optionValue;
|
|
2974
|
+
for (const root of roots) {
|
|
2975
|
+
const absRoot = isAbsolute4(root) ? root : resolve10(root);
|
|
2976
|
+
if (!existsSync11(absRoot)) continue;
|
|
2977
|
+
const detected = resolveActiveProvider(absRoot).resolved;
|
|
2978
|
+
if (detected !== null) return detected;
|
|
2979
|
+
}
|
|
2980
|
+
return null;
|
|
2981
|
+
}
|
|
2378
2982
|
|
|
2379
2983
|
// kernel/scan/watcher.ts
|
|
2380
|
-
import { resolve as
|
|
2984
|
+
import { resolve as resolve11, relative as relative4, sep as sep3 } from "path";
|
|
2381
2985
|
import chokidar from "chokidar";
|
|
2382
2986
|
function createChokidarWatcher(opts) {
|
|
2383
|
-
const absRoots = opts.roots.map((r) =>
|
|
2987
|
+
const absRoots = opts.roots.map((r) => resolve11(opts.cwd, r));
|
|
2384
2988
|
const ignoreFilterOpt = opts.ignoreFilter;
|
|
2385
2989
|
const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
|
|
2386
2990
|
const ignored = getFilter ? (path) => {
|