enigma-cli 1.59.0 → 1.60.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/assets/commands/handoff.md +48 -0
- package/assets/mods/enigma-relay/.claude-plugin/plugin.json +1 -0
- package/assets/mods/enigma-relay/hooks/hooks.json +1 -0
- package/assets/mods/enigma-relay/hooks/register.ts +86 -0
- package/assets/mods/enigma-relay/tests/relay.test.ts +65 -0
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/skill.json +1 -1
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/dist/ci-watch.js +2 -0
- package/dist/codegraph-hook.js +576 -48
- package/dist/post-edit.js +576 -48
- package/package.json +1 -1
package/dist/codegraph-hook.js
CHANGED
|
@@ -304,6 +304,8 @@ var init_config = __esm({
|
|
|
304
304
|
gateTrivialLines: 20,
|
|
305
305
|
subagentLimit: 4,
|
|
306
306
|
compactWindow: 4e5,
|
|
307
|
+
relay: true,
|
|
308
|
+
relayAt: 3e5,
|
|
307
309
|
planSessionLimit: 0,
|
|
308
310
|
planWeeklyLimit: 0,
|
|
309
311
|
planWeeklySonnetLimit: 0,
|
|
@@ -545,10 +547,222 @@ var init_codegraph_rank = __esm({
|
|
|
545
547
|
}
|
|
546
548
|
});
|
|
547
549
|
|
|
550
|
+
// src/codegraph-extract-docs.ts
|
|
551
|
+
function cap(text) {
|
|
552
|
+
const t = text.trim();
|
|
553
|
+
return t.length > MAX_SIGNATURE ? `${t.slice(0, MAX_SIGNATURE)}...` : t;
|
|
554
|
+
}
|
|
555
|
+
function indentOf(line) {
|
|
556
|
+
let n = 0;
|
|
557
|
+
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
558
|
+
return n;
|
|
559
|
+
}
|
|
560
|
+
function fencedLines(lines) {
|
|
561
|
+
const inside = new Uint8Array(lines.length);
|
|
562
|
+
let fence = null;
|
|
563
|
+
for (let i = 0; i < lines.length; i++) {
|
|
564
|
+
const m = /^\s*(`{3,}|~{3,})/.exec(lines[i]);
|
|
565
|
+
if (fence) {
|
|
566
|
+
inside[i] = 1;
|
|
567
|
+
if (m && m[1][0] === fence[0] && m[1].length >= fence.length) fence = null;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (m) {
|
|
571
|
+
fence = m[1];
|
|
572
|
+
inside[i] = 1;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return inside;
|
|
576
|
+
}
|
|
577
|
+
function markdownSymbols(lines) {
|
|
578
|
+
const fenced = fencedLines(lines);
|
|
579
|
+
const heads = [];
|
|
580
|
+
for (let i = 0; i < lines.length; i++) {
|
|
581
|
+
if (fenced[i]) continue;
|
|
582
|
+
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(lines[i]);
|
|
583
|
+
if (m && m[1].length <= MAX_HEADING_LEVEL) heads.push({ level: m[1].length, name: m[2].replace(/[`*_]/g, "").trim(), line: i + 1 });
|
|
584
|
+
}
|
|
585
|
+
return heads.filter((h) => h.name).map((h, idx) => {
|
|
586
|
+
const next = heads.slice(idx + 1).find((o) => o.level <= h.level);
|
|
587
|
+
return { name: h.name, kind: "section", line: h.line, endLine: next ? next.line - 1 : lines.length, signature: cap(lines[h.line - 1]) };
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function sqlEnd(lines, start) {
|
|
591
|
+
let dollar = false;
|
|
592
|
+
for (let i = start; i < lines.length; i++) {
|
|
593
|
+
const parts = lines[i].replace(/--.*$/, "").split("$$");
|
|
594
|
+
for (let p = 0; p < parts.length; p++) {
|
|
595
|
+
if (p > 0) dollar = !dollar;
|
|
596
|
+
if (!dollar && parts[p].includes(";")) return i + 1;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return lines.length;
|
|
600
|
+
}
|
|
601
|
+
function sqlSymbols(lines) {
|
|
602
|
+
const rules = [
|
|
603
|
+
["table", new RegExp(String.raw`^\s*create\s+(?:or\s+replace\s+)?(?:(?:global|local)\s+)?(?:temp(?:orary)?\s+|unlogged\s+)?(?:materialized\s+)?(?:table|view)\s+(?:if\s+not\s+exists\s+)?${SQL_NAME}`, "i")],
|
|
604
|
+
["function", new RegExp(String.raw`^\s*create\s+(?:or\s+replace\s+)?(?:function|procedure)\s+${SQL_NAME}`, "i")],
|
|
605
|
+
["type", new RegExp(String.raw`^\s*create\s+type\s+${SQL_NAME}`, "i")]
|
|
606
|
+
];
|
|
607
|
+
const out = [];
|
|
608
|
+
for (let i = 0; i < lines.length; i++) {
|
|
609
|
+
for (const [kind, re] of rules) {
|
|
610
|
+
const m = re.exec(lines[i]);
|
|
611
|
+
if (!m) continue;
|
|
612
|
+
out.push({ name: m[1], kind, line: i + 1, endLine: sqlEnd(lines, i), signature: cap(lines[i].replace(/\(.*$/, "")) });
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return out;
|
|
617
|
+
}
|
|
618
|
+
function prismaSymbols(lines) {
|
|
619
|
+
const out = [];
|
|
620
|
+
for (let i = 0; i < lines.length; i++) {
|
|
621
|
+
const m = /^\s*(model|view|enum|type)\s+([A-Za-z_]\w*)\s*\{/.exec(lines[i]);
|
|
622
|
+
if (!m) continue;
|
|
623
|
+
let end = i;
|
|
624
|
+
while (end < lines.length - 1 && !/^\s*\}/.test(lines[end])) end++;
|
|
625
|
+
const kind = m[1] === "enum" ? "enum" : m[1] === "type" ? "type" : "table";
|
|
626
|
+
out.push({ name: m[2], kind, line: i + 1, endLine: end + 1, signature: cap(`${m[1]} ${m[2]}`) });
|
|
627
|
+
}
|
|
628
|
+
return out;
|
|
629
|
+
}
|
|
630
|
+
function yamlEnd(lines, start) {
|
|
631
|
+
const base = indentOf(lines[start]);
|
|
632
|
+
let end = start + 1;
|
|
633
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
634
|
+
const line = lines[i];
|
|
635
|
+
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
|
636
|
+
if (indentOf(line) <= base && !line.trimStart().startsWith("- ")) break;
|
|
637
|
+
end = i + 1;
|
|
638
|
+
}
|
|
639
|
+
return end;
|
|
640
|
+
}
|
|
641
|
+
function yamlSymbols(lines) {
|
|
642
|
+
const out = [];
|
|
643
|
+
const KEY = /^(\s*)([A-Za-z0-9_][\w.-]*)\s*:(?:\s|$)/;
|
|
644
|
+
for (let i = 0; i < lines.length; i++) {
|
|
645
|
+
const m = KEY.exec(lines[i]);
|
|
646
|
+
if (!m || m[1].length) continue;
|
|
647
|
+
const end = yamlEnd(lines, i);
|
|
648
|
+
out.push({ name: m[2], kind: "key", line: i + 1, endLine: end, signature: cap(lines[i]) });
|
|
649
|
+
if (!YAML_CONTAINERS.has(m[2])) continue;
|
|
650
|
+
let childIndent = -1;
|
|
651
|
+
for (let j = i + 1; j < end; j++) {
|
|
652
|
+
const c = KEY.exec(lines[j]);
|
|
653
|
+
if (!c || !c[1].length) continue;
|
|
654
|
+
if (childIndent === -1) childIndent = c[1].length;
|
|
655
|
+
if (c[1].length !== childIndent) continue;
|
|
656
|
+
out.push({ name: c[2], kind: "key", line: j + 1, endLine: yamlEnd(lines, j), signature: cap(`${m[2]}.${c[2]}`) });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
function tomlSymbols(lines) {
|
|
662
|
+
const heads = [];
|
|
663
|
+
for (let i = 0; i < lines.length; i++) {
|
|
664
|
+
const m = /^\s*\[\[?\s*([^\]]+?)\s*\]\]?\s*(?:#.*)?$/.exec(lines[i]);
|
|
665
|
+
if (m) heads.push({ name: m[1], line: i + 1 });
|
|
666
|
+
}
|
|
667
|
+
return heads.map((h, idx) => ({
|
|
668
|
+
name: h.name,
|
|
669
|
+
kind: "key",
|
|
670
|
+
line: h.line,
|
|
671
|
+
endLine: idx + 1 < heads.length ? heads[idx + 1].line - 1 : lines.length,
|
|
672
|
+
signature: cap(lines[h.line - 1])
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
function markdownLinks(lines) {
|
|
676
|
+
const fenced = fencedLines(lines);
|
|
677
|
+
const out = /* @__PURE__ */ new Set();
|
|
678
|
+
for (let i = 0; i < lines.length; i++) {
|
|
679
|
+
if (fenced[i]) continue;
|
|
680
|
+
const re = /\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g;
|
|
681
|
+
let m;
|
|
682
|
+
while (m = re.exec(lines[i])) {
|
|
683
|
+
const target = m[1].replace(/[#?].*$/, "");
|
|
684
|
+
if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//")) continue;
|
|
685
|
+
try {
|
|
686
|
+
out.add(decodeURI(target));
|
|
687
|
+
} catch {
|
|
688
|
+
out.add(target);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return [...out];
|
|
693
|
+
}
|
|
694
|
+
function markdownCodeMentions(content) {
|
|
695
|
+
const lines = content.split("\n");
|
|
696
|
+
const fenced = fencedLines(lines);
|
|
697
|
+
const out = [];
|
|
698
|
+
for (let i = 0; i < lines.length; i++) {
|
|
699
|
+
if (fenced[i]) continue;
|
|
700
|
+
const re = /`([A-Za-z_$][\w$]*)(?:\(\))?`/g;
|
|
701
|
+
let m;
|
|
702
|
+
while (m = re.exec(lines[i])) out.push({ name: m[1], line: i + 1 });
|
|
703
|
+
}
|
|
704
|
+
return out;
|
|
705
|
+
}
|
|
706
|
+
function extractDocFile(lang, content) {
|
|
707
|
+
const lines = content.split("\n");
|
|
708
|
+
const out = (() => {
|
|
709
|
+
switch (lang) {
|
|
710
|
+
case "markdown":
|
|
711
|
+
return { symbols: markdownSymbols(lines), imports: markdownLinks(lines) };
|
|
712
|
+
case "sql":
|
|
713
|
+
return { symbols: sqlSymbols(lines), imports: [] };
|
|
714
|
+
case "prisma":
|
|
715
|
+
return { symbols: prismaSymbols(lines), imports: [] };
|
|
716
|
+
case "yaml":
|
|
717
|
+
return { symbols: yamlSymbols(lines), imports: [] };
|
|
718
|
+
case "toml":
|
|
719
|
+
return { symbols: tomlSymbols(lines), imports: [] };
|
|
720
|
+
default:
|
|
721
|
+
return { symbols: [], imports: [] };
|
|
722
|
+
}
|
|
723
|
+
})();
|
|
724
|
+
for (const s of out.symbols) while (s.endLine > s.line && !(lines[s.endLine - 1] ?? "").trim()) s.endLine--;
|
|
725
|
+
return out;
|
|
726
|
+
}
|
|
727
|
+
var DOC_LANG_BY_EXT, DOC_LANGS, DOC_REFERENCE_LANGS, YAML_CONTAINERS, MAX_SIGNATURE, MAX_HEADING_LEVEL, SQL_NAME;
|
|
728
|
+
var init_codegraph_extract_docs = __esm({
|
|
729
|
+
"src/codegraph-extract-docs.ts"() {
|
|
730
|
+
"use strict";
|
|
731
|
+
DOC_LANG_BY_EXT = {
|
|
732
|
+
".md": "markdown",
|
|
733
|
+
".mdx": "markdown",
|
|
734
|
+
".sql": "sql",
|
|
735
|
+
".prisma": "prisma",
|
|
736
|
+
".yaml": "yaml",
|
|
737
|
+
".yml": "yaml",
|
|
738
|
+
".toml": "toml"
|
|
739
|
+
};
|
|
740
|
+
DOC_LANGS = new Set(Object.values(DOC_LANG_BY_EXT));
|
|
741
|
+
DOC_REFERENCE_LANGS = /* @__PURE__ */ new Set(["sql", "prisma"]);
|
|
742
|
+
YAML_CONTAINERS = /* @__PURE__ */ new Set(["services", "jobs", "workflows", "volumes", "networks"]);
|
|
743
|
+
MAX_SIGNATURE = 200;
|
|
744
|
+
MAX_HEADING_LEVEL = 3;
|
|
745
|
+
SQL_NAME = String.raw`(?:[\w"\x60\[\]]+\.)?["\x60\[]?([A-Za-z_][\w$]*)["\x60\]]?`;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
|
|
548
749
|
// src/codegraph-extract.ts
|
|
549
750
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
550
751
|
import { extname, join as join4, relative as relative2, resolve as resolve2 } from "path";
|
|
551
752
|
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
753
|
+
function isCodeLang(lang) {
|
|
754
|
+
return !DOC_LANGS.has(lang);
|
|
755
|
+
}
|
|
756
|
+
function isDocPath(path) {
|
|
757
|
+
return !!DOC_LANG_BY_EXT[extname(path).toLowerCase()];
|
|
758
|
+
}
|
|
759
|
+
function isForeignPath(rel) {
|
|
760
|
+
return FOREIGN_DOC_DIRS.test(rel);
|
|
761
|
+
}
|
|
762
|
+
function pickDocs(root, docs, room) {
|
|
763
|
+
const depth = (p) => relPath(root, p).split("/").length;
|
|
764
|
+
return docs.filter((p) => !FOREIGN_DOC_DIRS.test(relPath(root, p))).sort((a, b) => depth(a) - depth(b) || a.localeCompare(b)).slice(0, Math.max(0, Math.min(MAX_DOC_FILES, room)));
|
|
765
|
+
}
|
|
552
766
|
function symRules(lang) {
|
|
553
767
|
switch (lang) {
|
|
554
768
|
case "ts":
|
|
@@ -713,7 +927,7 @@ function lineResolver(content) {
|
|
|
713
927
|
return lo + 1;
|
|
714
928
|
};
|
|
715
929
|
}
|
|
716
|
-
function
|
|
930
|
+
function indentOf2(line) {
|
|
717
931
|
let n = 0;
|
|
718
932
|
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
719
933
|
return n;
|
|
@@ -779,18 +993,18 @@ function braceEnd(lines, startIdx, lang) {
|
|
|
779
993
|
return lines.length;
|
|
780
994
|
}
|
|
781
995
|
function indentEnd(lines, startIdx) {
|
|
782
|
-
const base =
|
|
996
|
+
const base = indentOf2(lines[startIdx]);
|
|
783
997
|
let end = startIdx + 1;
|
|
784
998
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
785
999
|
const line = lines[i];
|
|
786
1000
|
if (!line.trim()) continue;
|
|
787
|
-
if (
|
|
1001
|
+
if (indentOf2(line) <= base) break;
|
|
788
1002
|
end = i + 1;
|
|
789
1003
|
}
|
|
790
1004
|
return end;
|
|
791
1005
|
}
|
|
792
1006
|
function clampToSiblings(symbols, lines) {
|
|
793
|
-
const indentAt = (line) =>
|
|
1007
|
+
const indentAt = (line) => indentOf2(lines[line - 1] ?? "");
|
|
794
1008
|
for (let i = 0; i < symbols.length; i++) {
|
|
795
1009
|
const s = symbols[i];
|
|
796
1010
|
for (let j = i + 1; j < symbols.length; j++) {
|
|
@@ -803,14 +1017,15 @@ function clampToSiblings(symbols, lines) {
|
|
|
803
1017
|
}
|
|
804
1018
|
}
|
|
805
1019
|
function signatureOf(line) {
|
|
806
|
-
const
|
|
1020
|
+
const MAX_SIGNATURE2 = 200;
|
|
807
1021
|
let sig = line.trim();
|
|
808
1022
|
const brace = sig.indexOf("{");
|
|
809
1023
|
if (brace > 0) sig = sig.slice(0, brace).trim();
|
|
810
1024
|
if (sig.endsWith(":")) sig = sig.slice(0, -1).trim();
|
|
811
|
-
return sig.length >
|
|
1025
|
+
return sig.length > MAX_SIGNATURE2 ? `${sig.slice(0, MAX_SIGNATURE2)}...` : sig;
|
|
812
1026
|
}
|
|
813
1027
|
function extractFile(lang, content) {
|
|
1028
|
+
if (DOC_LANGS.has(lang)) return extractDocFile(lang, content);
|
|
814
1029
|
const lineAt = lineResolver(content);
|
|
815
1030
|
const lines = content.split("\n");
|
|
816
1031
|
const braced = BRACE_LANGS.has(lang);
|
|
@@ -852,25 +1067,27 @@ function gitSourceFiles(root) {
|
|
|
852
1067
|
const out = gitList(root, ["--cached", "--others", "--exclude-standard"]);
|
|
853
1068
|
if (out === null) return null;
|
|
854
1069
|
const nested = existsSync4(join4(root, ".gitmodules")) ? gitList(root, ["--recurse-submodules"]) : null;
|
|
855
|
-
const
|
|
1070
|
+
const code = [];
|
|
1071
|
+
const docs = [];
|
|
856
1072
|
const seen = /* @__PURE__ */ new Set();
|
|
857
1073
|
for (const rel of [...out.split("\0"), ...(nested ?? "").split("\0")]) {
|
|
858
|
-
if (
|
|
1074
|
+
if (code.length >= MAX_FILES) break;
|
|
859
1075
|
if (!rel || seen.has(rel)) continue;
|
|
860
1076
|
seen.add(rel);
|
|
861
1077
|
if (!LANG_BY_EXT[extname(rel).toLowerCase()]) continue;
|
|
862
1078
|
if (rel.split("/").some((seg) => seg.startsWith(".") || IGNORE_DIRS.has(seg))) continue;
|
|
863
|
-
|
|
1079
|
+
(isDocPath(rel) ? docs : code).push(join4(root, rel));
|
|
864
1080
|
}
|
|
865
|
-
return
|
|
1081
|
+
return [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
866
1082
|
}
|
|
867
1083
|
function walk(root) {
|
|
868
1084
|
const tracked = gitSourceFiles(root);
|
|
869
1085
|
if (tracked && tracked.length) return { files: tracked, truncated: tracked.length >= MAX_FILES };
|
|
870
|
-
const
|
|
1086
|
+
const code = [];
|
|
1087
|
+
const docs = [];
|
|
871
1088
|
const queue = [root];
|
|
872
1089
|
let visited = 0;
|
|
873
|
-
for (let head = 0; head < queue.length &&
|
|
1090
|
+
for (let head = 0; head < queue.length && code.length < MAX_FILES && visited < MAX_ENTRIES; head++) {
|
|
874
1091
|
let entries;
|
|
875
1092
|
try {
|
|
876
1093
|
entries = readdirSync2(queue[head], { withFileTypes: true });
|
|
@@ -878,16 +1095,17 @@ function walk(root) {
|
|
|
878
1095
|
continue;
|
|
879
1096
|
}
|
|
880
1097
|
for (const e of entries) {
|
|
881
|
-
if (
|
|
1098
|
+
if (code.length >= MAX_FILES || ++visited >= MAX_ENTRIES) break;
|
|
882
1099
|
if (e.isDirectory()) {
|
|
883
1100
|
if (!e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) queue.push(join4(queue[head], e.name));
|
|
884
1101
|
continue;
|
|
885
1102
|
}
|
|
886
|
-
if (!e.isFile()) continue;
|
|
887
|
-
|
|
1103
|
+
if (!e.isFile() || !LANG_BY_EXT[extname(e.name).toLowerCase()]) continue;
|
|
1104
|
+
(isDocPath(e.name) ? docs : code).push(join4(queue[head], e.name));
|
|
888
1105
|
}
|
|
889
1106
|
}
|
|
890
|
-
|
|
1107
|
+
const ordered = [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
1108
|
+
return { files: ordered, truncated: code.length >= MAX_FILES || visited >= MAX_ENTRIES };
|
|
891
1109
|
}
|
|
892
1110
|
function relPath(root, abs) {
|
|
893
1111
|
return relative2(root, abs).split("\\").join("/");
|
|
@@ -945,10 +1163,11 @@ function statSourceFiles(root) {
|
|
|
945
1163
|
}
|
|
946
1164
|
return out;
|
|
947
1165
|
}
|
|
948
|
-
var IGNORE_DIRS, LANG_BY_EXT, MAX_FILES, MAX_FILE_BYTES, BRACE_LANGS, HASH_COMMENT_LANGS, PREPROCESSOR_LANGS, MAX_ENTRIES;
|
|
1166
|
+
var IGNORE_DIRS, CODE_LANG_BY_EXT, LANG_BY_EXT, FOREIGN_DOC_DIRS, MAX_DOC_FILES, MAX_FILES, MAX_FILE_BYTES, BRACE_LANGS, HASH_COMMENT_LANGS, PREPROCESSOR_LANGS, MAX_ENTRIES;
|
|
949
1167
|
var init_codegraph_extract = __esm({
|
|
950
1168
|
"src/codegraph-extract.ts"() {
|
|
951
1169
|
"use strict";
|
|
1170
|
+
init_codegraph_extract_docs();
|
|
952
1171
|
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
953
1172
|
"node_modules",
|
|
954
1173
|
".git",
|
|
@@ -973,7 +1192,7 @@ var init_codegraph_extract = __esm({
|
|
|
973
1192
|
".gradle",
|
|
974
1193
|
".dart_tool"
|
|
975
1194
|
]);
|
|
976
|
-
|
|
1195
|
+
CODE_LANG_BY_EXT = {
|
|
977
1196
|
".ts": "ts",
|
|
978
1197
|
".tsx": "ts",
|
|
979
1198
|
".mts": "ts",
|
|
@@ -999,6 +1218,9 @@ var init_codegraph_extract = __esm({
|
|
|
999
1218
|
".hpp": "cpp",
|
|
1000
1219
|
".hh": "cpp"
|
|
1001
1220
|
};
|
|
1221
|
+
LANG_BY_EXT = { ...CODE_LANG_BY_EXT, ...DOC_LANG_BY_EXT };
|
|
1222
|
+
FOREIGN_DOC_DIRS = /(?:^|\/)(?:references?|examples?|samples?|fixtures?|third[_-]party|vendor(?:ed)?)\//i;
|
|
1223
|
+
MAX_DOC_FILES = 1500;
|
|
1002
1224
|
MAX_FILES = 8e3;
|
|
1003
1225
|
MAX_FILE_BYTES = 512 * 1024;
|
|
1004
1226
|
BRACE_LANGS = /* @__PURE__ */ new Set(["ts", "js", "go", "rust", "java", "csharp", "kotlin", "php", "c", "cpp"]);
|
|
@@ -1214,12 +1436,12 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1214
1436
|
}
|
|
1215
1437
|
const edges = [];
|
|
1216
1438
|
const seen = /* @__PURE__ */ new Set();
|
|
1217
|
-
const push = (from, to, rel) => {
|
|
1439
|
+
const push = (from, to, rel, inferred = false) => {
|
|
1218
1440
|
if (edges.length >= MAX_EDGES || from === to) return;
|
|
1219
1441
|
const key = `${from}|${to}|${rel}`;
|
|
1220
1442
|
if (seen.has(key)) return;
|
|
1221
1443
|
seen.add(key);
|
|
1222
|
-
edges.push([from, to, rel]);
|
|
1444
|
+
edges.push(inferred ? [from, to, rel, 1] : [from, to, rel]);
|
|
1223
1445
|
};
|
|
1224
1446
|
for (const f of files) {
|
|
1225
1447
|
for (const span of spansByFile.get(f.path) ?? []) push(f.path, span.id, "contains");
|
|
@@ -1248,12 +1470,14 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1248
1470
|
const candidates = defsByName.get(name)?.filter((c) => imported?.has(c.path) && reachable(c.path));
|
|
1249
1471
|
return candidates?.length === 1 ? candidates[0].id : null;
|
|
1250
1472
|
};
|
|
1473
|
+
const extracted = (id) => id ? { id, inferred: false } : null;
|
|
1474
|
+
const inferred = (id) => id ? { id, inferred: true } : null;
|
|
1251
1475
|
if (!f.bindings) {
|
|
1252
1476
|
return (name) => {
|
|
1253
|
-
if (local.has(name)) return local.get(name)
|
|
1254
|
-
if (imported?.size) return unique(name);
|
|
1477
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
1478
|
+
if (imported?.size) return inferred(unique(name));
|
|
1255
1479
|
const all = defsByName.get(name)?.filter((c) => reachable(c.path));
|
|
1256
|
-
return all?.length === 1 ? all[0].id : null;
|
|
1480
|
+
return all?.length === 1 ? inferred(all[0].id) : null;
|
|
1257
1481
|
};
|
|
1258
1482
|
}
|
|
1259
1483
|
const bound = new Set(f.bindings.names);
|
|
@@ -1265,10 +1489,10 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1265
1489
|
return (name, memberOf) => {
|
|
1266
1490
|
if (memberOf !== void 0) {
|
|
1267
1491
|
const path = namespaces.get(memberOf);
|
|
1268
|
-
return path ? idByPathName.get(`${path}#${name}`)
|
|
1492
|
+
return path ? extracted(idByPathName.get(`${path}#${name}`)) : null;
|
|
1269
1493
|
}
|
|
1270
|
-
if (local.has(name)) return local.get(name)
|
|
1271
|
-
return bound.has(name) ? unique(name) : null;
|
|
1494
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
1495
|
+
return bound.has(name) ? extracted(unique(name)) : null;
|
|
1272
1496
|
};
|
|
1273
1497
|
}
|
|
1274
1498
|
const bodies = [];
|
|
@@ -1282,7 +1506,7 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1282
1506
|
const spans = spansByFile.get(f.path) ?? [];
|
|
1283
1507
|
const declLines = new Set(f.symbols.map((s) => s.line));
|
|
1284
1508
|
const lines = content.split("\n");
|
|
1285
|
-
indexBodies(bodies, f.path, spans, lines);
|
|
1509
|
+
indexBodies(bodies, f.path, spans, lines, DOC_LANGS.has(f.lang) ? MAX_DOC_BODY_TOKENS : MAX_BODY_TOKENS);
|
|
1286
1510
|
const bind = binderFor(f);
|
|
1287
1511
|
const strict = !!f.bindings;
|
|
1288
1512
|
for (const s of f.symbols) {
|
|
@@ -1291,9 +1515,17 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1291
1515
|
if (!self) continue;
|
|
1292
1516
|
for (const [rel, name] of inheritanceTargets(s.signature)) {
|
|
1293
1517
|
const target = bind(name);
|
|
1294
|
-
if (target) push(self.id, target, rel);
|
|
1518
|
+
if (target) push(self.id, target.id, rel, target.inferred);
|
|
1295
1519
|
}
|
|
1296
1520
|
}
|
|
1521
|
+
if (f.lang === "markdown") {
|
|
1522
|
+
for (const { name, line } of markdownCodeMentions(content)) {
|
|
1523
|
+
if (name.length < MIN_DOC_MENTION) continue;
|
|
1524
|
+
const code = defsByName.get(name)?.filter((c) => !DOC_LANGS.has(langByPath.get(c.path) ?? ""));
|
|
1525
|
+
if (code?.length === 1) push(enclosingSymbol(spans, line) ?? f.path, code[0].id, "references", true);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (DOC_LANGS.has(f.lang) && !DOC_REFERENCE_LANGS.has(f.lang)) continue;
|
|
1297
1529
|
for (let i = 0; i < lines.length; i++) {
|
|
1298
1530
|
const lineNo = i + 1;
|
|
1299
1531
|
if (declLines.has(lineNo)) continue;
|
|
@@ -1306,31 +1538,32 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1306
1538
|
if (name.length < MIN_REF_NAME || !defsByName.has(name)) continue;
|
|
1307
1539
|
const memberOf = strict ? /([A-Za-z_$][\w$]*)\s*\.\s*$/.exec(line.slice(0, m.index))?.[1] : void 0;
|
|
1308
1540
|
const target = bind(name, memberOf);
|
|
1309
|
-
if (!target || target === from) continue;
|
|
1541
|
+
if (!target || target.id === from) continue;
|
|
1310
1542
|
const after = line.slice(m.index + name.length);
|
|
1311
|
-
|
|
1543
|
+
const calls = !DOC_LANGS.has(f.lang) && /^\s*\(/.test(after);
|
|
1544
|
+
push(from, target.id, calls ? "calls" : "references", target.inferred);
|
|
1312
1545
|
}
|
|
1313
1546
|
}
|
|
1314
1547
|
}
|
|
1315
1548
|
return { edges, bodies };
|
|
1316
1549
|
}
|
|
1317
|
-
function indexBodies(out, path, spans, lines) {
|
|
1550
|
+
function indexBodies(out, path, spans, lines, limit) {
|
|
1318
1551
|
const covered = new Uint8Array(lines.length + 1);
|
|
1319
1552
|
for (const s of spans) {
|
|
1320
1553
|
const text = lines.slice(s.line - 1, s.endLine).join("\n");
|
|
1321
1554
|
for (let l = s.line; l <= Math.min(s.endLine, lines.length); l++) covered[l] = 1;
|
|
1322
|
-
const bag = topTokens(text);
|
|
1555
|
+
const bag = topTokens(text, limit);
|
|
1323
1556
|
if (bag.length) out.push([s.id, bag]);
|
|
1324
1557
|
}
|
|
1325
1558
|
const residual = [];
|
|
1326
1559
|
for (let l = 1; l <= lines.length; l++) if (!covered[l]) residual.push(lines[l - 1]);
|
|
1327
|
-
const fileBag = topTokens(residual.join("\n"));
|
|
1560
|
+
const fileBag = topTokens(residual.join("\n"), limit);
|
|
1328
1561
|
if (fileBag.length) out.push([path, fileBag]);
|
|
1329
1562
|
}
|
|
1330
|
-
function topTokens(text) {
|
|
1563
|
+
function topTokens(text, limit) {
|
|
1331
1564
|
const m = /* @__PURE__ */ new Map();
|
|
1332
1565
|
for (const t of tokenize(text)) m.set(t, (m.get(t) ?? 0) + 1);
|
|
1333
|
-
return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0,
|
|
1566
|
+
return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit);
|
|
1334
1567
|
}
|
|
1335
1568
|
function isManagedPath(dir) {
|
|
1336
1569
|
const managed = resolve3(join5(enigmaHome(), ".enigma"));
|
|
@@ -1548,17 +1781,18 @@ function codeGraphAvailable() {
|
|
|
1548
1781
|
function codeGraphStatus() {
|
|
1549
1782
|
return { enabled: readConfig().config.codeGraph, available: true, projects: listProjects().length };
|
|
1550
1783
|
}
|
|
1551
|
-
var GRAPH_VERSION, RESOLVE_EXTS, LANG_EXTS, LANG_INDEX, MIN_REF_NAME, MAX_EDGES, LANG_FAMILY, INDEX_NAMES, MODULE_ROOTS, MODULE_SEPARATOR, BARE_IS_PACKAGE, EXT_ORDER, INDEX_ORDER, MAX_BODY_TOKENS, ROOT_MARKERS, MAX_ROOT_WALK;
|
|
1784
|
+
var GRAPH_VERSION, RESOLVE_EXTS, LANG_EXTS, LANG_INDEX, MIN_REF_NAME, MIN_DOC_MENTION, MAX_EDGES, LANG_FAMILY, INDEX_NAMES, MODULE_ROOTS, MODULE_SEPARATOR, BARE_IS_PACKAGE, EXT_ORDER, INDEX_ORDER, MAX_BODY_TOKENS, MAX_DOC_BODY_TOKENS, ROOT_MARKERS, MAX_ROOT_WALK;
|
|
1552
1785
|
var init_codegraph = __esm({
|
|
1553
1786
|
"src/codegraph.ts"() {
|
|
1554
1787
|
"use strict";
|
|
1555
1788
|
init_util();
|
|
1556
1789
|
init_config();
|
|
1557
1790
|
init_codegraph_rank();
|
|
1791
|
+
init_codegraph_extract_docs();
|
|
1558
1792
|
init_codegraph_extract();
|
|
1559
1793
|
init_codegraph_extract();
|
|
1560
1794
|
init_codegraph_types();
|
|
1561
|
-
GRAPH_VERSION =
|
|
1795
|
+
GRAPH_VERSION = 5;
|
|
1562
1796
|
RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".rb", ".php", ".cs", ".kt"];
|
|
1563
1797
|
LANG_EXTS = {
|
|
1564
1798
|
ts: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
@@ -1574,6 +1808,7 @@ var init_codegraph = __esm({
|
|
|
1574
1808
|
};
|
|
1575
1809
|
LANG_INDEX = { python: ["__init__"], go: ["mod"], rust: ["mod"] };
|
|
1576
1810
|
MIN_REF_NAME = 3;
|
|
1811
|
+
MIN_DOC_MENTION = 4;
|
|
1577
1812
|
MAX_EDGES = 4e5;
|
|
1578
1813
|
LANG_FAMILY = {
|
|
1579
1814
|
ts: "js",
|
|
@@ -1590,6 +1825,7 @@ var init_codegraph = __esm({
|
|
|
1590
1825
|
EXT_ORDER = orders(RESOLVE_EXTS, LANG_EXTS);
|
|
1591
1826
|
INDEX_ORDER = orders(INDEX_NAMES, LANG_INDEX);
|
|
1592
1827
|
MAX_BODY_TOKENS = 80;
|
|
1828
|
+
MAX_DOC_BODY_TOKENS = 24;
|
|
1593
1829
|
ROOT_MARKERS = [".git", "package.json", "go.mod", "pyproject.toml", "Cargo.toml", "pom.xml", "composer.json"];
|
|
1594
1830
|
MAX_ROOT_WALK = 6;
|
|
1595
1831
|
}
|
|
@@ -1822,11 +2058,11 @@ ${result.note}` : structural.fallthrough;
|
|
|
1822
2058
|
}
|
|
1823
2059
|
function adjacency(edges, direction) {
|
|
1824
2060
|
const adj = /* @__PURE__ */ new Map();
|
|
1825
|
-
for (const [source, target, rel] of edges) {
|
|
2061
|
+
for (const [source, target, rel, guess] of edges) {
|
|
1826
2062
|
if (!WALK_RELATIONS.has(rel)) continue;
|
|
1827
2063
|
const key = direction === "in" ? target : source;
|
|
1828
2064
|
const other = direction === "in" ? source : target;
|
|
1829
|
-
const entry = { other, relation: rel };
|
|
2065
|
+
const entry = { other, relation: rel, inferred: guess === 1 };
|
|
1830
2066
|
const arr = adj.get(key);
|
|
1831
2067
|
if (arr) arr.push(entry);
|
|
1832
2068
|
else adj.set(key, [entry]);
|
|
@@ -1855,13 +2091,13 @@ function codeGraphTrace(symbol, opts = {}) {
|
|
|
1855
2091
|
for (let d = 1; d <= depth && frontier.length; d++) {
|
|
1856
2092
|
const next = [];
|
|
1857
2093
|
for (const current of frontier) {
|
|
1858
|
-
for (const { other, relation } of adj.get(current) ?? []) {
|
|
2094
|
+
for (const { other, relation, inferred } of adj.get(current) ?? []) {
|
|
1859
2095
|
if (visited.has(other)) continue;
|
|
1860
2096
|
visited.add(other);
|
|
1861
2097
|
const node = loaded.byId.get(other);
|
|
1862
2098
|
if (!node) continue;
|
|
1863
2099
|
if (inPrefix !== void 0 && !underPrefix(node.path, inPrefix)) continue;
|
|
1864
|
-
hits.push({ id: other, name: node.name, kind: node.kind, path: node.path, line: node.line, relation, depth: d });
|
|
2100
|
+
hits.push({ id: other, name: node.name, kind: node.kind, path: node.path, line: node.line, relation, depth: d, ...inferred ? { inferred } : {} });
|
|
1865
2101
|
next.push(other);
|
|
1866
2102
|
}
|
|
1867
2103
|
}
|
|
@@ -1900,8 +2136,8 @@ function dirKey(path, depth) {
|
|
|
1900
2136
|
const segments = path.split("/");
|
|
1901
2137
|
return segments.slice(0, Math.min(depth, segments.length)).join("/");
|
|
1902
2138
|
}
|
|
1903
|
-
function topHubs(nodes, deg,
|
|
1904
|
-
return nodes.map((n) => ({ name: n.name, kind: n.kind, path: n.path, line: n.line, inDegree: deg.get(n.id) ?? 0 })).filter((h) => h.inDegree > 0).sort((a, b) => b.inDegree - a.inDegree || a.name.localeCompare(b.name) || a.path.localeCompare(b.path)).slice(0,
|
|
2139
|
+
function topHubs(nodes, deg, cap2) {
|
|
2140
|
+
return nodes.map((n) => ({ name: n.name, kind: n.kind, path: n.path, line: n.line, inDegree: deg.get(n.id) ?? 0 })).filter((h) => h.inDegree > 0).sort((a, b) => b.inDegree - a.inDegree || a.name.localeCompare(b.name) || a.path.localeCompare(b.path)).slice(0, cap2);
|
|
1905
2141
|
}
|
|
1906
2142
|
function codeGraphMap(opts = {}) {
|
|
1907
2143
|
const loaded = load(opts);
|
|
@@ -2129,7 +2365,7 @@ ${r.note ?? empty}
|
|
|
2129
2365
|
const lines = [head, ""];
|
|
2130
2366
|
lines.push(`defined at: ${r.matched.map((m) => `${m.path}:${m.line}`).join(", ")}`, "");
|
|
2131
2367
|
for (const h of r.hits) {
|
|
2132
|
-
lines.push(`- ${h.name} - ${h.kind} - ${h.path}:${h.line} - ${h.relation}${r.depth > 1 ? ` - depth ${h.depth}` : ""}`);
|
|
2368
|
+
lines.push(`- ${h.name} - ${h.kind} - ${h.path}:${h.line} - ${h.relation}${h.inferred ? " (inferred)" : ""}${r.depth > 1 ? ` - depth ${h.depth}` : ""}`);
|
|
2133
2369
|
}
|
|
2134
2370
|
lines.push("", `${r.hits.length} node${r.hits.length === 1 ? "" : "s"}.`);
|
|
2135
2371
|
return `${lines.join("\n")}
|
|
@@ -2252,6 +2488,295 @@ var init_codegraph_format = __esm({
|
|
|
2252
2488
|
}
|
|
2253
2489
|
});
|
|
2254
2490
|
|
|
2491
|
+
// src/codegraph-cluster.ts
|
|
2492
|
+
function localMoves(level, totalWeight) {
|
|
2493
|
+
const { nodes, adj, selfLoops } = level;
|
|
2494
|
+
const degree = new Array(nodes).fill(0);
|
|
2495
|
+
for (let i = 0; i < nodes; i++) {
|
|
2496
|
+
let d = selfLoops[i] * 2;
|
|
2497
|
+
for (const w of adj[i].values()) d += w;
|
|
2498
|
+
degree[i] = d;
|
|
2499
|
+
}
|
|
2500
|
+
const comm = Array.from({ length: nodes }, (_, i) => i);
|
|
2501
|
+
const commDegree = degree.slice();
|
|
2502
|
+
const m2 = totalWeight * 2;
|
|
2503
|
+
for (let sweep = 0; sweep < MAX_SWEEPS; sweep++) {
|
|
2504
|
+
let moved = false;
|
|
2505
|
+
for (let i = 0; i < nodes; i++) {
|
|
2506
|
+
const current = comm[i];
|
|
2507
|
+
const toComm = /* @__PURE__ */ new Map();
|
|
2508
|
+
for (const [j, w] of adj[i]) toComm.set(comm[j], (toComm.get(comm[j]) ?? 0) + w);
|
|
2509
|
+
commDegree[current] -= degree[i];
|
|
2510
|
+
let best = current;
|
|
2511
|
+
let bestGain = (toComm.get(current) ?? 0) - commDegree[current] * degree[i] / m2;
|
|
2512
|
+
for (const [c, w] of [...toComm.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2513
|
+
const gain = w - commDegree[c] * degree[i] / m2;
|
|
2514
|
+
if (gain > bestGain + 1e-12) {
|
|
2515
|
+
best = c;
|
|
2516
|
+
bestGain = gain;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
commDegree[best] += degree[i];
|
|
2520
|
+
if (best !== current) {
|
|
2521
|
+
comm[i] = best;
|
|
2522
|
+
moved = true;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
if (!moved) break;
|
|
2526
|
+
}
|
|
2527
|
+
return comm;
|
|
2528
|
+
}
|
|
2529
|
+
function clusterFiles(files, links) {
|
|
2530
|
+
const sorted = [...new Set(files)].sort();
|
|
2531
|
+
const index = new Map(sorted.map((f, i) => [f, i]));
|
|
2532
|
+
let level = { nodes: sorted.length, adj: sorted.map(() => /* @__PURE__ */ new Map()), selfLoops: new Array(sorted.length).fill(0) };
|
|
2533
|
+
let totalWeight = 0;
|
|
2534
|
+
for (const [a, b, w] of links) {
|
|
2535
|
+
const i = index.get(a);
|
|
2536
|
+
const j = index.get(b);
|
|
2537
|
+
if (i === void 0 || j === void 0 || w <= 0) continue;
|
|
2538
|
+
totalWeight += w;
|
|
2539
|
+
if (i === j) {
|
|
2540
|
+
level.selfLoops[i] += w;
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
level.adj[i].set(j, (level.adj[i].get(j) ?? 0) + w);
|
|
2544
|
+
level.adj[j].set(i, (level.adj[j].get(i) ?? 0) + w);
|
|
2545
|
+
}
|
|
2546
|
+
let membership = sorted.map((_, i) => i);
|
|
2547
|
+
if (totalWeight === 0) return new Map(sorted.map((f, i) => [f, i]));
|
|
2548
|
+
for (let l = 0; l < MAX_LEVELS; l++) {
|
|
2549
|
+
const comm = localMoves(level, totalWeight);
|
|
2550
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2551
|
+
for (const c of comm) if (!ids.has(c)) ids.set(c, ids.size);
|
|
2552
|
+
if (ids.size === level.nodes) break;
|
|
2553
|
+
membership = membership.map((c) => ids.get(comm[c]));
|
|
2554
|
+
const next = { nodes: ids.size, adj: Array.from({ length: ids.size }, () => /* @__PURE__ */ new Map()), selfLoops: new Array(ids.size).fill(0) };
|
|
2555
|
+
for (let i = 0; i < level.nodes; i++) {
|
|
2556
|
+
const ci = ids.get(comm[i]);
|
|
2557
|
+
next.selfLoops[ci] += level.selfLoops[i];
|
|
2558
|
+
for (const [j, w] of level.adj[i]) {
|
|
2559
|
+
const cj = ids.get(comm[j]);
|
|
2560
|
+
if (ci === cj) {
|
|
2561
|
+
if (i < j) next.selfLoops[ci] += w;
|
|
2562
|
+
} else next.adj[ci].set(cj, (next.adj[ci].get(cj) ?? 0) + w);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
level = next;
|
|
2566
|
+
}
|
|
2567
|
+
const members = /* @__PURE__ */ new Map();
|
|
2568
|
+
sorted.forEach((f, i) => {
|
|
2569
|
+
const c = membership[i];
|
|
2570
|
+
const list = members.get(c);
|
|
2571
|
+
if (list) list.push(f);
|
|
2572
|
+
else members.set(c, [f]);
|
|
2573
|
+
});
|
|
2574
|
+
const order = [...members.entries()].sort((a, b) => b[1].length - a[1].length || a[1][0].localeCompare(b[1][0]));
|
|
2575
|
+
const out = /* @__PURE__ */ new Map();
|
|
2576
|
+
order.forEach(([, list], rank) => {
|
|
2577
|
+
for (const f of list) out.set(f, rank);
|
|
2578
|
+
});
|
|
2579
|
+
return out;
|
|
2580
|
+
}
|
|
2581
|
+
var MAX_SWEEPS, MAX_LEVELS;
|
|
2582
|
+
var init_codegraph_cluster = __esm({
|
|
2583
|
+
"src/codegraph-cluster.ts"() {
|
|
2584
|
+
"use strict";
|
|
2585
|
+
MAX_SWEEPS = 20;
|
|
2586
|
+
MAX_LEVELS = 6;
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
|
|
2590
|
+
// src/codegraph-report.ts
|
|
2591
|
+
var codegraph_report_exports = {};
|
|
2592
|
+
__export(codegraph_report_exports, {
|
|
2593
|
+
codeGraphReport: () => codeGraphReport,
|
|
2594
|
+
formatReport: () => formatReport
|
|
2595
|
+
});
|
|
2596
|
+
function areaLabel(paths) {
|
|
2597
|
+
const dirs = paths.map((p) => p.split("/").slice(0, -1));
|
|
2598
|
+
let label = [];
|
|
2599
|
+
for (let depth = 1; ; depth++) {
|
|
2600
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2601
|
+
for (const d of dirs) if (d.length >= depth) {
|
|
2602
|
+
const k = d.slice(0, depth).join("/");
|
|
2603
|
+
counts2.set(k, (counts2.get(k) ?? 0) + 1);
|
|
2604
|
+
}
|
|
2605
|
+
const top2 = [...counts2.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
2606
|
+
if (!top2 || top2[1] / paths.length < LABEL_SHARE) break;
|
|
2607
|
+
label = top2[0].split("/");
|
|
2608
|
+
}
|
|
2609
|
+
if (label.length) return `${label.join("/")}/`;
|
|
2610
|
+
if (paths.length === 1) return paths[0];
|
|
2611
|
+
const first = /* @__PURE__ */ new Map();
|
|
2612
|
+
for (const d of dirs) {
|
|
2613
|
+
const k = d[0] ?? "(root)";
|
|
2614
|
+
first.set(k, (first.get(k) ?? 0) + 1);
|
|
2615
|
+
}
|
|
2616
|
+
const [top] = [...first.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
2617
|
+
return `${top}/ (mixed)`;
|
|
2618
|
+
}
|
|
2619
|
+
function codeGraphReport(opts = {}) {
|
|
2620
|
+
const graph = loadFreshGraph(opts.project, opts.refresh ?? true);
|
|
2621
|
+
if (!graph) return null;
|
|
2622
|
+
const nodes = graphNodes(graph);
|
|
2623
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
2624
|
+
const langByPath = new Map(graph.files.map((f) => [f.path, f.lang]));
|
|
2625
|
+
const own = graph.files.filter((f) => !isForeignPath(f.path));
|
|
2626
|
+
const codePaths = new Set(own.filter((f) => isCodeLang(f.lang)).map((f) => f.path));
|
|
2627
|
+
const docPaths = new Set(own.filter((f) => f.lang === "markdown").map((f) => f.path));
|
|
2628
|
+
const weights = /* @__PURE__ */ new Map();
|
|
2629
|
+
const directed = /* @__PURE__ */ new Map();
|
|
2630
|
+
const docRefs = /* @__PURE__ */ new Map();
|
|
2631
|
+
for (const e of graph.edges) {
|
|
2632
|
+
if (e[2] === "contains") continue;
|
|
2633
|
+
const a = byId.get(e[0])?.path;
|
|
2634
|
+
const b = byId.get(e[1])?.path;
|
|
2635
|
+
if (!a || !b || a === b) continue;
|
|
2636
|
+
if (docPaths.has(a) && codePaths.has(b)) {
|
|
2637
|
+
const refs = docRefs.get(a) ?? /* @__PURE__ */ new Map();
|
|
2638
|
+
refs.set(b, (refs.get(b) ?? 0) + 1);
|
|
2639
|
+
docRefs.set(a, refs);
|
|
2640
|
+
continue;
|
|
2641
|
+
}
|
|
2642
|
+
if (!codePaths.has(a) || !codePaths.has(b)) continue;
|
|
2643
|
+
const key = a < b ? `${a}
|
|
2644
|
+
${b}` : `${b}
|
|
2645
|
+
${a}`;
|
|
2646
|
+
weights.set(key, (weights.get(key) ?? 0) + 1);
|
|
2647
|
+
directed.set(`${a}
|
|
2648
|
+
${b}`, (directed.get(`${a}
|
|
2649
|
+
${b}`) ?? 0) + 1);
|
|
2650
|
+
}
|
|
2651
|
+
const links = [...weights.entries()].map(([k, w]) => {
|
|
2652
|
+
const [a, b] = k.split("\n");
|
|
2653
|
+
return [a, b, w];
|
|
2654
|
+
});
|
|
2655
|
+
const areaOf = clusterFiles([...codePaths], links);
|
|
2656
|
+
const members = /* @__PURE__ */ new Map();
|
|
2657
|
+
for (const [path, area] of areaOf) {
|
|
2658
|
+
const list = members.get(area);
|
|
2659
|
+
if (list) list.push(path);
|
|
2660
|
+
else members.set(area, [path]);
|
|
2661
|
+
}
|
|
2662
|
+
const deg = inDegree(graph.edges, true);
|
|
2663
|
+
const maxAreas = opts.maxAreas ?? DEFAULT_MAX_AREAS;
|
|
2664
|
+
const ranked = [...members.entries()].filter(([, paths]) => paths.length >= 2).sort((a, b) => b[1].length - a[1].length || a[0] - b[0]);
|
|
2665
|
+
const symbolsOf = /* @__PURE__ */ new Map();
|
|
2666
|
+
for (const n of nodes) {
|
|
2667
|
+
if (n.kind === "file" || isTestPath(n.path)) continue;
|
|
2668
|
+
const area = areaOf.get(n.path);
|
|
2669
|
+
if (area === void 0) continue;
|
|
2670
|
+
const list = symbolsOf.get(area);
|
|
2671
|
+
if (list) list.push(n);
|
|
2672
|
+
else symbolsOf.set(area, [n]);
|
|
2673
|
+
}
|
|
2674
|
+
const keys = /* @__PURE__ */ new Map();
|
|
2675
|
+
const keyOf = (area) => {
|
|
2676
|
+
let key = keys.get(area);
|
|
2677
|
+
if (key) return key;
|
|
2678
|
+
key = (symbolsOf.get(area) ?? []).map((n) => ({ name: n.name, kind: n.kind, path: n.path, line: n.line, inDegree: deg.get(n.id) ?? 0 })).filter((sym) => sym.inDegree > 0).sort((a, b) => b.inDegree - a.inDegree || a.name.localeCompare(b.name)).slice(0, KEY_SYMBOLS);
|
|
2679
|
+
keys.set(area, key);
|
|
2680
|
+
return key;
|
|
2681
|
+
};
|
|
2682
|
+
const base = new Map(ranked.map(([area, paths]) => [area, areaLabel(paths)]));
|
|
2683
|
+
const seenLabel = /* @__PURE__ */ new Map();
|
|
2684
|
+
for (const label of base.values()) seenLabel.set(label, (seenLabel.get(label) ?? 0) + 1);
|
|
2685
|
+
const labels = new Map([...base.entries()].map(([area, label]) => {
|
|
2686
|
+
if ((seenLabel.get(label) ?? 0) < 2) return [area, label];
|
|
2687
|
+
const around = keyOf(area)[0]?.name;
|
|
2688
|
+
return [area, around ? `${label} ~ ${around}` : label];
|
|
2689
|
+
}));
|
|
2690
|
+
const docsOf = /* @__PURE__ */ new Map();
|
|
2691
|
+
for (const [doc, refs] of docRefs) {
|
|
2692
|
+
const byArea = /* @__PURE__ */ new Map();
|
|
2693
|
+
for (const [code, w] of refs) {
|
|
2694
|
+
const area = areaOf.get(code);
|
|
2695
|
+
if (area !== void 0) byArea.set(area, (byArea.get(area) ?? 0) + w);
|
|
2696
|
+
}
|
|
2697
|
+
const best = [...byArea.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0];
|
|
2698
|
+
if (!best) continue;
|
|
2699
|
+
const list = docsOf.get(best[0]) ?? [];
|
|
2700
|
+
list.push(doc);
|
|
2701
|
+
docsOf.set(best[0], list);
|
|
2702
|
+
}
|
|
2703
|
+
const areas = ranked.slice(0, maxAreas).map(([area, paths]) => {
|
|
2704
|
+
const inArea = new Set(paths);
|
|
2705
|
+
const out = /* @__PURE__ */ new Map();
|
|
2706
|
+
for (const [key, w] of directed) {
|
|
2707
|
+
const [a, b] = key.split("\n");
|
|
2708
|
+
if (!inArea.has(a) || inArea.has(b)) continue;
|
|
2709
|
+
const target = areaOf.get(b);
|
|
2710
|
+
if (target !== void 0 && target !== area && labels.has(target)) out.set(target, (out.get(target) ?? 0) + w);
|
|
2711
|
+
}
|
|
2712
|
+
return {
|
|
2713
|
+
label: labels.get(area),
|
|
2714
|
+
files: paths.length,
|
|
2715
|
+
languages: [...new Set(paths.map((p) => langByPath.get(p)).filter((l) => !!l))].sort(),
|
|
2716
|
+
keySymbols: keyOf(area),
|
|
2717
|
+
docs: (docsOf.get(area) ?? []).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, AREA_DOCS),
|
|
2718
|
+
dependsOn: [...out.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, DEPENDS_ON).map(([t]) => labels.get(t))
|
|
2719
|
+
};
|
|
2720
|
+
});
|
|
2721
|
+
const titleOf = /* @__PURE__ */ new Map();
|
|
2722
|
+
for (const n of nodes) if (n.kind === "section" && !titleOf.has(n.path)) titleOf.set(n.path, n.name);
|
|
2723
|
+
const docFiles = own.filter((f) => f.lang === "markdown").sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
|
|
2724
|
+
const maxDocs = opts.maxDocs ?? DEFAULT_MAX_DOCS;
|
|
2725
|
+
return {
|
|
2726
|
+
name: graph.name,
|
|
2727
|
+
totals: {
|
|
2728
|
+
files: graph.files.length,
|
|
2729
|
+
symbols: nodes.length - graph.files.length,
|
|
2730
|
+
edges: graph.edges.length,
|
|
2731
|
+
inferred: graph.edges.filter((e) => e[3] === 1).length,
|
|
2732
|
+
docs: docFiles.length
|
|
2733
|
+
},
|
|
2734
|
+
areas,
|
|
2735
|
+
moreAreas: Math.max(0, ranked.length - areas.length),
|
|
2736
|
+
docs: docFiles.slice(0, maxDocs).map((f) => ({ path: f.path, title: titleOf.get(f.path) ?? "" })),
|
|
2737
|
+
moreDocs: Math.max(0, docFiles.length - maxDocs),
|
|
2738
|
+
foreign: graph.files.length - own.length,
|
|
2739
|
+
truncated: graph.truncated
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
function formatReport(r) {
|
|
2743
|
+
const t = r.totals;
|
|
2744
|
+
const lines = [`repo report - ${r.name} - ${t.files} files (${t.docs} docs) - ${t.symbols} symbols - ${t.edges} edges (${t.inferred} inferred by name)`];
|
|
2745
|
+
if (r.foreign) lines.push(`${r.foreign} file(s) of vendored, reference or example material are left out of the areas below.`);
|
|
2746
|
+
lines.push("", "areas (files that depend on each other, largest first):");
|
|
2747
|
+
for (const a of r.areas) {
|
|
2748
|
+
lines.push(`- ${a.label} - ${a.files} files${a.languages.length ? ` - ${a.languages.join(", ")}` : ""}`);
|
|
2749
|
+
if (a.keySymbols.length) lines.push(` key: ${a.keySymbols.map((s) => `${s.name} (${s.path}:${s.line}, ${s.inDegree} in)`).join(", ")}`);
|
|
2750
|
+
if (a.dependsOn.length) lines.push(` uses: ${a.dependsOn.join(", ")}`);
|
|
2751
|
+
if (a.docs.length) lines.push(` docs: ${a.docs.join(", ")}`);
|
|
2752
|
+
}
|
|
2753
|
+
if (r.moreAreas) lines.push(` +${r.moreAreas} smaller area(s)`);
|
|
2754
|
+
if (r.docs.length) {
|
|
2755
|
+
lines.push("", "docs:");
|
|
2756
|
+
for (const d of r.docs) lines.push(`- ${d.path}${d.title ? ` - ${d.title}` : ""}`);
|
|
2757
|
+
if (r.moreDocs) lines.push(` +${r.moreDocs} more (enigma_codegraph_ask finds them by topic)`);
|
|
2758
|
+
}
|
|
2759
|
+
if (r.truncated) lines.push("", "The index hit its size cap; some files are not covered.");
|
|
2760
|
+
return `${lines.join("\n")}
|
|
2761
|
+
`;
|
|
2762
|
+
}
|
|
2763
|
+
var DEFAULT_MAX_AREAS, DEFAULT_MAX_DOCS, KEY_SYMBOLS, AREA_DOCS, DEPENDS_ON, LABEL_SHARE;
|
|
2764
|
+
var init_codegraph_report = __esm({
|
|
2765
|
+
"src/codegraph-report.ts"() {
|
|
2766
|
+
"use strict";
|
|
2767
|
+
init_codegraph();
|
|
2768
|
+
init_codegraph_rank();
|
|
2769
|
+
init_codegraph_extract();
|
|
2770
|
+
init_codegraph_cluster();
|
|
2771
|
+
DEFAULT_MAX_AREAS = 8;
|
|
2772
|
+
DEFAULT_MAX_DOCS = 12;
|
|
2773
|
+
KEY_SYMBOLS = 3;
|
|
2774
|
+
AREA_DOCS = 2;
|
|
2775
|
+
DEPENDS_ON = 2;
|
|
2776
|
+
LABEL_SHARE = 0.6;
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
|
|
2255
2780
|
// src/codegraph-hook.ts
|
|
2256
2781
|
import { homedir as homedir3 } from "os";
|
|
2257
2782
|
|
|
@@ -2388,14 +2913,17 @@ async function sessionStart(dir) {
|
|
|
2388
2913
|
" - enigma_codegraph_skeleton: a file's whole API surface, no bodies.",
|
|
2389
2914
|
" - enigma_codegraph_grep: every occurrence, grouped by enclosing symbol - when you need them ALL, not the top matches."
|
|
2390
2915
|
].join("\n");
|
|
2391
|
-
const
|
|
2916
|
+
const rep = await Promise.resolve().then(() => (init_codegraph_report(), codegraph_report_exports));
|
|
2917
|
+
const report = rep.codeGraphReport({ project, refresh: false });
|
|
2918
|
+
const map = report ? null : q.codeGraphMap({ project, refresh: false, maxDirs: 8 });
|
|
2919
|
+
const body = report ? rep.formatReport(report) : map ? fmt.formatMap(map) : "";
|
|
2392
2920
|
const fresh = q.codeGraphCheck(project);
|
|
2393
2921
|
const stale = fresh && fresh.stale > 0 ? `
|
|
2394
2922
|
${fresh.stale} file(s) changed since the last index; the next query refreshes them.` : "";
|
|
2395
|
-
writeStatuslineSnapshot(projectRoot(cg, project, dir), map?.totals.symbols ?? 0, fresh?.stale ?? 0);
|
|
2396
|
-
emit("SessionStart",
|
|
2923
|
+
writeStatuslineSnapshot(projectRoot(cg, project, dir), report?.totals.symbols ?? map?.totals.symbols ?? 0, fresh?.stale ?? 0);
|
|
2924
|
+
emit("SessionStart", body ? `${directive}
|
|
2397
2925
|
|
|
2398
|
-
${
|
|
2926
|
+
${body.trimEnd()}${stale}` : directive);
|
|
2399
2927
|
}
|
|
2400
2928
|
function coveringProject(cg, dir) {
|
|
2401
2929
|
const project = cg.findProjectForCwd(dir);
|