enigma-cli 1.59.1 → 1.61.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 +5 -0
- package/dist/codegraph-hook.js +588 -50
- package/dist/post-edit.js +588 -50
- package/package.json +1 -1
package/dist/codegraph-hook.js
CHANGED
|
@@ -94,6 +94,7 @@ var init_secret_box = __esm({
|
|
|
94
94
|
// src/config.ts
|
|
95
95
|
var config_exports = {};
|
|
96
96
|
__export(config_exports, {
|
|
97
|
+
API_ROTATIONS: () => API_ROTATIONS,
|
|
97
98
|
CONFIG_CHOICES: () => CONFIG_CHOICES,
|
|
98
99
|
CONFIG_DEFAULTS: () => CONFIG_DEFAULTS,
|
|
99
100
|
CONFIG_FILE: () => CONFIG_FILE,
|
|
@@ -106,6 +107,7 @@ __export(config_exports, {
|
|
|
106
107
|
PROMPT_SECRET_MODES: () => PROMPT_SECRET_MODES,
|
|
107
108
|
RECALL_PROVIDERS: () => RECALL_PROVIDERS,
|
|
108
109
|
SKILL_UPDATE_POLICIES: () => SKILL_UPDATE_POLICIES,
|
|
110
|
+
apiRotationOf: () => apiRotationOf,
|
|
109
111
|
getRecallApiKey: () => getRecallApiKey,
|
|
110
112
|
readConfig: () => readConfig,
|
|
111
113
|
readConfigAt: () => readConfigAt,
|
|
@@ -126,6 +128,9 @@ __export(config_exports, {
|
|
|
126
128
|
});
|
|
127
129
|
import { join as join3 } from "path";
|
|
128
130
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
131
|
+
function apiRotationOf(value) {
|
|
132
|
+
return API_ROTATIONS.includes(value) ? value : "off";
|
|
133
|
+
}
|
|
129
134
|
function configPath(scope) {
|
|
130
135
|
if (scope === "local") return join3(process.cwd(), CONFIG_FILE);
|
|
131
136
|
return join3(enigmaHome(), CONFIG_FILE);
|
|
@@ -240,7 +245,7 @@ function setSkillAgentOff(skill, agent, off) {
|
|
|
240
245
|
if (!isDir(dir)) mkdirSync2(dir, { recursive: true });
|
|
241
246
|
writeFileSync3(path, JSON.stringify(next, null, 2) + "\n");
|
|
242
247
|
}
|
|
243
|
-
var CONFIG_FILE, OUTPUT_STYLES, MINIMAL_CODE_LEVELS, DASHBOARD_MODES, DASHBOARD_BINDS, PROMPT_SECRET_MODES, SKILL_UPDATE_POLICIES, LOGO_COLOR_POLICIES, RECALL_PROVIDERS, GATE_SEVERITIES, CONFIG_DEFAULTS, CONFIG_CHOICES;
|
|
248
|
+
var CONFIG_FILE, OUTPUT_STYLES, MINIMAL_CODE_LEVELS, API_ROTATIONS, DASHBOARD_MODES, DASHBOARD_BINDS, PROMPT_SECRET_MODES, SKILL_UPDATE_POLICIES, LOGO_COLOR_POLICIES, RECALL_PROVIDERS, GATE_SEVERITIES, CONFIG_DEFAULTS, CONFIG_CHOICES;
|
|
244
249
|
var init_config = __esm({
|
|
245
250
|
"src/config.ts"() {
|
|
246
251
|
"use strict";
|
|
@@ -249,6 +254,7 @@ var init_config = __esm({
|
|
|
249
254
|
CONFIG_FILE = ".enigma.json";
|
|
250
255
|
OUTPUT_STYLES = ["off", "lite", "full", "ultra"];
|
|
251
256
|
MINIMAL_CODE_LEVELS = ["off", "lite", "full", "ultra"];
|
|
257
|
+
API_ROTATIONS = ["off", "round-robin", "least-used", "fill-first", "random"];
|
|
252
258
|
DASHBOARD_MODES = ["off", "on-demand", "always"];
|
|
253
259
|
DASHBOARD_BINDS = ["loopback", "lan", "custom"];
|
|
254
260
|
PROMPT_SECRET_MODES = ["redact", "reject"];
|
|
@@ -304,6 +310,8 @@ var init_config = __esm({
|
|
|
304
310
|
gateTrivialLines: 20,
|
|
305
311
|
subagentLimit: 4,
|
|
306
312
|
compactWindow: 4e5,
|
|
313
|
+
relay: true,
|
|
314
|
+
relayAt: 3e5,
|
|
307
315
|
planSessionLimit: 0,
|
|
308
316
|
planWeeklyLimit: 0,
|
|
309
317
|
planWeeklySonnetLimit: 0,
|
|
@@ -317,6 +325,9 @@ var init_config = __esm({
|
|
|
317
325
|
apiAccount: "",
|
|
318
326
|
apiProfile: "",
|
|
319
327
|
apiPack: "",
|
|
328
|
+
apiRotation: "off",
|
|
329
|
+
apiAccountPool: [],
|
|
330
|
+
apiClientContext: true,
|
|
320
331
|
toolPaths: {},
|
|
321
332
|
bypassDisabled: [],
|
|
322
333
|
discardedSkills: [],
|
|
@@ -337,7 +348,8 @@ var init_config = __esm({
|
|
|
337
348
|
skillUpdatePolicy: SKILL_UPDATE_POLICIES,
|
|
338
349
|
logoColorPolicy: LOGO_COLOR_POLICIES,
|
|
339
350
|
recallProvider: RECALL_PROVIDERS,
|
|
340
|
-
gateSeverity: GATE_SEVERITIES
|
|
351
|
+
gateSeverity: GATE_SEVERITIES,
|
|
352
|
+
apiRotation: API_ROTATIONS
|
|
341
353
|
};
|
|
342
354
|
}
|
|
343
355
|
});
|
|
@@ -545,10 +557,222 @@ var init_codegraph_rank = __esm({
|
|
|
545
557
|
}
|
|
546
558
|
});
|
|
547
559
|
|
|
560
|
+
// src/codegraph-extract-docs.ts
|
|
561
|
+
function cap(text) {
|
|
562
|
+
const t = text.trim();
|
|
563
|
+
return t.length > MAX_SIGNATURE ? `${t.slice(0, MAX_SIGNATURE)}...` : t;
|
|
564
|
+
}
|
|
565
|
+
function indentOf(line) {
|
|
566
|
+
let n = 0;
|
|
567
|
+
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
568
|
+
return n;
|
|
569
|
+
}
|
|
570
|
+
function fencedLines(lines) {
|
|
571
|
+
const inside = new Uint8Array(lines.length);
|
|
572
|
+
let fence = null;
|
|
573
|
+
for (let i = 0; i < lines.length; i++) {
|
|
574
|
+
const m = /^\s*(`{3,}|~{3,})/.exec(lines[i]);
|
|
575
|
+
if (fence) {
|
|
576
|
+
inside[i] = 1;
|
|
577
|
+
if (m && m[1][0] === fence[0] && m[1].length >= fence.length) fence = null;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (m) {
|
|
581
|
+
fence = m[1];
|
|
582
|
+
inside[i] = 1;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return inside;
|
|
586
|
+
}
|
|
587
|
+
function markdownSymbols(lines) {
|
|
588
|
+
const fenced = fencedLines(lines);
|
|
589
|
+
const heads = [];
|
|
590
|
+
for (let i = 0; i < lines.length; i++) {
|
|
591
|
+
if (fenced[i]) continue;
|
|
592
|
+
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(lines[i]);
|
|
593
|
+
if (m && m[1].length <= MAX_HEADING_LEVEL) heads.push({ level: m[1].length, name: m[2].replace(/[`*_]/g, "").trim(), line: i + 1 });
|
|
594
|
+
}
|
|
595
|
+
return heads.filter((h) => h.name).map((h, idx) => {
|
|
596
|
+
const next = heads.slice(idx + 1).find((o) => o.level <= h.level);
|
|
597
|
+
return { name: h.name, kind: "section", line: h.line, endLine: next ? next.line - 1 : lines.length, signature: cap(lines[h.line - 1]) };
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
function sqlEnd(lines, start) {
|
|
601
|
+
let dollar = false;
|
|
602
|
+
for (let i = start; i < lines.length; i++) {
|
|
603
|
+
const parts = lines[i].replace(/--.*$/, "").split("$$");
|
|
604
|
+
for (let p = 0; p < parts.length; p++) {
|
|
605
|
+
if (p > 0) dollar = !dollar;
|
|
606
|
+
if (!dollar && parts[p].includes(";")) return i + 1;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return lines.length;
|
|
610
|
+
}
|
|
611
|
+
function sqlSymbols(lines) {
|
|
612
|
+
const rules = [
|
|
613
|
+
["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")],
|
|
614
|
+
["function", new RegExp(String.raw`^\s*create\s+(?:or\s+replace\s+)?(?:function|procedure)\s+${SQL_NAME}`, "i")],
|
|
615
|
+
["type", new RegExp(String.raw`^\s*create\s+type\s+${SQL_NAME}`, "i")]
|
|
616
|
+
];
|
|
617
|
+
const out = [];
|
|
618
|
+
for (let i = 0; i < lines.length; i++) {
|
|
619
|
+
for (const [kind, re] of rules) {
|
|
620
|
+
const m = re.exec(lines[i]);
|
|
621
|
+
if (!m) continue;
|
|
622
|
+
out.push({ name: m[1], kind, line: i + 1, endLine: sqlEnd(lines, i), signature: cap(lines[i].replace(/\(.*$/, "")) });
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return out;
|
|
627
|
+
}
|
|
628
|
+
function prismaSymbols(lines) {
|
|
629
|
+
const out = [];
|
|
630
|
+
for (let i = 0; i < lines.length; i++) {
|
|
631
|
+
const m = /^\s*(model|view|enum|type)\s+([A-Za-z_]\w*)\s*\{/.exec(lines[i]);
|
|
632
|
+
if (!m) continue;
|
|
633
|
+
let end = i;
|
|
634
|
+
while (end < lines.length - 1 && !/^\s*\}/.test(lines[end])) end++;
|
|
635
|
+
const kind = m[1] === "enum" ? "enum" : m[1] === "type" ? "type" : "table";
|
|
636
|
+
out.push({ name: m[2], kind, line: i + 1, endLine: end + 1, signature: cap(`${m[1]} ${m[2]}`) });
|
|
637
|
+
}
|
|
638
|
+
return out;
|
|
639
|
+
}
|
|
640
|
+
function yamlEnd(lines, start) {
|
|
641
|
+
const base = indentOf(lines[start]);
|
|
642
|
+
let end = start + 1;
|
|
643
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
644
|
+
const line = lines[i];
|
|
645
|
+
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
|
646
|
+
if (indentOf(line) <= base && !line.trimStart().startsWith("- ")) break;
|
|
647
|
+
end = i + 1;
|
|
648
|
+
}
|
|
649
|
+
return end;
|
|
650
|
+
}
|
|
651
|
+
function yamlSymbols(lines) {
|
|
652
|
+
const out = [];
|
|
653
|
+
const KEY = /^(\s*)([A-Za-z0-9_][\w.-]*)\s*:(?:\s|$)/;
|
|
654
|
+
for (let i = 0; i < lines.length; i++) {
|
|
655
|
+
const m = KEY.exec(lines[i]);
|
|
656
|
+
if (!m || m[1].length) continue;
|
|
657
|
+
const end = yamlEnd(lines, i);
|
|
658
|
+
out.push({ name: m[2], kind: "key", line: i + 1, endLine: end, signature: cap(lines[i]) });
|
|
659
|
+
if (!YAML_CONTAINERS.has(m[2])) continue;
|
|
660
|
+
let childIndent = -1;
|
|
661
|
+
for (let j = i + 1; j < end; j++) {
|
|
662
|
+
const c = KEY.exec(lines[j]);
|
|
663
|
+
if (!c || !c[1].length) continue;
|
|
664
|
+
if (childIndent === -1) childIndent = c[1].length;
|
|
665
|
+
if (c[1].length !== childIndent) continue;
|
|
666
|
+
out.push({ name: c[2], kind: "key", line: j + 1, endLine: yamlEnd(lines, j), signature: cap(`${m[2]}.${c[2]}`) });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return out;
|
|
670
|
+
}
|
|
671
|
+
function tomlSymbols(lines) {
|
|
672
|
+
const heads = [];
|
|
673
|
+
for (let i = 0; i < lines.length; i++) {
|
|
674
|
+
const m = /^\s*\[\[?\s*([^\]]+?)\s*\]\]?\s*(?:#.*)?$/.exec(lines[i]);
|
|
675
|
+
if (m) heads.push({ name: m[1], line: i + 1 });
|
|
676
|
+
}
|
|
677
|
+
return heads.map((h, idx) => ({
|
|
678
|
+
name: h.name,
|
|
679
|
+
kind: "key",
|
|
680
|
+
line: h.line,
|
|
681
|
+
endLine: idx + 1 < heads.length ? heads[idx + 1].line - 1 : lines.length,
|
|
682
|
+
signature: cap(lines[h.line - 1])
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
685
|
+
function markdownLinks(lines) {
|
|
686
|
+
const fenced = fencedLines(lines);
|
|
687
|
+
const out = /* @__PURE__ */ new Set();
|
|
688
|
+
for (let i = 0; i < lines.length; i++) {
|
|
689
|
+
if (fenced[i]) continue;
|
|
690
|
+
const re = /\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g;
|
|
691
|
+
let m;
|
|
692
|
+
while (m = re.exec(lines[i])) {
|
|
693
|
+
const target = m[1].replace(/[#?].*$/, "");
|
|
694
|
+
if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//")) continue;
|
|
695
|
+
try {
|
|
696
|
+
out.add(decodeURI(target));
|
|
697
|
+
} catch {
|
|
698
|
+
out.add(target);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return [...out];
|
|
703
|
+
}
|
|
704
|
+
function markdownCodeMentions(content) {
|
|
705
|
+
const lines = content.split("\n");
|
|
706
|
+
const fenced = fencedLines(lines);
|
|
707
|
+
const out = [];
|
|
708
|
+
for (let i = 0; i < lines.length; i++) {
|
|
709
|
+
if (fenced[i]) continue;
|
|
710
|
+
const re = /`([A-Za-z_$][\w$]*)(?:\(\))?`/g;
|
|
711
|
+
let m;
|
|
712
|
+
while (m = re.exec(lines[i])) out.push({ name: m[1], line: i + 1 });
|
|
713
|
+
}
|
|
714
|
+
return out;
|
|
715
|
+
}
|
|
716
|
+
function extractDocFile(lang, content) {
|
|
717
|
+
const lines = content.split("\n");
|
|
718
|
+
const out = (() => {
|
|
719
|
+
switch (lang) {
|
|
720
|
+
case "markdown":
|
|
721
|
+
return { symbols: markdownSymbols(lines), imports: markdownLinks(lines) };
|
|
722
|
+
case "sql":
|
|
723
|
+
return { symbols: sqlSymbols(lines), imports: [] };
|
|
724
|
+
case "prisma":
|
|
725
|
+
return { symbols: prismaSymbols(lines), imports: [] };
|
|
726
|
+
case "yaml":
|
|
727
|
+
return { symbols: yamlSymbols(lines), imports: [] };
|
|
728
|
+
case "toml":
|
|
729
|
+
return { symbols: tomlSymbols(lines), imports: [] };
|
|
730
|
+
default:
|
|
731
|
+
return { symbols: [], imports: [] };
|
|
732
|
+
}
|
|
733
|
+
})();
|
|
734
|
+
for (const s of out.symbols) while (s.endLine > s.line && !(lines[s.endLine - 1] ?? "").trim()) s.endLine--;
|
|
735
|
+
return out;
|
|
736
|
+
}
|
|
737
|
+
var DOC_LANG_BY_EXT, DOC_LANGS, DOC_REFERENCE_LANGS, YAML_CONTAINERS, MAX_SIGNATURE, MAX_HEADING_LEVEL, SQL_NAME;
|
|
738
|
+
var init_codegraph_extract_docs = __esm({
|
|
739
|
+
"src/codegraph-extract-docs.ts"() {
|
|
740
|
+
"use strict";
|
|
741
|
+
DOC_LANG_BY_EXT = {
|
|
742
|
+
".md": "markdown",
|
|
743
|
+
".mdx": "markdown",
|
|
744
|
+
".sql": "sql",
|
|
745
|
+
".prisma": "prisma",
|
|
746
|
+
".yaml": "yaml",
|
|
747
|
+
".yml": "yaml",
|
|
748
|
+
".toml": "toml"
|
|
749
|
+
};
|
|
750
|
+
DOC_LANGS = new Set(Object.values(DOC_LANG_BY_EXT));
|
|
751
|
+
DOC_REFERENCE_LANGS = /* @__PURE__ */ new Set(["sql", "prisma"]);
|
|
752
|
+
YAML_CONTAINERS = /* @__PURE__ */ new Set(["services", "jobs", "workflows", "volumes", "networks"]);
|
|
753
|
+
MAX_SIGNATURE = 200;
|
|
754
|
+
MAX_HEADING_LEVEL = 3;
|
|
755
|
+
SQL_NAME = String.raw`(?:[\w"\x60\[\]]+\.)?["\x60\[]?([A-Za-z_][\w$]*)["\x60\]]?`;
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
|
|
548
759
|
// src/codegraph-extract.ts
|
|
549
760
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
550
761
|
import { extname, join as join4, relative as relative2, resolve as resolve2 } from "path";
|
|
551
762
|
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
763
|
+
function isCodeLang(lang) {
|
|
764
|
+
return !DOC_LANGS.has(lang);
|
|
765
|
+
}
|
|
766
|
+
function isDocPath(path) {
|
|
767
|
+
return !!DOC_LANG_BY_EXT[extname(path).toLowerCase()];
|
|
768
|
+
}
|
|
769
|
+
function isForeignPath(rel) {
|
|
770
|
+
return FOREIGN_DOC_DIRS.test(rel);
|
|
771
|
+
}
|
|
772
|
+
function pickDocs(root, docs, room) {
|
|
773
|
+
const depth = (p) => relPath(root, p).split("/").length;
|
|
774
|
+
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)));
|
|
775
|
+
}
|
|
552
776
|
function symRules(lang) {
|
|
553
777
|
switch (lang) {
|
|
554
778
|
case "ts":
|
|
@@ -713,7 +937,7 @@ function lineResolver(content) {
|
|
|
713
937
|
return lo + 1;
|
|
714
938
|
};
|
|
715
939
|
}
|
|
716
|
-
function
|
|
940
|
+
function indentOf2(line) {
|
|
717
941
|
let n = 0;
|
|
718
942
|
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
719
943
|
return n;
|
|
@@ -779,18 +1003,18 @@ function braceEnd(lines, startIdx, lang) {
|
|
|
779
1003
|
return lines.length;
|
|
780
1004
|
}
|
|
781
1005
|
function indentEnd(lines, startIdx) {
|
|
782
|
-
const base =
|
|
1006
|
+
const base = indentOf2(lines[startIdx]);
|
|
783
1007
|
let end = startIdx + 1;
|
|
784
1008
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
785
1009
|
const line = lines[i];
|
|
786
1010
|
if (!line.trim()) continue;
|
|
787
|
-
if (
|
|
1011
|
+
if (indentOf2(line) <= base) break;
|
|
788
1012
|
end = i + 1;
|
|
789
1013
|
}
|
|
790
1014
|
return end;
|
|
791
1015
|
}
|
|
792
1016
|
function clampToSiblings(symbols, lines) {
|
|
793
|
-
const indentAt = (line) =>
|
|
1017
|
+
const indentAt = (line) => indentOf2(lines[line - 1] ?? "");
|
|
794
1018
|
for (let i = 0; i < symbols.length; i++) {
|
|
795
1019
|
const s = symbols[i];
|
|
796
1020
|
for (let j = i + 1; j < symbols.length; j++) {
|
|
@@ -803,14 +1027,15 @@ function clampToSiblings(symbols, lines) {
|
|
|
803
1027
|
}
|
|
804
1028
|
}
|
|
805
1029
|
function signatureOf(line) {
|
|
806
|
-
const
|
|
1030
|
+
const MAX_SIGNATURE2 = 200;
|
|
807
1031
|
let sig = line.trim();
|
|
808
1032
|
const brace = sig.indexOf("{");
|
|
809
1033
|
if (brace > 0) sig = sig.slice(0, brace).trim();
|
|
810
1034
|
if (sig.endsWith(":")) sig = sig.slice(0, -1).trim();
|
|
811
|
-
return sig.length >
|
|
1035
|
+
return sig.length > MAX_SIGNATURE2 ? `${sig.slice(0, MAX_SIGNATURE2)}...` : sig;
|
|
812
1036
|
}
|
|
813
1037
|
function extractFile(lang, content) {
|
|
1038
|
+
if (DOC_LANGS.has(lang)) return extractDocFile(lang, content);
|
|
814
1039
|
const lineAt = lineResolver(content);
|
|
815
1040
|
const lines = content.split("\n");
|
|
816
1041
|
const braced = BRACE_LANGS.has(lang);
|
|
@@ -852,25 +1077,27 @@ function gitSourceFiles(root) {
|
|
|
852
1077
|
const out = gitList(root, ["--cached", "--others", "--exclude-standard"]);
|
|
853
1078
|
if (out === null) return null;
|
|
854
1079
|
const nested = existsSync4(join4(root, ".gitmodules")) ? gitList(root, ["--recurse-submodules"]) : null;
|
|
855
|
-
const
|
|
1080
|
+
const code = [];
|
|
1081
|
+
const docs = [];
|
|
856
1082
|
const seen = /* @__PURE__ */ new Set();
|
|
857
1083
|
for (const rel of [...out.split("\0"), ...(nested ?? "").split("\0")]) {
|
|
858
|
-
if (
|
|
1084
|
+
if (code.length >= MAX_FILES) break;
|
|
859
1085
|
if (!rel || seen.has(rel)) continue;
|
|
860
1086
|
seen.add(rel);
|
|
861
1087
|
if (!LANG_BY_EXT[extname(rel).toLowerCase()]) continue;
|
|
862
1088
|
if (rel.split("/").some((seg) => seg.startsWith(".") || IGNORE_DIRS.has(seg))) continue;
|
|
863
|
-
|
|
1089
|
+
(isDocPath(rel) ? docs : code).push(join4(root, rel));
|
|
864
1090
|
}
|
|
865
|
-
return
|
|
1091
|
+
return [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
866
1092
|
}
|
|
867
1093
|
function walk(root) {
|
|
868
1094
|
const tracked = gitSourceFiles(root);
|
|
869
1095
|
if (tracked && tracked.length) return { files: tracked, truncated: tracked.length >= MAX_FILES };
|
|
870
|
-
const
|
|
1096
|
+
const code = [];
|
|
1097
|
+
const docs = [];
|
|
871
1098
|
const queue = [root];
|
|
872
1099
|
let visited = 0;
|
|
873
|
-
for (let head = 0; head < queue.length &&
|
|
1100
|
+
for (let head = 0; head < queue.length && code.length < MAX_FILES && visited < MAX_ENTRIES; head++) {
|
|
874
1101
|
let entries;
|
|
875
1102
|
try {
|
|
876
1103
|
entries = readdirSync2(queue[head], { withFileTypes: true });
|
|
@@ -878,16 +1105,17 @@ function walk(root) {
|
|
|
878
1105
|
continue;
|
|
879
1106
|
}
|
|
880
1107
|
for (const e of entries) {
|
|
881
|
-
if (
|
|
1108
|
+
if (code.length >= MAX_FILES || ++visited >= MAX_ENTRIES) break;
|
|
882
1109
|
if (e.isDirectory()) {
|
|
883
1110
|
if (!e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) queue.push(join4(queue[head], e.name));
|
|
884
1111
|
continue;
|
|
885
1112
|
}
|
|
886
|
-
if (!e.isFile()) continue;
|
|
887
|
-
|
|
1113
|
+
if (!e.isFile() || !LANG_BY_EXT[extname(e.name).toLowerCase()]) continue;
|
|
1114
|
+
(isDocPath(e.name) ? docs : code).push(join4(queue[head], e.name));
|
|
888
1115
|
}
|
|
889
1116
|
}
|
|
890
|
-
|
|
1117
|
+
const ordered = [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
1118
|
+
return { files: ordered, truncated: code.length >= MAX_FILES || visited >= MAX_ENTRIES };
|
|
891
1119
|
}
|
|
892
1120
|
function relPath(root, abs) {
|
|
893
1121
|
return relative2(root, abs).split("\\").join("/");
|
|
@@ -945,10 +1173,11 @@ function statSourceFiles(root) {
|
|
|
945
1173
|
}
|
|
946
1174
|
return out;
|
|
947
1175
|
}
|
|
948
|
-
var IGNORE_DIRS, LANG_BY_EXT, MAX_FILES, MAX_FILE_BYTES, BRACE_LANGS, HASH_COMMENT_LANGS, PREPROCESSOR_LANGS, MAX_ENTRIES;
|
|
1176
|
+
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
1177
|
var init_codegraph_extract = __esm({
|
|
950
1178
|
"src/codegraph-extract.ts"() {
|
|
951
1179
|
"use strict";
|
|
1180
|
+
init_codegraph_extract_docs();
|
|
952
1181
|
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
953
1182
|
"node_modules",
|
|
954
1183
|
".git",
|
|
@@ -973,7 +1202,7 @@ var init_codegraph_extract = __esm({
|
|
|
973
1202
|
".gradle",
|
|
974
1203
|
".dart_tool"
|
|
975
1204
|
]);
|
|
976
|
-
|
|
1205
|
+
CODE_LANG_BY_EXT = {
|
|
977
1206
|
".ts": "ts",
|
|
978
1207
|
".tsx": "ts",
|
|
979
1208
|
".mts": "ts",
|
|
@@ -999,6 +1228,9 @@ var init_codegraph_extract = __esm({
|
|
|
999
1228
|
".hpp": "cpp",
|
|
1000
1229
|
".hh": "cpp"
|
|
1001
1230
|
};
|
|
1231
|
+
LANG_BY_EXT = { ...CODE_LANG_BY_EXT, ...DOC_LANG_BY_EXT };
|
|
1232
|
+
FOREIGN_DOC_DIRS = /(?:^|\/)(?:references?|examples?|samples?|fixtures?|third[_-]party|vendor(?:ed)?)\//i;
|
|
1233
|
+
MAX_DOC_FILES = 1500;
|
|
1002
1234
|
MAX_FILES = 8e3;
|
|
1003
1235
|
MAX_FILE_BYTES = 512 * 1024;
|
|
1004
1236
|
BRACE_LANGS = /* @__PURE__ */ new Set(["ts", "js", "go", "rust", "java", "csharp", "kotlin", "php", "c", "cpp"]);
|
|
@@ -1214,12 +1446,12 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1214
1446
|
}
|
|
1215
1447
|
const edges = [];
|
|
1216
1448
|
const seen = /* @__PURE__ */ new Set();
|
|
1217
|
-
const push = (from, to, rel) => {
|
|
1449
|
+
const push = (from, to, rel, inferred = false) => {
|
|
1218
1450
|
if (edges.length >= MAX_EDGES || from === to) return;
|
|
1219
1451
|
const key = `${from}|${to}|${rel}`;
|
|
1220
1452
|
if (seen.has(key)) return;
|
|
1221
1453
|
seen.add(key);
|
|
1222
|
-
edges.push([from, to, rel]);
|
|
1454
|
+
edges.push(inferred ? [from, to, rel, 1] : [from, to, rel]);
|
|
1223
1455
|
};
|
|
1224
1456
|
for (const f of files) {
|
|
1225
1457
|
for (const span of spansByFile.get(f.path) ?? []) push(f.path, span.id, "contains");
|
|
@@ -1248,12 +1480,14 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1248
1480
|
const candidates = defsByName.get(name)?.filter((c) => imported?.has(c.path) && reachable(c.path));
|
|
1249
1481
|
return candidates?.length === 1 ? candidates[0].id : null;
|
|
1250
1482
|
};
|
|
1483
|
+
const extracted = (id) => id ? { id, inferred: false } : null;
|
|
1484
|
+
const inferred = (id) => id ? { id, inferred: true } : null;
|
|
1251
1485
|
if (!f.bindings) {
|
|
1252
1486
|
return (name) => {
|
|
1253
|
-
if (local.has(name)) return local.get(name)
|
|
1254
|
-
if (imported?.size) return unique(name);
|
|
1487
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
1488
|
+
if (imported?.size) return inferred(unique(name));
|
|
1255
1489
|
const all = defsByName.get(name)?.filter((c) => reachable(c.path));
|
|
1256
|
-
return all?.length === 1 ? all[0].id : null;
|
|
1490
|
+
return all?.length === 1 ? inferred(all[0].id) : null;
|
|
1257
1491
|
};
|
|
1258
1492
|
}
|
|
1259
1493
|
const bound = new Set(f.bindings.names);
|
|
@@ -1265,10 +1499,10 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1265
1499
|
return (name, memberOf) => {
|
|
1266
1500
|
if (memberOf !== void 0) {
|
|
1267
1501
|
const path = namespaces.get(memberOf);
|
|
1268
|
-
return path ? idByPathName.get(`${path}#${name}`)
|
|
1502
|
+
return path ? extracted(idByPathName.get(`${path}#${name}`)) : null;
|
|
1269
1503
|
}
|
|
1270
|
-
if (local.has(name)) return local.get(name)
|
|
1271
|
-
return bound.has(name) ? unique(name) : null;
|
|
1504
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
1505
|
+
return bound.has(name) ? extracted(unique(name)) : null;
|
|
1272
1506
|
};
|
|
1273
1507
|
}
|
|
1274
1508
|
const bodies = [];
|
|
@@ -1282,7 +1516,7 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1282
1516
|
const spans = spansByFile.get(f.path) ?? [];
|
|
1283
1517
|
const declLines = new Set(f.symbols.map((s) => s.line));
|
|
1284
1518
|
const lines = content.split("\n");
|
|
1285
|
-
indexBodies(bodies, f.path, spans, lines);
|
|
1519
|
+
indexBodies(bodies, f.path, spans, lines, DOC_LANGS.has(f.lang) ? MAX_DOC_BODY_TOKENS : MAX_BODY_TOKENS);
|
|
1286
1520
|
const bind = binderFor(f);
|
|
1287
1521
|
const strict = !!f.bindings;
|
|
1288
1522
|
for (const s of f.symbols) {
|
|
@@ -1291,9 +1525,17 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1291
1525
|
if (!self) continue;
|
|
1292
1526
|
for (const [rel, name] of inheritanceTargets(s.signature)) {
|
|
1293
1527
|
const target = bind(name);
|
|
1294
|
-
if (target) push(self.id, target, rel);
|
|
1528
|
+
if (target) push(self.id, target.id, rel, target.inferred);
|
|
1295
1529
|
}
|
|
1296
1530
|
}
|
|
1531
|
+
if (f.lang === "markdown") {
|
|
1532
|
+
for (const { name, line } of markdownCodeMentions(content)) {
|
|
1533
|
+
if (name.length < MIN_DOC_MENTION) continue;
|
|
1534
|
+
const code = defsByName.get(name)?.filter((c) => !DOC_LANGS.has(langByPath.get(c.path) ?? ""));
|
|
1535
|
+
if (code?.length === 1) push(enclosingSymbol(spans, line) ?? f.path, code[0].id, "references", true);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (DOC_LANGS.has(f.lang) && !DOC_REFERENCE_LANGS.has(f.lang)) continue;
|
|
1297
1539
|
for (let i = 0; i < lines.length; i++) {
|
|
1298
1540
|
const lineNo = i + 1;
|
|
1299
1541
|
if (declLines.has(lineNo)) continue;
|
|
@@ -1306,31 +1548,32 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
1306
1548
|
if (name.length < MIN_REF_NAME || !defsByName.has(name)) continue;
|
|
1307
1549
|
const memberOf = strict ? /([A-Za-z_$][\w$]*)\s*\.\s*$/.exec(line.slice(0, m.index))?.[1] : void 0;
|
|
1308
1550
|
const target = bind(name, memberOf);
|
|
1309
|
-
if (!target || target === from) continue;
|
|
1551
|
+
if (!target || target.id === from) continue;
|
|
1310
1552
|
const after = line.slice(m.index + name.length);
|
|
1311
|
-
|
|
1553
|
+
const calls = !DOC_LANGS.has(f.lang) && /^\s*\(/.test(after);
|
|
1554
|
+
push(from, target.id, calls ? "calls" : "references", target.inferred);
|
|
1312
1555
|
}
|
|
1313
1556
|
}
|
|
1314
1557
|
}
|
|
1315
1558
|
return { edges, bodies };
|
|
1316
1559
|
}
|
|
1317
|
-
function indexBodies(out, path, spans, lines) {
|
|
1560
|
+
function indexBodies(out, path, spans, lines, limit) {
|
|
1318
1561
|
const covered = new Uint8Array(lines.length + 1);
|
|
1319
1562
|
for (const s of spans) {
|
|
1320
1563
|
const text = lines.slice(s.line - 1, s.endLine).join("\n");
|
|
1321
1564
|
for (let l = s.line; l <= Math.min(s.endLine, lines.length); l++) covered[l] = 1;
|
|
1322
|
-
const bag = topTokens(text);
|
|
1565
|
+
const bag = topTokens(text, limit);
|
|
1323
1566
|
if (bag.length) out.push([s.id, bag]);
|
|
1324
1567
|
}
|
|
1325
1568
|
const residual = [];
|
|
1326
1569
|
for (let l = 1; l <= lines.length; l++) if (!covered[l]) residual.push(lines[l - 1]);
|
|
1327
|
-
const fileBag = topTokens(residual.join("\n"));
|
|
1570
|
+
const fileBag = topTokens(residual.join("\n"), limit);
|
|
1328
1571
|
if (fileBag.length) out.push([path, fileBag]);
|
|
1329
1572
|
}
|
|
1330
|
-
function topTokens(text) {
|
|
1573
|
+
function topTokens(text, limit) {
|
|
1331
1574
|
const m = /* @__PURE__ */ new Map();
|
|
1332
1575
|
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,
|
|
1576
|
+
return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit);
|
|
1334
1577
|
}
|
|
1335
1578
|
function isManagedPath(dir) {
|
|
1336
1579
|
const managed = resolve3(join5(enigmaHome(), ".enigma"));
|
|
@@ -1548,17 +1791,18 @@ function codeGraphAvailable() {
|
|
|
1548
1791
|
function codeGraphStatus() {
|
|
1549
1792
|
return { enabled: readConfig().config.codeGraph, available: true, projects: listProjects().length };
|
|
1550
1793
|
}
|
|
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;
|
|
1794
|
+
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
1795
|
var init_codegraph = __esm({
|
|
1553
1796
|
"src/codegraph.ts"() {
|
|
1554
1797
|
"use strict";
|
|
1555
1798
|
init_util();
|
|
1556
1799
|
init_config();
|
|
1557
1800
|
init_codegraph_rank();
|
|
1801
|
+
init_codegraph_extract_docs();
|
|
1558
1802
|
init_codegraph_extract();
|
|
1559
1803
|
init_codegraph_extract();
|
|
1560
1804
|
init_codegraph_types();
|
|
1561
|
-
GRAPH_VERSION =
|
|
1805
|
+
GRAPH_VERSION = 5;
|
|
1562
1806
|
RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".rb", ".php", ".cs", ".kt"];
|
|
1563
1807
|
LANG_EXTS = {
|
|
1564
1808
|
ts: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
@@ -1574,6 +1818,7 @@ var init_codegraph = __esm({
|
|
|
1574
1818
|
};
|
|
1575
1819
|
LANG_INDEX = { python: ["__init__"], go: ["mod"], rust: ["mod"] };
|
|
1576
1820
|
MIN_REF_NAME = 3;
|
|
1821
|
+
MIN_DOC_MENTION = 4;
|
|
1577
1822
|
MAX_EDGES = 4e5;
|
|
1578
1823
|
LANG_FAMILY = {
|
|
1579
1824
|
ts: "js",
|
|
@@ -1590,6 +1835,7 @@ var init_codegraph = __esm({
|
|
|
1590
1835
|
EXT_ORDER = orders(RESOLVE_EXTS, LANG_EXTS);
|
|
1591
1836
|
INDEX_ORDER = orders(INDEX_NAMES, LANG_INDEX);
|
|
1592
1837
|
MAX_BODY_TOKENS = 80;
|
|
1838
|
+
MAX_DOC_BODY_TOKENS = 24;
|
|
1593
1839
|
ROOT_MARKERS = [".git", "package.json", "go.mod", "pyproject.toml", "Cargo.toml", "pom.xml", "composer.json"];
|
|
1594
1840
|
MAX_ROOT_WALK = 6;
|
|
1595
1841
|
}
|
|
@@ -1822,11 +2068,11 @@ ${result.note}` : structural.fallthrough;
|
|
|
1822
2068
|
}
|
|
1823
2069
|
function adjacency(edges, direction) {
|
|
1824
2070
|
const adj = /* @__PURE__ */ new Map();
|
|
1825
|
-
for (const [source, target, rel] of edges) {
|
|
2071
|
+
for (const [source, target, rel, guess] of edges) {
|
|
1826
2072
|
if (!WALK_RELATIONS.has(rel)) continue;
|
|
1827
2073
|
const key = direction === "in" ? target : source;
|
|
1828
2074
|
const other = direction === "in" ? source : target;
|
|
1829
|
-
const entry = { other, relation: rel };
|
|
2075
|
+
const entry = { other, relation: rel, inferred: guess === 1 };
|
|
1830
2076
|
const arr = adj.get(key);
|
|
1831
2077
|
if (arr) arr.push(entry);
|
|
1832
2078
|
else adj.set(key, [entry]);
|
|
@@ -1855,13 +2101,13 @@ function codeGraphTrace(symbol, opts = {}) {
|
|
|
1855
2101
|
for (let d = 1; d <= depth && frontier.length; d++) {
|
|
1856
2102
|
const next = [];
|
|
1857
2103
|
for (const current of frontier) {
|
|
1858
|
-
for (const { other, relation } of adj.get(current) ?? []) {
|
|
2104
|
+
for (const { other, relation, inferred } of adj.get(current) ?? []) {
|
|
1859
2105
|
if (visited.has(other)) continue;
|
|
1860
2106
|
visited.add(other);
|
|
1861
2107
|
const node = loaded.byId.get(other);
|
|
1862
2108
|
if (!node) continue;
|
|
1863
2109
|
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 });
|
|
2110
|
+
hits.push({ id: other, name: node.name, kind: node.kind, path: node.path, line: node.line, relation, depth: d, ...inferred ? { inferred } : {} });
|
|
1865
2111
|
next.push(other);
|
|
1866
2112
|
}
|
|
1867
2113
|
}
|
|
@@ -1900,8 +2146,8 @@ function dirKey(path, depth) {
|
|
|
1900
2146
|
const segments = path.split("/");
|
|
1901
2147
|
return segments.slice(0, Math.min(depth, segments.length)).join("/");
|
|
1902
2148
|
}
|
|
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,
|
|
2149
|
+
function topHubs(nodes, deg, cap2) {
|
|
2150
|
+
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
2151
|
}
|
|
1906
2152
|
function codeGraphMap(opts = {}) {
|
|
1907
2153
|
const loaded = load(opts);
|
|
@@ -2129,7 +2375,7 @@ ${r.note ?? empty}
|
|
|
2129
2375
|
const lines = [head, ""];
|
|
2130
2376
|
lines.push(`defined at: ${r.matched.map((m) => `${m.path}:${m.line}`).join(", ")}`, "");
|
|
2131
2377
|
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}` : ""}`);
|
|
2378
|
+
lines.push(`- ${h.name} - ${h.kind} - ${h.path}:${h.line} - ${h.relation}${h.inferred ? " (inferred)" : ""}${r.depth > 1 ? ` - depth ${h.depth}` : ""}`);
|
|
2133
2379
|
}
|
|
2134
2380
|
lines.push("", `${r.hits.length} node${r.hits.length === 1 ? "" : "s"}.`);
|
|
2135
2381
|
return `${lines.join("\n")}
|
|
@@ -2252,6 +2498,295 @@ var init_codegraph_format = __esm({
|
|
|
2252
2498
|
}
|
|
2253
2499
|
});
|
|
2254
2500
|
|
|
2501
|
+
// src/codegraph-cluster.ts
|
|
2502
|
+
function localMoves(level, totalWeight) {
|
|
2503
|
+
const { nodes, adj, selfLoops } = level;
|
|
2504
|
+
const degree = new Array(nodes).fill(0);
|
|
2505
|
+
for (let i = 0; i < nodes; i++) {
|
|
2506
|
+
let d = selfLoops[i] * 2;
|
|
2507
|
+
for (const w of adj[i].values()) d += w;
|
|
2508
|
+
degree[i] = d;
|
|
2509
|
+
}
|
|
2510
|
+
const comm = Array.from({ length: nodes }, (_, i) => i);
|
|
2511
|
+
const commDegree = degree.slice();
|
|
2512
|
+
const m2 = totalWeight * 2;
|
|
2513
|
+
for (let sweep = 0; sweep < MAX_SWEEPS; sweep++) {
|
|
2514
|
+
let moved = false;
|
|
2515
|
+
for (let i = 0; i < nodes; i++) {
|
|
2516
|
+
const current = comm[i];
|
|
2517
|
+
const toComm = /* @__PURE__ */ new Map();
|
|
2518
|
+
for (const [j, w] of adj[i]) toComm.set(comm[j], (toComm.get(comm[j]) ?? 0) + w);
|
|
2519
|
+
commDegree[current] -= degree[i];
|
|
2520
|
+
let best = current;
|
|
2521
|
+
let bestGain = (toComm.get(current) ?? 0) - commDegree[current] * degree[i] / m2;
|
|
2522
|
+
for (const [c, w] of [...toComm.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2523
|
+
const gain = w - commDegree[c] * degree[i] / m2;
|
|
2524
|
+
if (gain > bestGain + 1e-12) {
|
|
2525
|
+
best = c;
|
|
2526
|
+
bestGain = gain;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
commDegree[best] += degree[i];
|
|
2530
|
+
if (best !== current) {
|
|
2531
|
+
comm[i] = best;
|
|
2532
|
+
moved = true;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
if (!moved) break;
|
|
2536
|
+
}
|
|
2537
|
+
return comm;
|
|
2538
|
+
}
|
|
2539
|
+
function clusterFiles(files, links) {
|
|
2540
|
+
const sorted = [...new Set(files)].sort();
|
|
2541
|
+
const index = new Map(sorted.map((f, i) => [f, i]));
|
|
2542
|
+
let level = { nodes: sorted.length, adj: sorted.map(() => /* @__PURE__ */ new Map()), selfLoops: new Array(sorted.length).fill(0) };
|
|
2543
|
+
let totalWeight = 0;
|
|
2544
|
+
for (const [a, b, w] of links) {
|
|
2545
|
+
const i = index.get(a);
|
|
2546
|
+
const j = index.get(b);
|
|
2547
|
+
if (i === void 0 || j === void 0 || w <= 0) continue;
|
|
2548
|
+
totalWeight += w;
|
|
2549
|
+
if (i === j) {
|
|
2550
|
+
level.selfLoops[i] += w;
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
level.adj[i].set(j, (level.adj[i].get(j) ?? 0) + w);
|
|
2554
|
+
level.adj[j].set(i, (level.adj[j].get(i) ?? 0) + w);
|
|
2555
|
+
}
|
|
2556
|
+
let membership = sorted.map((_, i) => i);
|
|
2557
|
+
if (totalWeight === 0) return new Map(sorted.map((f, i) => [f, i]));
|
|
2558
|
+
for (let l = 0; l < MAX_LEVELS; l++) {
|
|
2559
|
+
const comm = localMoves(level, totalWeight);
|
|
2560
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2561
|
+
for (const c of comm) if (!ids.has(c)) ids.set(c, ids.size);
|
|
2562
|
+
if (ids.size === level.nodes) break;
|
|
2563
|
+
membership = membership.map((c) => ids.get(comm[c]));
|
|
2564
|
+
const next = { nodes: ids.size, adj: Array.from({ length: ids.size }, () => /* @__PURE__ */ new Map()), selfLoops: new Array(ids.size).fill(0) };
|
|
2565
|
+
for (let i = 0; i < level.nodes; i++) {
|
|
2566
|
+
const ci = ids.get(comm[i]);
|
|
2567
|
+
next.selfLoops[ci] += level.selfLoops[i];
|
|
2568
|
+
for (const [j, w] of level.adj[i]) {
|
|
2569
|
+
const cj = ids.get(comm[j]);
|
|
2570
|
+
if (ci === cj) {
|
|
2571
|
+
if (i < j) next.selfLoops[ci] += w;
|
|
2572
|
+
} else next.adj[ci].set(cj, (next.adj[ci].get(cj) ?? 0) + w);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
level = next;
|
|
2576
|
+
}
|
|
2577
|
+
const members = /* @__PURE__ */ new Map();
|
|
2578
|
+
sorted.forEach((f, i) => {
|
|
2579
|
+
const c = membership[i];
|
|
2580
|
+
const list = members.get(c);
|
|
2581
|
+
if (list) list.push(f);
|
|
2582
|
+
else members.set(c, [f]);
|
|
2583
|
+
});
|
|
2584
|
+
const order = [...members.entries()].sort((a, b) => b[1].length - a[1].length || a[1][0].localeCompare(b[1][0]));
|
|
2585
|
+
const out = /* @__PURE__ */ new Map();
|
|
2586
|
+
order.forEach(([, list], rank) => {
|
|
2587
|
+
for (const f of list) out.set(f, rank);
|
|
2588
|
+
});
|
|
2589
|
+
return out;
|
|
2590
|
+
}
|
|
2591
|
+
var MAX_SWEEPS, MAX_LEVELS;
|
|
2592
|
+
var init_codegraph_cluster = __esm({
|
|
2593
|
+
"src/codegraph-cluster.ts"() {
|
|
2594
|
+
"use strict";
|
|
2595
|
+
MAX_SWEEPS = 20;
|
|
2596
|
+
MAX_LEVELS = 6;
|
|
2597
|
+
}
|
|
2598
|
+
});
|
|
2599
|
+
|
|
2600
|
+
// src/codegraph-report.ts
|
|
2601
|
+
var codegraph_report_exports = {};
|
|
2602
|
+
__export(codegraph_report_exports, {
|
|
2603
|
+
codeGraphReport: () => codeGraphReport,
|
|
2604
|
+
formatReport: () => formatReport
|
|
2605
|
+
});
|
|
2606
|
+
function areaLabel(paths) {
|
|
2607
|
+
const dirs = paths.map((p) => p.split("/").slice(0, -1));
|
|
2608
|
+
let label = [];
|
|
2609
|
+
for (let depth = 1; ; depth++) {
|
|
2610
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2611
|
+
for (const d of dirs) if (d.length >= depth) {
|
|
2612
|
+
const k = d.slice(0, depth).join("/");
|
|
2613
|
+
counts2.set(k, (counts2.get(k) ?? 0) + 1);
|
|
2614
|
+
}
|
|
2615
|
+
const top2 = [...counts2.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
2616
|
+
if (!top2 || top2[1] / paths.length < LABEL_SHARE) break;
|
|
2617
|
+
label = top2[0].split("/");
|
|
2618
|
+
}
|
|
2619
|
+
if (label.length) return `${label.join("/")}/`;
|
|
2620
|
+
if (paths.length === 1) return paths[0];
|
|
2621
|
+
const first = /* @__PURE__ */ new Map();
|
|
2622
|
+
for (const d of dirs) {
|
|
2623
|
+
const k = d[0] ?? "(root)";
|
|
2624
|
+
first.set(k, (first.get(k) ?? 0) + 1);
|
|
2625
|
+
}
|
|
2626
|
+
const [top] = [...first.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
2627
|
+
return `${top}/ (mixed)`;
|
|
2628
|
+
}
|
|
2629
|
+
function codeGraphReport(opts = {}) {
|
|
2630
|
+
const graph = loadFreshGraph(opts.project, opts.refresh ?? true);
|
|
2631
|
+
if (!graph) return null;
|
|
2632
|
+
const nodes = graphNodes(graph);
|
|
2633
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
2634
|
+
const langByPath = new Map(graph.files.map((f) => [f.path, f.lang]));
|
|
2635
|
+
const own = graph.files.filter((f) => !isForeignPath(f.path));
|
|
2636
|
+
const codePaths = new Set(own.filter((f) => isCodeLang(f.lang)).map((f) => f.path));
|
|
2637
|
+
const docPaths = new Set(own.filter((f) => f.lang === "markdown").map((f) => f.path));
|
|
2638
|
+
const weights = /* @__PURE__ */ new Map();
|
|
2639
|
+
const directed = /* @__PURE__ */ new Map();
|
|
2640
|
+
const docRefs = /* @__PURE__ */ new Map();
|
|
2641
|
+
for (const e of graph.edges) {
|
|
2642
|
+
if (e[2] === "contains") continue;
|
|
2643
|
+
const a = byId.get(e[0])?.path;
|
|
2644
|
+
const b = byId.get(e[1])?.path;
|
|
2645
|
+
if (!a || !b || a === b) continue;
|
|
2646
|
+
if (docPaths.has(a) && codePaths.has(b)) {
|
|
2647
|
+
const refs = docRefs.get(a) ?? /* @__PURE__ */ new Map();
|
|
2648
|
+
refs.set(b, (refs.get(b) ?? 0) + 1);
|
|
2649
|
+
docRefs.set(a, refs);
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
if (!codePaths.has(a) || !codePaths.has(b)) continue;
|
|
2653
|
+
const key = a < b ? `${a}
|
|
2654
|
+
${b}` : `${b}
|
|
2655
|
+
${a}`;
|
|
2656
|
+
weights.set(key, (weights.get(key) ?? 0) + 1);
|
|
2657
|
+
directed.set(`${a}
|
|
2658
|
+
${b}`, (directed.get(`${a}
|
|
2659
|
+
${b}`) ?? 0) + 1);
|
|
2660
|
+
}
|
|
2661
|
+
const links = [...weights.entries()].map(([k, w]) => {
|
|
2662
|
+
const [a, b] = k.split("\n");
|
|
2663
|
+
return [a, b, w];
|
|
2664
|
+
});
|
|
2665
|
+
const areaOf = clusterFiles([...codePaths], links);
|
|
2666
|
+
const members = /* @__PURE__ */ new Map();
|
|
2667
|
+
for (const [path, area] of areaOf) {
|
|
2668
|
+
const list = members.get(area);
|
|
2669
|
+
if (list) list.push(path);
|
|
2670
|
+
else members.set(area, [path]);
|
|
2671
|
+
}
|
|
2672
|
+
const deg = inDegree(graph.edges, true);
|
|
2673
|
+
const maxAreas = opts.maxAreas ?? DEFAULT_MAX_AREAS;
|
|
2674
|
+
const ranked = [...members.entries()].filter(([, paths]) => paths.length >= 2).sort((a, b) => b[1].length - a[1].length || a[0] - b[0]);
|
|
2675
|
+
const symbolsOf = /* @__PURE__ */ new Map();
|
|
2676
|
+
for (const n of nodes) {
|
|
2677
|
+
if (n.kind === "file" || isTestPath(n.path)) continue;
|
|
2678
|
+
const area = areaOf.get(n.path);
|
|
2679
|
+
if (area === void 0) continue;
|
|
2680
|
+
const list = symbolsOf.get(area);
|
|
2681
|
+
if (list) list.push(n);
|
|
2682
|
+
else symbolsOf.set(area, [n]);
|
|
2683
|
+
}
|
|
2684
|
+
const keys = /* @__PURE__ */ new Map();
|
|
2685
|
+
const keyOf = (area) => {
|
|
2686
|
+
let key = keys.get(area);
|
|
2687
|
+
if (key) return key;
|
|
2688
|
+
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);
|
|
2689
|
+
keys.set(area, key);
|
|
2690
|
+
return key;
|
|
2691
|
+
};
|
|
2692
|
+
const base = new Map(ranked.map(([area, paths]) => [area, areaLabel(paths)]));
|
|
2693
|
+
const seenLabel = /* @__PURE__ */ new Map();
|
|
2694
|
+
for (const label of base.values()) seenLabel.set(label, (seenLabel.get(label) ?? 0) + 1);
|
|
2695
|
+
const labels = new Map([...base.entries()].map(([area, label]) => {
|
|
2696
|
+
if ((seenLabel.get(label) ?? 0) < 2) return [area, label];
|
|
2697
|
+
const around = keyOf(area)[0]?.name;
|
|
2698
|
+
return [area, around ? `${label} ~ ${around}` : label];
|
|
2699
|
+
}));
|
|
2700
|
+
const docsOf = /* @__PURE__ */ new Map();
|
|
2701
|
+
for (const [doc, refs] of docRefs) {
|
|
2702
|
+
const byArea = /* @__PURE__ */ new Map();
|
|
2703
|
+
for (const [code, w] of refs) {
|
|
2704
|
+
const area = areaOf.get(code);
|
|
2705
|
+
if (area !== void 0) byArea.set(area, (byArea.get(area) ?? 0) + w);
|
|
2706
|
+
}
|
|
2707
|
+
const best = [...byArea.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0];
|
|
2708
|
+
if (!best) continue;
|
|
2709
|
+
const list = docsOf.get(best[0]) ?? [];
|
|
2710
|
+
list.push(doc);
|
|
2711
|
+
docsOf.set(best[0], list);
|
|
2712
|
+
}
|
|
2713
|
+
const areas = ranked.slice(0, maxAreas).map(([area, paths]) => {
|
|
2714
|
+
const inArea = new Set(paths);
|
|
2715
|
+
const out = /* @__PURE__ */ new Map();
|
|
2716
|
+
for (const [key, w] of directed) {
|
|
2717
|
+
const [a, b] = key.split("\n");
|
|
2718
|
+
if (!inArea.has(a) || inArea.has(b)) continue;
|
|
2719
|
+
const target = areaOf.get(b);
|
|
2720
|
+
if (target !== void 0 && target !== area && labels.has(target)) out.set(target, (out.get(target) ?? 0) + w);
|
|
2721
|
+
}
|
|
2722
|
+
return {
|
|
2723
|
+
label: labels.get(area),
|
|
2724
|
+
files: paths.length,
|
|
2725
|
+
languages: [...new Set(paths.map((p) => langByPath.get(p)).filter((l) => !!l))].sort(),
|
|
2726
|
+
keySymbols: keyOf(area),
|
|
2727
|
+
docs: (docsOf.get(area) ?? []).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, AREA_DOCS),
|
|
2728
|
+
dependsOn: [...out.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, DEPENDS_ON).map(([t]) => labels.get(t))
|
|
2729
|
+
};
|
|
2730
|
+
});
|
|
2731
|
+
const titleOf = /* @__PURE__ */ new Map();
|
|
2732
|
+
for (const n of nodes) if (n.kind === "section" && !titleOf.has(n.path)) titleOf.set(n.path, n.name);
|
|
2733
|
+
const docFiles = own.filter((f) => f.lang === "markdown").sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
|
|
2734
|
+
const maxDocs = opts.maxDocs ?? DEFAULT_MAX_DOCS;
|
|
2735
|
+
return {
|
|
2736
|
+
name: graph.name,
|
|
2737
|
+
totals: {
|
|
2738
|
+
files: graph.files.length,
|
|
2739
|
+
symbols: nodes.length - graph.files.length,
|
|
2740
|
+
edges: graph.edges.length,
|
|
2741
|
+
inferred: graph.edges.filter((e) => e[3] === 1).length,
|
|
2742
|
+
docs: docFiles.length
|
|
2743
|
+
},
|
|
2744
|
+
areas,
|
|
2745
|
+
moreAreas: Math.max(0, ranked.length - areas.length),
|
|
2746
|
+
docs: docFiles.slice(0, maxDocs).map((f) => ({ path: f.path, title: titleOf.get(f.path) ?? "" })),
|
|
2747
|
+
moreDocs: Math.max(0, docFiles.length - maxDocs),
|
|
2748
|
+
foreign: graph.files.length - own.length,
|
|
2749
|
+
truncated: graph.truncated
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
function formatReport(r) {
|
|
2753
|
+
const t = r.totals;
|
|
2754
|
+
const lines = [`repo report - ${r.name} - ${t.files} files (${t.docs} docs) - ${t.symbols} symbols - ${t.edges} edges (${t.inferred} inferred by name)`];
|
|
2755
|
+
if (r.foreign) lines.push(`${r.foreign} file(s) of vendored, reference or example material are left out of the areas below.`);
|
|
2756
|
+
lines.push("", "areas (files that depend on each other, largest first):");
|
|
2757
|
+
for (const a of r.areas) {
|
|
2758
|
+
lines.push(`- ${a.label} - ${a.files} files${a.languages.length ? ` - ${a.languages.join(", ")}` : ""}`);
|
|
2759
|
+
if (a.keySymbols.length) lines.push(` key: ${a.keySymbols.map((s) => `${s.name} (${s.path}:${s.line}, ${s.inDegree} in)`).join(", ")}`);
|
|
2760
|
+
if (a.dependsOn.length) lines.push(` uses: ${a.dependsOn.join(", ")}`);
|
|
2761
|
+
if (a.docs.length) lines.push(` docs: ${a.docs.join(", ")}`);
|
|
2762
|
+
}
|
|
2763
|
+
if (r.moreAreas) lines.push(` +${r.moreAreas} smaller area(s)`);
|
|
2764
|
+
if (r.docs.length) {
|
|
2765
|
+
lines.push("", "docs:");
|
|
2766
|
+
for (const d of r.docs) lines.push(`- ${d.path}${d.title ? ` - ${d.title}` : ""}`);
|
|
2767
|
+
if (r.moreDocs) lines.push(` +${r.moreDocs} more (enigma_codegraph_ask finds them by topic)`);
|
|
2768
|
+
}
|
|
2769
|
+
if (r.truncated) lines.push("", "The index hit its size cap; some files are not covered.");
|
|
2770
|
+
return `${lines.join("\n")}
|
|
2771
|
+
`;
|
|
2772
|
+
}
|
|
2773
|
+
var DEFAULT_MAX_AREAS, DEFAULT_MAX_DOCS, KEY_SYMBOLS, AREA_DOCS, DEPENDS_ON, LABEL_SHARE;
|
|
2774
|
+
var init_codegraph_report = __esm({
|
|
2775
|
+
"src/codegraph-report.ts"() {
|
|
2776
|
+
"use strict";
|
|
2777
|
+
init_codegraph();
|
|
2778
|
+
init_codegraph_rank();
|
|
2779
|
+
init_codegraph_extract();
|
|
2780
|
+
init_codegraph_cluster();
|
|
2781
|
+
DEFAULT_MAX_AREAS = 8;
|
|
2782
|
+
DEFAULT_MAX_DOCS = 12;
|
|
2783
|
+
KEY_SYMBOLS = 3;
|
|
2784
|
+
AREA_DOCS = 2;
|
|
2785
|
+
DEPENDS_ON = 2;
|
|
2786
|
+
LABEL_SHARE = 0.6;
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2255
2790
|
// src/codegraph-hook.ts
|
|
2256
2791
|
import { homedir as homedir3 } from "os";
|
|
2257
2792
|
|
|
@@ -2388,14 +2923,17 @@ async function sessionStart(dir) {
|
|
|
2388
2923
|
" - enigma_codegraph_skeleton: a file's whole API surface, no bodies.",
|
|
2389
2924
|
" - enigma_codegraph_grep: every occurrence, grouped by enclosing symbol - when you need them ALL, not the top matches."
|
|
2390
2925
|
].join("\n");
|
|
2391
|
-
const
|
|
2926
|
+
const rep = await Promise.resolve().then(() => (init_codegraph_report(), codegraph_report_exports));
|
|
2927
|
+
const report = rep.codeGraphReport({ project, refresh: false });
|
|
2928
|
+
const map = report ? null : q.codeGraphMap({ project, refresh: false, maxDirs: 8 });
|
|
2929
|
+
const body = report ? rep.formatReport(report) : map ? fmt.formatMap(map) : "";
|
|
2392
2930
|
const fresh = q.codeGraphCheck(project);
|
|
2393
2931
|
const stale = fresh && fresh.stale > 0 ? `
|
|
2394
2932
|
${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",
|
|
2933
|
+
writeStatuslineSnapshot(projectRoot(cg, project, dir), report?.totals.symbols ?? map?.totals.symbols ?? 0, fresh?.stale ?? 0);
|
|
2934
|
+
emit("SessionStart", body ? `${directive}
|
|
2397
2935
|
|
|
2398
|
-
${
|
|
2936
|
+
${body.trimEnd()}${stale}` : directive);
|
|
2399
2937
|
}
|
|
2400
2938
|
function coveringProject(cg, dir) {
|
|
2401
2939
|
const project = cg.findProjectForCwd(dir);
|