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/post-edit.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, writeFileSync as writeFileSync2 } 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)) mkdirSync(dir, { recursive: true });
|
|
241
246
|
writeFileSync2(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
|
});
|
|
@@ -5686,10 +5698,222 @@ var init_codegraph_rank = __esm({
|
|
|
5686
5698
|
}
|
|
5687
5699
|
});
|
|
5688
5700
|
|
|
5701
|
+
// src/codegraph-extract-docs.ts
|
|
5702
|
+
function cap(text) {
|
|
5703
|
+
const t = text.trim();
|
|
5704
|
+
return t.length > MAX_SIGNATURE ? `${t.slice(0, MAX_SIGNATURE)}...` : t;
|
|
5705
|
+
}
|
|
5706
|
+
function indentOf(line) {
|
|
5707
|
+
let n = 0;
|
|
5708
|
+
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
5709
|
+
return n;
|
|
5710
|
+
}
|
|
5711
|
+
function fencedLines(lines) {
|
|
5712
|
+
const inside = new Uint8Array(lines.length);
|
|
5713
|
+
let fence = null;
|
|
5714
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5715
|
+
const m = /^\s*(`{3,}|~{3,})/.exec(lines[i]);
|
|
5716
|
+
if (fence) {
|
|
5717
|
+
inside[i] = 1;
|
|
5718
|
+
if (m && m[1][0] === fence[0] && m[1].length >= fence.length) fence = null;
|
|
5719
|
+
continue;
|
|
5720
|
+
}
|
|
5721
|
+
if (m) {
|
|
5722
|
+
fence = m[1];
|
|
5723
|
+
inside[i] = 1;
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
return inside;
|
|
5727
|
+
}
|
|
5728
|
+
function markdownSymbols(lines) {
|
|
5729
|
+
const fenced = fencedLines(lines);
|
|
5730
|
+
const heads = [];
|
|
5731
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5732
|
+
if (fenced[i]) continue;
|
|
5733
|
+
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(lines[i]);
|
|
5734
|
+
if (m && m[1].length <= MAX_HEADING_LEVEL) heads.push({ level: m[1].length, name: m[2].replace(/[`*_]/g, "").trim(), line: i + 1 });
|
|
5735
|
+
}
|
|
5736
|
+
return heads.filter((h) => h.name).map((h, idx) => {
|
|
5737
|
+
const next = heads.slice(idx + 1).find((o) => o.level <= h.level);
|
|
5738
|
+
return { name: h.name, kind: "section", line: h.line, endLine: next ? next.line - 1 : lines.length, signature: cap(lines[h.line - 1]) };
|
|
5739
|
+
});
|
|
5740
|
+
}
|
|
5741
|
+
function sqlEnd(lines, start) {
|
|
5742
|
+
let dollar = false;
|
|
5743
|
+
for (let i = start; i < lines.length; i++) {
|
|
5744
|
+
const parts = lines[i].replace(/--.*$/, "").split("$$");
|
|
5745
|
+
for (let p = 0; p < parts.length; p++) {
|
|
5746
|
+
if (p > 0) dollar = !dollar;
|
|
5747
|
+
if (!dollar && parts[p].includes(";")) return i + 1;
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
return lines.length;
|
|
5751
|
+
}
|
|
5752
|
+
function sqlSymbols(lines) {
|
|
5753
|
+
const rules = [
|
|
5754
|
+
["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")],
|
|
5755
|
+
["function", new RegExp(String.raw`^\s*create\s+(?:or\s+replace\s+)?(?:function|procedure)\s+${SQL_NAME}`, "i")],
|
|
5756
|
+
["type", new RegExp(String.raw`^\s*create\s+type\s+${SQL_NAME}`, "i")]
|
|
5757
|
+
];
|
|
5758
|
+
const out = [];
|
|
5759
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5760
|
+
for (const [kind, re] of rules) {
|
|
5761
|
+
const m = re.exec(lines[i]);
|
|
5762
|
+
if (!m) continue;
|
|
5763
|
+
out.push({ name: m[1], kind, line: i + 1, endLine: sqlEnd(lines, i), signature: cap(lines[i].replace(/\(.*$/, "")) });
|
|
5764
|
+
break;
|
|
5765
|
+
}
|
|
5766
|
+
}
|
|
5767
|
+
return out;
|
|
5768
|
+
}
|
|
5769
|
+
function prismaSymbols(lines) {
|
|
5770
|
+
const out = [];
|
|
5771
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5772
|
+
const m = /^\s*(model|view|enum|type)\s+([A-Za-z_]\w*)\s*\{/.exec(lines[i]);
|
|
5773
|
+
if (!m) continue;
|
|
5774
|
+
let end = i;
|
|
5775
|
+
while (end < lines.length - 1 && !/^\s*\}/.test(lines[end])) end++;
|
|
5776
|
+
const kind = m[1] === "enum" ? "enum" : m[1] === "type" ? "type" : "table";
|
|
5777
|
+
out.push({ name: m[2], kind, line: i + 1, endLine: end + 1, signature: cap(`${m[1]} ${m[2]}`) });
|
|
5778
|
+
}
|
|
5779
|
+
return out;
|
|
5780
|
+
}
|
|
5781
|
+
function yamlEnd(lines, start) {
|
|
5782
|
+
const base = indentOf(lines[start]);
|
|
5783
|
+
let end = start + 1;
|
|
5784
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
5785
|
+
const line = lines[i];
|
|
5786
|
+
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
|
5787
|
+
if (indentOf(line) <= base && !line.trimStart().startsWith("- ")) break;
|
|
5788
|
+
end = i + 1;
|
|
5789
|
+
}
|
|
5790
|
+
return end;
|
|
5791
|
+
}
|
|
5792
|
+
function yamlSymbols(lines) {
|
|
5793
|
+
const out = [];
|
|
5794
|
+
const KEY = /^(\s*)([A-Za-z0-9_][\w.-]*)\s*:(?:\s|$)/;
|
|
5795
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5796
|
+
const m = KEY.exec(lines[i]);
|
|
5797
|
+
if (!m || m[1].length) continue;
|
|
5798
|
+
const end = yamlEnd(lines, i);
|
|
5799
|
+
out.push({ name: m[2], kind: "key", line: i + 1, endLine: end, signature: cap(lines[i]) });
|
|
5800
|
+
if (!YAML_CONTAINERS.has(m[2])) continue;
|
|
5801
|
+
let childIndent = -1;
|
|
5802
|
+
for (let j = i + 1; j < end; j++) {
|
|
5803
|
+
const c = KEY.exec(lines[j]);
|
|
5804
|
+
if (!c || !c[1].length) continue;
|
|
5805
|
+
if (childIndent === -1) childIndent = c[1].length;
|
|
5806
|
+
if (c[1].length !== childIndent) continue;
|
|
5807
|
+
out.push({ name: c[2], kind: "key", line: j + 1, endLine: yamlEnd(lines, j), signature: cap(`${m[2]}.${c[2]}`) });
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
return out;
|
|
5811
|
+
}
|
|
5812
|
+
function tomlSymbols(lines) {
|
|
5813
|
+
const heads = [];
|
|
5814
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5815
|
+
const m = /^\s*\[\[?\s*([^\]]+?)\s*\]\]?\s*(?:#.*)?$/.exec(lines[i]);
|
|
5816
|
+
if (m) heads.push({ name: m[1], line: i + 1 });
|
|
5817
|
+
}
|
|
5818
|
+
return heads.map((h, idx) => ({
|
|
5819
|
+
name: h.name,
|
|
5820
|
+
kind: "key",
|
|
5821
|
+
line: h.line,
|
|
5822
|
+
endLine: idx + 1 < heads.length ? heads[idx + 1].line - 1 : lines.length,
|
|
5823
|
+
signature: cap(lines[h.line - 1])
|
|
5824
|
+
}));
|
|
5825
|
+
}
|
|
5826
|
+
function markdownLinks(lines) {
|
|
5827
|
+
const fenced = fencedLines(lines);
|
|
5828
|
+
const out = /* @__PURE__ */ new Set();
|
|
5829
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5830
|
+
if (fenced[i]) continue;
|
|
5831
|
+
const re = /\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g;
|
|
5832
|
+
let m;
|
|
5833
|
+
while (m = re.exec(lines[i])) {
|
|
5834
|
+
const target = m[1].replace(/[#?].*$/, "");
|
|
5835
|
+
if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//")) continue;
|
|
5836
|
+
try {
|
|
5837
|
+
out.add(decodeURI(target));
|
|
5838
|
+
} catch {
|
|
5839
|
+
out.add(target);
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
return [...out];
|
|
5844
|
+
}
|
|
5845
|
+
function markdownCodeMentions(content) {
|
|
5846
|
+
const lines = content.split("\n");
|
|
5847
|
+
const fenced = fencedLines(lines);
|
|
5848
|
+
const out = [];
|
|
5849
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5850
|
+
if (fenced[i]) continue;
|
|
5851
|
+
const re = /`([A-Za-z_$][\w$]*)(?:\(\))?`/g;
|
|
5852
|
+
let m;
|
|
5853
|
+
while (m = re.exec(lines[i])) out.push({ name: m[1], line: i + 1 });
|
|
5854
|
+
}
|
|
5855
|
+
return out;
|
|
5856
|
+
}
|
|
5857
|
+
function extractDocFile(lang, content) {
|
|
5858
|
+
const lines = content.split("\n");
|
|
5859
|
+
const out = (() => {
|
|
5860
|
+
switch (lang) {
|
|
5861
|
+
case "markdown":
|
|
5862
|
+
return { symbols: markdownSymbols(lines), imports: markdownLinks(lines) };
|
|
5863
|
+
case "sql":
|
|
5864
|
+
return { symbols: sqlSymbols(lines), imports: [] };
|
|
5865
|
+
case "prisma":
|
|
5866
|
+
return { symbols: prismaSymbols(lines), imports: [] };
|
|
5867
|
+
case "yaml":
|
|
5868
|
+
return { symbols: yamlSymbols(lines), imports: [] };
|
|
5869
|
+
case "toml":
|
|
5870
|
+
return { symbols: tomlSymbols(lines), imports: [] };
|
|
5871
|
+
default:
|
|
5872
|
+
return { symbols: [], imports: [] };
|
|
5873
|
+
}
|
|
5874
|
+
})();
|
|
5875
|
+
for (const s of out.symbols) while (s.endLine > s.line && !(lines[s.endLine - 1] ?? "").trim()) s.endLine--;
|
|
5876
|
+
return out;
|
|
5877
|
+
}
|
|
5878
|
+
var DOC_LANG_BY_EXT, DOC_LANGS, DOC_REFERENCE_LANGS, YAML_CONTAINERS, MAX_SIGNATURE, MAX_HEADING_LEVEL, SQL_NAME;
|
|
5879
|
+
var init_codegraph_extract_docs = __esm({
|
|
5880
|
+
"src/codegraph-extract-docs.ts"() {
|
|
5881
|
+
"use strict";
|
|
5882
|
+
DOC_LANG_BY_EXT = {
|
|
5883
|
+
".md": "markdown",
|
|
5884
|
+
".mdx": "markdown",
|
|
5885
|
+
".sql": "sql",
|
|
5886
|
+
".prisma": "prisma",
|
|
5887
|
+
".yaml": "yaml",
|
|
5888
|
+
".yml": "yaml",
|
|
5889
|
+
".toml": "toml"
|
|
5890
|
+
};
|
|
5891
|
+
DOC_LANGS = new Set(Object.values(DOC_LANG_BY_EXT));
|
|
5892
|
+
DOC_REFERENCE_LANGS = /* @__PURE__ */ new Set(["sql", "prisma"]);
|
|
5893
|
+
YAML_CONTAINERS = /* @__PURE__ */ new Set(["services", "jobs", "workflows", "volumes", "networks"]);
|
|
5894
|
+
MAX_SIGNATURE = 200;
|
|
5895
|
+
MAX_HEADING_LEVEL = 3;
|
|
5896
|
+
SQL_NAME = String.raw`(?:[\w"\x60\[\]]+\.)?["\x60\[]?([A-Za-z_][\w$]*)["\x60\]]?`;
|
|
5897
|
+
}
|
|
5898
|
+
});
|
|
5899
|
+
|
|
5689
5900
|
// src/codegraph-extract.ts
|
|
5690
5901
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5691
5902
|
import { extname as extname2, join as join6, relative as relative2, resolve as resolve3 } from "path";
|
|
5692
5903
|
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
5904
|
+
function isCodeLang(lang) {
|
|
5905
|
+
return !DOC_LANGS.has(lang);
|
|
5906
|
+
}
|
|
5907
|
+
function isDocPath(path) {
|
|
5908
|
+
return !!DOC_LANG_BY_EXT[extname2(path).toLowerCase()];
|
|
5909
|
+
}
|
|
5910
|
+
function isForeignPath(rel) {
|
|
5911
|
+
return FOREIGN_DOC_DIRS.test(rel);
|
|
5912
|
+
}
|
|
5913
|
+
function pickDocs(root, docs, room) {
|
|
5914
|
+
const depth = (p) => relPath(root, p).split("/").length;
|
|
5915
|
+
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)));
|
|
5916
|
+
}
|
|
5693
5917
|
function symRules(lang) {
|
|
5694
5918
|
switch (lang) {
|
|
5695
5919
|
case "ts":
|
|
@@ -5854,7 +6078,7 @@ function lineResolver(content) {
|
|
|
5854
6078
|
return lo + 1;
|
|
5855
6079
|
};
|
|
5856
6080
|
}
|
|
5857
|
-
function
|
|
6081
|
+
function indentOf2(line) {
|
|
5858
6082
|
let n = 0;
|
|
5859
6083
|
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
5860
6084
|
return n;
|
|
@@ -5920,18 +6144,18 @@ function braceEnd(lines, startIdx, lang) {
|
|
|
5920
6144
|
return lines.length;
|
|
5921
6145
|
}
|
|
5922
6146
|
function indentEnd(lines, startIdx) {
|
|
5923
|
-
const base =
|
|
6147
|
+
const base = indentOf2(lines[startIdx]);
|
|
5924
6148
|
let end = startIdx + 1;
|
|
5925
6149
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
5926
6150
|
const line = lines[i];
|
|
5927
6151
|
if (!line.trim()) continue;
|
|
5928
|
-
if (
|
|
6152
|
+
if (indentOf2(line) <= base) break;
|
|
5929
6153
|
end = i + 1;
|
|
5930
6154
|
}
|
|
5931
6155
|
return end;
|
|
5932
6156
|
}
|
|
5933
6157
|
function clampToSiblings(symbols, lines) {
|
|
5934
|
-
const indentAt = (line) =>
|
|
6158
|
+
const indentAt = (line) => indentOf2(lines[line - 1] ?? "");
|
|
5935
6159
|
for (let i = 0; i < symbols.length; i++) {
|
|
5936
6160
|
const s = symbols[i];
|
|
5937
6161
|
for (let j = i + 1; j < symbols.length; j++) {
|
|
@@ -5944,14 +6168,15 @@ function clampToSiblings(symbols, lines) {
|
|
|
5944
6168
|
}
|
|
5945
6169
|
}
|
|
5946
6170
|
function signatureOf(line) {
|
|
5947
|
-
const
|
|
6171
|
+
const MAX_SIGNATURE2 = 200;
|
|
5948
6172
|
let sig = line.trim();
|
|
5949
6173
|
const brace = sig.indexOf("{");
|
|
5950
6174
|
if (brace > 0) sig = sig.slice(0, brace).trim();
|
|
5951
6175
|
if (sig.endsWith(":")) sig = sig.slice(0, -1).trim();
|
|
5952
|
-
return sig.length >
|
|
6176
|
+
return sig.length > MAX_SIGNATURE2 ? `${sig.slice(0, MAX_SIGNATURE2)}...` : sig;
|
|
5953
6177
|
}
|
|
5954
6178
|
function extractFile(lang, content) {
|
|
6179
|
+
if (DOC_LANGS.has(lang)) return extractDocFile(lang, content);
|
|
5955
6180
|
const lineAt = lineResolver(content);
|
|
5956
6181
|
const lines = content.split("\n");
|
|
5957
6182
|
const braced = BRACE_LANGS.has(lang);
|
|
@@ -5993,25 +6218,27 @@ function gitSourceFiles(root) {
|
|
|
5993
6218
|
const out = gitList(root, ["--cached", "--others", "--exclude-standard"]);
|
|
5994
6219
|
if (out === null) return null;
|
|
5995
6220
|
const nested = existsSync5(join6(root, ".gitmodules")) ? gitList(root, ["--recurse-submodules"]) : null;
|
|
5996
|
-
const
|
|
6221
|
+
const code = [];
|
|
6222
|
+
const docs = [];
|
|
5997
6223
|
const seen = /* @__PURE__ */ new Set();
|
|
5998
6224
|
for (const rel of [...out.split("\0"), ...(nested ?? "").split("\0")]) {
|
|
5999
|
-
if (
|
|
6225
|
+
if (code.length >= MAX_FILES) break;
|
|
6000
6226
|
if (!rel || seen.has(rel)) continue;
|
|
6001
6227
|
seen.add(rel);
|
|
6002
6228
|
if (!LANG_BY_EXT[extname2(rel).toLowerCase()]) continue;
|
|
6003
6229
|
if (rel.split("/").some((seg) => seg.startsWith(".") || IGNORE_DIRS.has(seg))) continue;
|
|
6004
|
-
|
|
6230
|
+
(isDocPath(rel) ? docs : code).push(join6(root, rel));
|
|
6005
6231
|
}
|
|
6006
|
-
return
|
|
6232
|
+
return [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
6007
6233
|
}
|
|
6008
6234
|
function walk(root) {
|
|
6009
6235
|
const tracked = gitSourceFiles(root);
|
|
6010
6236
|
if (tracked && tracked.length) return { files: tracked, truncated: tracked.length >= MAX_FILES };
|
|
6011
|
-
const
|
|
6237
|
+
const code = [];
|
|
6238
|
+
const docs = [];
|
|
6012
6239
|
const queue = [root];
|
|
6013
6240
|
let visited = 0;
|
|
6014
|
-
for (let head = 0; head < queue.length &&
|
|
6241
|
+
for (let head = 0; head < queue.length && code.length < MAX_FILES && visited < MAX_ENTRIES; head++) {
|
|
6015
6242
|
let entries;
|
|
6016
6243
|
try {
|
|
6017
6244
|
entries = readdirSync3(queue[head], { withFileTypes: true });
|
|
@@ -6019,16 +6246,17 @@ function walk(root) {
|
|
|
6019
6246
|
continue;
|
|
6020
6247
|
}
|
|
6021
6248
|
for (const e of entries) {
|
|
6022
|
-
if (
|
|
6249
|
+
if (code.length >= MAX_FILES || ++visited >= MAX_ENTRIES) break;
|
|
6023
6250
|
if (e.isDirectory()) {
|
|
6024
6251
|
if (!e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) queue.push(join6(queue[head], e.name));
|
|
6025
6252
|
continue;
|
|
6026
6253
|
}
|
|
6027
|
-
if (!e.isFile()) continue;
|
|
6028
|
-
|
|
6254
|
+
if (!e.isFile() || !LANG_BY_EXT[extname2(e.name).toLowerCase()]) continue;
|
|
6255
|
+
(isDocPath(e.name) ? docs : code).push(join6(queue[head], e.name));
|
|
6029
6256
|
}
|
|
6030
6257
|
}
|
|
6031
|
-
|
|
6258
|
+
const ordered = [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
6259
|
+
return { files: ordered, truncated: code.length >= MAX_FILES || visited >= MAX_ENTRIES };
|
|
6032
6260
|
}
|
|
6033
6261
|
function relPath(root, abs) {
|
|
6034
6262
|
return relative2(root, abs).split("\\").join("/");
|
|
@@ -6086,10 +6314,11 @@ function statSourceFiles(root) {
|
|
|
6086
6314
|
}
|
|
6087
6315
|
return out;
|
|
6088
6316
|
}
|
|
6089
|
-
var IGNORE_DIRS, LANG_BY_EXT, MAX_FILES, MAX_FILE_BYTES, BRACE_LANGS, HASH_COMMENT_LANGS, PREPROCESSOR_LANGS, MAX_ENTRIES;
|
|
6317
|
+
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;
|
|
6090
6318
|
var init_codegraph_extract = __esm({
|
|
6091
6319
|
"src/codegraph-extract.ts"() {
|
|
6092
6320
|
"use strict";
|
|
6321
|
+
init_codegraph_extract_docs();
|
|
6093
6322
|
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
6094
6323
|
"node_modules",
|
|
6095
6324
|
".git",
|
|
@@ -6114,7 +6343,7 @@ var init_codegraph_extract = __esm({
|
|
|
6114
6343
|
".gradle",
|
|
6115
6344
|
".dart_tool"
|
|
6116
6345
|
]);
|
|
6117
|
-
|
|
6346
|
+
CODE_LANG_BY_EXT = {
|
|
6118
6347
|
".ts": "ts",
|
|
6119
6348
|
".tsx": "ts",
|
|
6120
6349
|
".mts": "ts",
|
|
@@ -6140,6 +6369,9 @@ var init_codegraph_extract = __esm({
|
|
|
6140
6369
|
".hpp": "cpp",
|
|
6141
6370
|
".hh": "cpp"
|
|
6142
6371
|
};
|
|
6372
|
+
LANG_BY_EXT = { ...CODE_LANG_BY_EXT, ...DOC_LANG_BY_EXT };
|
|
6373
|
+
FOREIGN_DOC_DIRS = /(?:^|\/)(?:references?|examples?|samples?|fixtures?|third[_-]party|vendor(?:ed)?)\//i;
|
|
6374
|
+
MAX_DOC_FILES = 1500;
|
|
6143
6375
|
MAX_FILES = 8e3;
|
|
6144
6376
|
MAX_FILE_BYTES = 512 * 1024;
|
|
6145
6377
|
BRACE_LANGS = /* @__PURE__ */ new Set(["ts", "js", "go", "rust", "java", "csharp", "kotlin", "php", "c", "cpp"]);
|
|
@@ -6355,12 +6587,12 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6355
6587
|
}
|
|
6356
6588
|
const edges = [];
|
|
6357
6589
|
const seen = /* @__PURE__ */ new Set();
|
|
6358
|
-
const push = (from, to, rel) => {
|
|
6590
|
+
const push = (from, to, rel, inferred = false) => {
|
|
6359
6591
|
if (edges.length >= MAX_EDGES || from === to) return;
|
|
6360
6592
|
const key = `${from}|${to}|${rel}`;
|
|
6361
6593
|
if (seen.has(key)) return;
|
|
6362
6594
|
seen.add(key);
|
|
6363
|
-
edges.push([from, to, rel]);
|
|
6595
|
+
edges.push(inferred ? [from, to, rel, 1] : [from, to, rel]);
|
|
6364
6596
|
};
|
|
6365
6597
|
for (const f of files) {
|
|
6366
6598
|
for (const span of spansByFile.get(f.path) ?? []) push(f.path, span.id, "contains");
|
|
@@ -6389,12 +6621,14 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6389
6621
|
const candidates = defsByName.get(name)?.filter((c) => imported?.has(c.path) && reachable(c.path));
|
|
6390
6622
|
return candidates?.length === 1 ? candidates[0].id : null;
|
|
6391
6623
|
};
|
|
6624
|
+
const extracted = (id) => id ? { id, inferred: false } : null;
|
|
6625
|
+
const inferred = (id) => id ? { id, inferred: true } : null;
|
|
6392
6626
|
if (!f.bindings) {
|
|
6393
6627
|
return (name) => {
|
|
6394
|
-
if (local.has(name)) return local.get(name)
|
|
6395
|
-
if (imported?.size) return unique(name);
|
|
6628
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
6629
|
+
if (imported?.size) return inferred(unique(name));
|
|
6396
6630
|
const all = defsByName.get(name)?.filter((c) => reachable(c.path));
|
|
6397
|
-
return all?.length === 1 ? all[0].id : null;
|
|
6631
|
+
return all?.length === 1 ? inferred(all[0].id) : null;
|
|
6398
6632
|
};
|
|
6399
6633
|
}
|
|
6400
6634
|
const bound = new Set(f.bindings.names);
|
|
@@ -6406,10 +6640,10 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6406
6640
|
return (name, memberOf) => {
|
|
6407
6641
|
if (memberOf !== void 0) {
|
|
6408
6642
|
const path = namespaces.get(memberOf);
|
|
6409
|
-
return path ? idByPathName.get(`${path}#${name}`)
|
|
6643
|
+
return path ? extracted(idByPathName.get(`${path}#${name}`)) : null;
|
|
6410
6644
|
}
|
|
6411
|
-
if (local.has(name)) return local.get(name)
|
|
6412
|
-
return bound.has(name) ? unique(name) : null;
|
|
6645
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
6646
|
+
return bound.has(name) ? extracted(unique(name)) : null;
|
|
6413
6647
|
};
|
|
6414
6648
|
}
|
|
6415
6649
|
const bodies = [];
|
|
@@ -6423,7 +6657,7 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6423
6657
|
const spans = spansByFile.get(f.path) ?? [];
|
|
6424
6658
|
const declLines = new Set(f.symbols.map((s) => s.line));
|
|
6425
6659
|
const lines = content.split("\n");
|
|
6426
|
-
indexBodies(bodies, f.path, spans, lines);
|
|
6660
|
+
indexBodies(bodies, f.path, spans, lines, DOC_LANGS.has(f.lang) ? MAX_DOC_BODY_TOKENS : MAX_BODY_TOKENS);
|
|
6427
6661
|
const bind = binderFor(f);
|
|
6428
6662
|
const strict = !!f.bindings;
|
|
6429
6663
|
for (const s of f.symbols) {
|
|
@@ -6432,9 +6666,17 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6432
6666
|
if (!self) continue;
|
|
6433
6667
|
for (const [rel, name] of inheritanceTargets(s.signature)) {
|
|
6434
6668
|
const target = bind(name);
|
|
6435
|
-
if (target) push(self.id, target, rel);
|
|
6669
|
+
if (target) push(self.id, target.id, rel, target.inferred);
|
|
6670
|
+
}
|
|
6671
|
+
}
|
|
6672
|
+
if (f.lang === "markdown") {
|
|
6673
|
+
for (const { name, line } of markdownCodeMentions(content)) {
|
|
6674
|
+
if (name.length < MIN_DOC_MENTION) continue;
|
|
6675
|
+
const code = defsByName.get(name)?.filter((c) => !DOC_LANGS.has(langByPath.get(c.path) ?? ""));
|
|
6676
|
+
if (code?.length === 1) push(enclosingSymbol(spans, line) ?? f.path, code[0].id, "references", true);
|
|
6436
6677
|
}
|
|
6437
6678
|
}
|
|
6679
|
+
if (DOC_LANGS.has(f.lang) && !DOC_REFERENCE_LANGS.has(f.lang)) continue;
|
|
6438
6680
|
for (let i = 0; i < lines.length; i++) {
|
|
6439
6681
|
const lineNo = i + 1;
|
|
6440
6682
|
if (declLines.has(lineNo)) continue;
|
|
@@ -6447,31 +6689,32 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6447
6689
|
if (name.length < MIN_REF_NAME || !defsByName.has(name)) continue;
|
|
6448
6690
|
const memberOf = strict ? /([A-Za-z_$][\w$]*)\s*\.\s*$/.exec(line.slice(0, m.index))?.[1] : void 0;
|
|
6449
6691
|
const target = bind(name, memberOf);
|
|
6450
|
-
if (!target || target === from) continue;
|
|
6692
|
+
if (!target || target.id === from) continue;
|
|
6451
6693
|
const after = line.slice(m.index + name.length);
|
|
6452
|
-
|
|
6694
|
+
const calls = !DOC_LANGS.has(f.lang) && /^\s*\(/.test(after);
|
|
6695
|
+
push(from, target.id, calls ? "calls" : "references", target.inferred);
|
|
6453
6696
|
}
|
|
6454
6697
|
}
|
|
6455
6698
|
}
|
|
6456
6699
|
return { edges, bodies };
|
|
6457
6700
|
}
|
|
6458
|
-
function indexBodies(out, path, spans, lines) {
|
|
6701
|
+
function indexBodies(out, path, spans, lines, limit) {
|
|
6459
6702
|
const covered = new Uint8Array(lines.length + 1);
|
|
6460
6703
|
for (const s of spans) {
|
|
6461
6704
|
const text = lines.slice(s.line - 1, s.endLine).join("\n");
|
|
6462
6705
|
for (let l = s.line; l <= Math.min(s.endLine, lines.length); l++) covered[l] = 1;
|
|
6463
|
-
const bag = topTokens(text);
|
|
6706
|
+
const bag = topTokens(text, limit);
|
|
6464
6707
|
if (bag.length) out.push([s.id, bag]);
|
|
6465
6708
|
}
|
|
6466
6709
|
const residual = [];
|
|
6467
6710
|
for (let l = 1; l <= lines.length; l++) if (!covered[l]) residual.push(lines[l - 1]);
|
|
6468
|
-
const fileBag = topTokens(residual.join("\n"));
|
|
6711
|
+
const fileBag = topTokens(residual.join("\n"), limit);
|
|
6469
6712
|
if (fileBag.length) out.push([path, fileBag]);
|
|
6470
6713
|
}
|
|
6471
|
-
function topTokens(text) {
|
|
6714
|
+
function topTokens(text, limit) {
|
|
6472
6715
|
const m = /* @__PURE__ */ new Map();
|
|
6473
6716
|
for (const t of tokenize(text)) m.set(t, (m.get(t) ?? 0) + 1);
|
|
6474
|
-
return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0,
|
|
6717
|
+
return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit);
|
|
6475
6718
|
}
|
|
6476
6719
|
function isManagedPath(dir) {
|
|
6477
6720
|
const managed = resolve4(join7(enigmaHome(), ".enigma"));
|
|
@@ -6689,17 +6932,18 @@ function codeGraphAvailable() {
|
|
|
6689
6932
|
function codeGraphStatus() {
|
|
6690
6933
|
return { enabled: readConfig().config.codeGraph, available: true, projects: listProjects().length };
|
|
6691
6934
|
}
|
|
6692
|
-
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;
|
|
6935
|
+
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;
|
|
6693
6936
|
var init_codegraph = __esm({
|
|
6694
6937
|
"src/codegraph.ts"() {
|
|
6695
6938
|
"use strict";
|
|
6696
6939
|
init_util();
|
|
6697
6940
|
init_config();
|
|
6698
6941
|
init_codegraph_rank();
|
|
6942
|
+
init_codegraph_extract_docs();
|
|
6699
6943
|
init_codegraph_extract();
|
|
6700
6944
|
init_codegraph_extract();
|
|
6701
6945
|
init_codegraph_types();
|
|
6702
|
-
GRAPH_VERSION =
|
|
6946
|
+
GRAPH_VERSION = 5;
|
|
6703
6947
|
RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".rb", ".php", ".cs", ".kt"];
|
|
6704
6948
|
LANG_EXTS = {
|
|
6705
6949
|
ts: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
@@ -6715,6 +6959,7 @@ var init_codegraph = __esm({
|
|
|
6715
6959
|
};
|
|
6716
6960
|
LANG_INDEX = { python: ["__init__"], go: ["mod"], rust: ["mod"] };
|
|
6717
6961
|
MIN_REF_NAME = 3;
|
|
6962
|
+
MIN_DOC_MENTION = 4;
|
|
6718
6963
|
MAX_EDGES = 4e5;
|
|
6719
6964
|
LANG_FAMILY = {
|
|
6720
6965
|
ts: "js",
|
|
@@ -6731,6 +6976,7 @@ var init_codegraph = __esm({
|
|
|
6731
6976
|
EXT_ORDER = orders(RESOLVE_EXTS, LANG_EXTS);
|
|
6732
6977
|
INDEX_ORDER = orders(INDEX_NAMES, LANG_INDEX);
|
|
6733
6978
|
MAX_BODY_TOKENS = 80;
|
|
6979
|
+
MAX_DOC_BODY_TOKENS = 24;
|
|
6734
6980
|
ROOT_MARKERS = [".git", "package.json", "go.mod", "pyproject.toml", "Cargo.toml", "pom.xml", "composer.json"];
|
|
6735
6981
|
MAX_ROOT_WALK = 6;
|
|
6736
6982
|
}
|
|
@@ -6963,11 +7209,11 @@ ${result.note}` : structural.fallthrough;
|
|
|
6963
7209
|
}
|
|
6964
7210
|
function adjacency(edges, direction) {
|
|
6965
7211
|
const adj = /* @__PURE__ */ new Map();
|
|
6966
|
-
for (const [source, target, rel] of edges) {
|
|
7212
|
+
for (const [source, target, rel, guess] of edges) {
|
|
6967
7213
|
if (!WALK_RELATIONS.has(rel)) continue;
|
|
6968
7214
|
const key = direction === "in" ? target : source;
|
|
6969
7215
|
const other = direction === "in" ? source : target;
|
|
6970
|
-
const entry = { other, relation: rel };
|
|
7216
|
+
const entry = { other, relation: rel, inferred: guess === 1 };
|
|
6971
7217
|
const arr = adj.get(key);
|
|
6972
7218
|
if (arr) arr.push(entry);
|
|
6973
7219
|
else adj.set(key, [entry]);
|
|
@@ -6996,13 +7242,13 @@ function codeGraphTrace(symbol, opts = {}) {
|
|
|
6996
7242
|
for (let d = 1; d <= depth && frontier.length; d++) {
|
|
6997
7243
|
const next = [];
|
|
6998
7244
|
for (const current of frontier) {
|
|
6999
|
-
for (const { other, relation } of adj.get(current) ?? []) {
|
|
7245
|
+
for (const { other, relation, inferred } of adj.get(current) ?? []) {
|
|
7000
7246
|
if (visited.has(other)) continue;
|
|
7001
7247
|
visited.add(other);
|
|
7002
7248
|
const node = loaded.byId.get(other);
|
|
7003
7249
|
if (!node) continue;
|
|
7004
7250
|
if (inPrefix !== void 0 && !underPrefix(node.path, inPrefix)) continue;
|
|
7005
|
-
hits.push({ id: other, name: node.name, kind: node.kind, path: node.path, line: node.line, relation, depth: d });
|
|
7251
|
+
hits.push({ id: other, name: node.name, kind: node.kind, path: node.path, line: node.line, relation, depth: d, ...inferred ? { inferred } : {} });
|
|
7006
7252
|
next.push(other);
|
|
7007
7253
|
}
|
|
7008
7254
|
}
|
|
@@ -7041,8 +7287,8 @@ function dirKey(path, depth) {
|
|
|
7041
7287
|
const segments = path.split("/");
|
|
7042
7288
|
return segments.slice(0, Math.min(depth, segments.length)).join("/");
|
|
7043
7289
|
}
|
|
7044
|
-
function topHubs(nodes, deg,
|
|
7045
|
-
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,
|
|
7290
|
+
function topHubs(nodes, deg, cap2) {
|
|
7291
|
+
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);
|
|
7046
7292
|
}
|
|
7047
7293
|
function codeGraphMap(opts = {}) {
|
|
7048
7294
|
const loaded = load(opts);
|
|
@@ -7270,7 +7516,7 @@ ${r.note ?? empty}
|
|
|
7270
7516
|
const lines = [head, ""];
|
|
7271
7517
|
lines.push(`defined at: ${r.matched.map((m) => `${m.path}:${m.line}`).join(", ")}`, "");
|
|
7272
7518
|
for (const h of r.hits) {
|
|
7273
|
-
lines.push(`- ${h.name} - ${h.kind} - ${h.path}:${h.line} - ${h.relation}${r.depth > 1 ? ` - depth ${h.depth}` : ""}`);
|
|
7519
|
+
lines.push(`- ${h.name} - ${h.kind} - ${h.path}:${h.line} - ${h.relation}${h.inferred ? " (inferred)" : ""}${r.depth > 1 ? ` - depth ${h.depth}` : ""}`);
|
|
7274
7520
|
}
|
|
7275
7521
|
lines.push("", `${r.hits.length} node${r.hits.length === 1 ? "" : "s"}.`);
|
|
7276
7522
|
return `${lines.join("\n")}
|
|
@@ -7393,6 +7639,295 @@ var init_codegraph_format = __esm({
|
|
|
7393
7639
|
}
|
|
7394
7640
|
});
|
|
7395
7641
|
|
|
7642
|
+
// src/codegraph-cluster.ts
|
|
7643
|
+
function localMoves(level, totalWeight) {
|
|
7644
|
+
const { nodes, adj, selfLoops } = level;
|
|
7645
|
+
const degree = new Array(nodes).fill(0);
|
|
7646
|
+
for (let i = 0; i < nodes; i++) {
|
|
7647
|
+
let d = selfLoops[i] * 2;
|
|
7648
|
+
for (const w of adj[i].values()) d += w;
|
|
7649
|
+
degree[i] = d;
|
|
7650
|
+
}
|
|
7651
|
+
const comm = Array.from({ length: nodes }, (_, i) => i);
|
|
7652
|
+
const commDegree = degree.slice();
|
|
7653
|
+
const m2 = totalWeight * 2;
|
|
7654
|
+
for (let sweep = 0; sweep < MAX_SWEEPS; sweep++) {
|
|
7655
|
+
let moved = false;
|
|
7656
|
+
for (let i = 0; i < nodes; i++) {
|
|
7657
|
+
const current = comm[i];
|
|
7658
|
+
const toComm = /* @__PURE__ */ new Map();
|
|
7659
|
+
for (const [j, w] of adj[i]) toComm.set(comm[j], (toComm.get(comm[j]) ?? 0) + w);
|
|
7660
|
+
commDegree[current] -= degree[i];
|
|
7661
|
+
let best = current;
|
|
7662
|
+
let bestGain = (toComm.get(current) ?? 0) - commDegree[current] * degree[i] / m2;
|
|
7663
|
+
for (const [c, w] of [...toComm.entries()].sort((a, b) => a[0] - b[0])) {
|
|
7664
|
+
const gain = w - commDegree[c] * degree[i] / m2;
|
|
7665
|
+
if (gain > bestGain + 1e-12) {
|
|
7666
|
+
best = c;
|
|
7667
|
+
bestGain = gain;
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
commDegree[best] += degree[i];
|
|
7671
|
+
if (best !== current) {
|
|
7672
|
+
comm[i] = best;
|
|
7673
|
+
moved = true;
|
|
7674
|
+
}
|
|
7675
|
+
}
|
|
7676
|
+
if (!moved) break;
|
|
7677
|
+
}
|
|
7678
|
+
return comm;
|
|
7679
|
+
}
|
|
7680
|
+
function clusterFiles(files, links) {
|
|
7681
|
+
const sorted = [...new Set(files)].sort();
|
|
7682
|
+
const index = new Map(sorted.map((f, i) => [f, i]));
|
|
7683
|
+
let level = { nodes: sorted.length, adj: sorted.map(() => /* @__PURE__ */ new Map()), selfLoops: new Array(sorted.length).fill(0) };
|
|
7684
|
+
let totalWeight = 0;
|
|
7685
|
+
for (const [a, b, w] of links) {
|
|
7686
|
+
const i = index.get(a);
|
|
7687
|
+
const j = index.get(b);
|
|
7688
|
+
if (i === void 0 || j === void 0 || w <= 0) continue;
|
|
7689
|
+
totalWeight += w;
|
|
7690
|
+
if (i === j) {
|
|
7691
|
+
level.selfLoops[i] += w;
|
|
7692
|
+
continue;
|
|
7693
|
+
}
|
|
7694
|
+
level.adj[i].set(j, (level.adj[i].get(j) ?? 0) + w);
|
|
7695
|
+
level.adj[j].set(i, (level.adj[j].get(i) ?? 0) + w);
|
|
7696
|
+
}
|
|
7697
|
+
let membership = sorted.map((_, i) => i);
|
|
7698
|
+
if (totalWeight === 0) return new Map(sorted.map((f, i) => [f, i]));
|
|
7699
|
+
for (let l = 0; l < MAX_LEVELS; l++) {
|
|
7700
|
+
const comm = localMoves(level, totalWeight);
|
|
7701
|
+
const ids = /* @__PURE__ */ new Map();
|
|
7702
|
+
for (const c of comm) if (!ids.has(c)) ids.set(c, ids.size);
|
|
7703
|
+
if (ids.size === level.nodes) break;
|
|
7704
|
+
membership = membership.map((c) => ids.get(comm[c]));
|
|
7705
|
+
const next = { nodes: ids.size, adj: Array.from({ length: ids.size }, () => /* @__PURE__ */ new Map()), selfLoops: new Array(ids.size).fill(0) };
|
|
7706
|
+
for (let i = 0; i < level.nodes; i++) {
|
|
7707
|
+
const ci = ids.get(comm[i]);
|
|
7708
|
+
next.selfLoops[ci] += level.selfLoops[i];
|
|
7709
|
+
for (const [j, w] of level.adj[i]) {
|
|
7710
|
+
const cj = ids.get(comm[j]);
|
|
7711
|
+
if (ci === cj) {
|
|
7712
|
+
if (i < j) next.selfLoops[ci] += w;
|
|
7713
|
+
} else next.adj[ci].set(cj, (next.adj[ci].get(cj) ?? 0) + w);
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7716
|
+
level = next;
|
|
7717
|
+
}
|
|
7718
|
+
const members = /* @__PURE__ */ new Map();
|
|
7719
|
+
sorted.forEach((f, i) => {
|
|
7720
|
+
const c = membership[i];
|
|
7721
|
+
const list = members.get(c);
|
|
7722
|
+
if (list) list.push(f);
|
|
7723
|
+
else members.set(c, [f]);
|
|
7724
|
+
});
|
|
7725
|
+
const order = [...members.entries()].sort((a, b) => b[1].length - a[1].length || a[1][0].localeCompare(b[1][0]));
|
|
7726
|
+
const out = /* @__PURE__ */ new Map();
|
|
7727
|
+
order.forEach(([, list], rank) => {
|
|
7728
|
+
for (const f of list) out.set(f, rank);
|
|
7729
|
+
});
|
|
7730
|
+
return out;
|
|
7731
|
+
}
|
|
7732
|
+
var MAX_SWEEPS, MAX_LEVELS;
|
|
7733
|
+
var init_codegraph_cluster = __esm({
|
|
7734
|
+
"src/codegraph-cluster.ts"() {
|
|
7735
|
+
"use strict";
|
|
7736
|
+
MAX_SWEEPS = 20;
|
|
7737
|
+
MAX_LEVELS = 6;
|
|
7738
|
+
}
|
|
7739
|
+
});
|
|
7740
|
+
|
|
7741
|
+
// src/codegraph-report.ts
|
|
7742
|
+
var codegraph_report_exports = {};
|
|
7743
|
+
__export(codegraph_report_exports, {
|
|
7744
|
+
codeGraphReport: () => codeGraphReport,
|
|
7745
|
+
formatReport: () => formatReport
|
|
7746
|
+
});
|
|
7747
|
+
function areaLabel(paths) {
|
|
7748
|
+
const dirs = paths.map((p) => p.split("/").slice(0, -1));
|
|
7749
|
+
let label = [];
|
|
7750
|
+
for (let depth = 1; ; depth++) {
|
|
7751
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
7752
|
+
for (const d of dirs) if (d.length >= depth) {
|
|
7753
|
+
const k = d.slice(0, depth).join("/");
|
|
7754
|
+
counts2.set(k, (counts2.get(k) ?? 0) + 1);
|
|
7755
|
+
}
|
|
7756
|
+
const top2 = [...counts2.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
7757
|
+
if (!top2 || top2[1] / paths.length < LABEL_SHARE) break;
|
|
7758
|
+
label = top2[0].split("/");
|
|
7759
|
+
}
|
|
7760
|
+
if (label.length) return `${label.join("/")}/`;
|
|
7761
|
+
if (paths.length === 1) return paths[0];
|
|
7762
|
+
const first = /* @__PURE__ */ new Map();
|
|
7763
|
+
for (const d of dirs) {
|
|
7764
|
+
const k = d[0] ?? "(root)";
|
|
7765
|
+
first.set(k, (first.get(k) ?? 0) + 1);
|
|
7766
|
+
}
|
|
7767
|
+
const [top] = [...first.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
7768
|
+
return `${top}/ (mixed)`;
|
|
7769
|
+
}
|
|
7770
|
+
function codeGraphReport(opts = {}) {
|
|
7771
|
+
const graph = loadFreshGraph(opts.project, opts.refresh ?? true);
|
|
7772
|
+
if (!graph) return null;
|
|
7773
|
+
const nodes = graphNodes(graph);
|
|
7774
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
7775
|
+
const langByPath = new Map(graph.files.map((f) => [f.path, f.lang]));
|
|
7776
|
+
const own = graph.files.filter((f) => !isForeignPath(f.path));
|
|
7777
|
+
const codePaths = new Set(own.filter((f) => isCodeLang(f.lang)).map((f) => f.path));
|
|
7778
|
+
const docPaths = new Set(own.filter((f) => f.lang === "markdown").map((f) => f.path));
|
|
7779
|
+
const weights = /* @__PURE__ */ new Map();
|
|
7780
|
+
const directed = /* @__PURE__ */ new Map();
|
|
7781
|
+
const docRefs = /* @__PURE__ */ new Map();
|
|
7782
|
+
for (const e of graph.edges) {
|
|
7783
|
+
if (e[2] === "contains") continue;
|
|
7784
|
+
const a = byId.get(e[0])?.path;
|
|
7785
|
+
const b = byId.get(e[1])?.path;
|
|
7786
|
+
if (!a || !b || a === b) continue;
|
|
7787
|
+
if (docPaths.has(a) && codePaths.has(b)) {
|
|
7788
|
+
const refs = docRefs.get(a) ?? /* @__PURE__ */ new Map();
|
|
7789
|
+
refs.set(b, (refs.get(b) ?? 0) + 1);
|
|
7790
|
+
docRefs.set(a, refs);
|
|
7791
|
+
continue;
|
|
7792
|
+
}
|
|
7793
|
+
if (!codePaths.has(a) || !codePaths.has(b)) continue;
|
|
7794
|
+
const key = a < b ? `${a}
|
|
7795
|
+
${b}` : `${b}
|
|
7796
|
+
${a}`;
|
|
7797
|
+
weights.set(key, (weights.get(key) ?? 0) + 1);
|
|
7798
|
+
directed.set(`${a}
|
|
7799
|
+
${b}`, (directed.get(`${a}
|
|
7800
|
+
${b}`) ?? 0) + 1);
|
|
7801
|
+
}
|
|
7802
|
+
const links = [...weights.entries()].map(([k, w]) => {
|
|
7803
|
+
const [a, b] = k.split("\n");
|
|
7804
|
+
return [a, b, w];
|
|
7805
|
+
});
|
|
7806
|
+
const areaOf = clusterFiles([...codePaths], links);
|
|
7807
|
+
const members = /* @__PURE__ */ new Map();
|
|
7808
|
+
for (const [path, area] of areaOf) {
|
|
7809
|
+
const list = members.get(area);
|
|
7810
|
+
if (list) list.push(path);
|
|
7811
|
+
else members.set(area, [path]);
|
|
7812
|
+
}
|
|
7813
|
+
const deg = inDegree(graph.edges, true);
|
|
7814
|
+
const maxAreas = opts.maxAreas ?? DEFAULT_MAX_AREAS;
|
|
7815
|
+
const ranked = [...members.entries()].filter(([, paths]) => paths.length >= 2).sort((a, b) => b[1].length - a[1].length || a[0] - b[0]);
|
|
7816
|
+
const symbolsOf = /* @__PURE__ */ new Map();
|
|
7817
|
+
for (const n of nodes) {
|
|
7818
|
+
if (n.kind === "file" || isTestPath(n.path)) continue;
|
|
7819
|
+
const area = areaOf.get(n.path);
|
|
7820
|
+
if (area === void 0) continue;
|
|
7821
|
+
const list = symbolsOf.get(area);
|
|
7822
|
+
if (list) list.push(n);
|
|
7823
|
+
else symbolsOf.set(area, [n]);
|
|
7824
|
+
}
|
|
7825
|
+
const keys = /* @__PURE__ */ new Map();
|
|
7826
|
+
const keyOf = (area) => {
|
|
7827
|
+
let key = keys.get(area);
|
|
7828
|
+
if (key) return key;
|
|
7829
|
+
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);
|
|
7830
|
+
keys.set(area, key);
|
|
7831
|
+
return key;
|
|
7832
|
+
};
|
|
7833
|
+
const base = new Map(ranked.map(([area, paths]) => [area, areaLabel(paths)]));
|
|
7834
|
+
const seenLabel = /* @__PURE__ */ new Map();
|
|
7835
|
+
for (const label of base.values()) seenLabel.set(label, (seenLabel.get(label) ?? 0) + 1);
|
|
7836
|
+
const labels = new Map([...base.entries()].map(([area, label]) => {
|
|
7837
|
+
if ((seenLabel.get(label) ?? 0) < 2) return [area, label];
|
|
7838
|
+
const around = keyOf(area)[0]?.name;
|
|
7839
|
+
return [area, around ? `${label} ~ ${around}` : label];
|
|
7840
|
+
}));
|
|
7841
|
+
const docsOf = /* @__PURE__ */ new Map();
|
|
7842
|
+
for (const [doc, refs] of docRefs) {
|
|
7843
|
+
const byArea = /* @__PURE__ */ new Map();
|
|
7844
|
+
for (const [code, w] of refs) {
|
|
7845
|
+
const area = areaOf.get(code);
|
|
7846
|
+
if (area !== void 0) byArea.set(area, (byArea.get(area) ?? 0) + w);
|
|
7847
|
+
}
|
|
7848
|
+
const best = [...byArea.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0];
|
|
7849
|
+
if (!best) continue;
|
|
7850
|
+
const list = docsOf.get(best[0]) ?? [];
|
|
7851
|
+
list.push(doc);
|
|
7852
|
+
docsOf.set(best[0], list);
|
|
7853
|
+
}
|
|
7854
|
+
const areas = ranked.slice(0, maxAreas).map(([area, paths]) => {
|
|
7855
|
+
const inArea = new Set(paths);
|
|
7856
|
+
const out = /* @__PURE__ */ new Map();
|
|
7857
|
+
for (const [key, w] of directed) {
|
|
7858
|
+
const [a, b] = key.split("\n");
|
|
7859
|
+
if (!inArea.has(a) || inArea.has(b)) continue;
|
|
7860
|
+
const target = areaOf.get(b);
|
|
7861
|
+
if (target !== void 0 && target !== area && labels.has(target)) out.set(target, (out.get(target) ?? 0) + w);
|
|
7862
|
+
}
|
|
7863
|
+
return {
|
|
7864
|
+
label: labels.get(area),
|
|
7865
|
+
files: paths.length,
|
|
7866
|
+
languages: [...new Set(paths.map((p) => langByPath.get(p)).filter((l) => !!l))].sort(),
|
|
7867
|
+
keySymbols: keyOf(area),
|
|
7868
|
+
docs: (docsOf.get(area) ?? []).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, AREA_DOCS),
|
|
7869
|
+
dependsOn: [...out.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, DEPENDS_ON).map(([t]) => labels.get(t))
|
|
7870
|
+
};
|
|
7871
|
+
});
|
|
7872
|
+
const titleOf = /* @__PURE__ */ new Map();
|
|
7873
|
+
for (const n of nodes) if (n.kind === "section" && !titleOf.has(n.path)) titleOf.set(n.path, n.name);
|
|
7874
|
+
const docFiles = own.filter((f) => f.lang === "markdown").sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
|
|
7875
|
+
const maxDocs = opts.maxDocs ?? DEFAULT_MAX_DOCS;
|
|
7876
|
+
return {
|
|
7877
|
+
name: graph.name,
|
|
7878
|
+
totals: {
|
|
7879
|
+
files: graph.files.length,
|
|
7880
|
+
symbols: nodes.length - graph.files.length,
|
|
7881
|
+
edges: graph.edges.length,
|
|
7882
|
+
inferred: graph.edges.filter((e) => e[3] === 1).length,
|
|
7883
|
+
docs: docFiles.length
|
|
7884
|
+
},
|
|
7885
|
+
areas,
|
|
7886
|
+
moreAreas: Math.max(0, ranked.length - areas.length),
|
|
7887
|
+
docs: docFiles.slice(0, maxDocs).map((f) => ({ path: f.path, title: titleOf.get(f.path) ?? "" })),
|
|
7888
|
+
moreDocs: Math.max(0, docFiles.length - maxDocs),
|
|
7889
|
+
foreign: graph.files.length - own.length,
|
|
7890
|
+
truncated: graph.truncated
|
|
7891
|
+
};
|
|
7892
|
+
}
|
|
7893
|
+
function formatReport(r) {
|
|
7894
|
+
const t = r.totals;
|
|
7895
|
+
const lines = [`repo report - ${r.name} - ${t.files} files (${t.docs} docs) - ${t.symbols} symbols - ${t.edges} edges (${t.inferred} inferred by name)`];
|
|
7896
|
+
if (r.foreign) lines.push(`${r.foreign} file(s) of vendored, reference or example material are left out of the areas below.`);
|
|
7897
|
+
lines.push("", "areas (files that depend on each other, largest first):");
|
|
7898
|
+
for (const a of r.areas) {
|
|
7899
|
+
lines.push(`- ${a.label} - ${a.files} files${a.languages.length ? ` - ${a.languages.join(", ")}` : ""}`);
|
|
7900
|
+
if (a.keySymbols.length) lines.push(` key: ${a.keySymbols.map((s) => `${s.name} (${s.path}:${s.line}, ${s.inDegree} in)`).join(", ")}`);
|
|
7901
|
+
if (a.dependsOn.length) lines.push(` uses: ${a.dependsOn.join(", ")}`);
|
|
7902
|
+
if (a.docs.length) lines.push(` docs: ${a.docs.join(", ")}`);
|
|
7903
|
+
}
|
|
7904
|
+
if (r.moreAreas) lines.push(` +${r.moreAreas} smaller area(s)`);
|
|
7905
|
+
if (r.docs.length) {
|
|
7906
|
+
lines.push("", "docs:");
|
|
7907
|
+
for (const d of r.docs) lines.push(`- ${d.path}${d.title ? ` - ${d.title}` : ""}`);
|
|
7908
|
+
if (r.moreDocs) lines.push(` +${r.moreDocs} more (enigma_codegraph_ask finds them by topic)`);
|
|
7909
|
+
}
|
|
7910
|
+
if (r.truncated) lines.push("", "The index hit its size cap; some files are not covered.");
|
|
7911
|
+
return `${lines.join("\n")}
|
|
7912
|
+
`;
|
|
7913
|
+
}
|
|
7914
|
+
var DEFAULT_MAX_AREAS, DEFAULT_MAX_DOCS, KEY_SYMBOLS, AREA_DOCS, DEPENDS_ON, LABEL_SHARE;
|
|
7915
|
+
var init_codegraph_report = __esm({
|
|
7916
|
+
"src/codegraph-report.ts"() {
|
|
7917
|
+
"use strict";
|
|
7918
|
+
init_codegraph();
|
|
7919
|
+
init_codegraph_rank();
|
|
7920
|
+
init_codegraph_extract();
|
|
7921
|
+
init_codegraph_cluster();
|
|
7922
|
+
DEFAULT_MAX_AREAS = 8;
|
|
7923
|
+
DEFAULT_MAX_DOCS = 12;
|
|
7924
|
+
KEY_SYMBOLS = 3;
|
|
7925
|
+
AREA_DOCS = 2;
|
|
7926
|
+
DEPENDS_ON = 2;
|
|
7927
|
+
LABEL_SHARE = 0.6;
|
|
7928
|
+
}
|
|
7929
|
+
});
|
|
7930
|
+
|
|
7396
7931
|
// src/codegraph-hook.ts
|
|
7397
7932
|
var codegraph_hook_exports = {};
|
|
7398
7933
|
__export(codegraph_hook_exports, {
|
|
@@ -7471,14 +8006,17 @@ async function sessionStart(dir) {
|
|
|
7471
8006
|
" - enigma_codegraph_skeleton: a file's whole API surface, no bodies.",
|
|
7472
8007
|
" - enigma_codegraph_grep: every occurrence, grouped by enclosing symbol - when you need them ALL, not the top matches."
|
|
7473
8008
|
].join("\n");
|
|
7474
|
-
const
|
|
8009
|
+
const rep = await Promise.resolve().then(() => (init_codegraph_report(), codegraph_report_exports));
|
|
8010
|
+
const report = rep.codeGraphReport({ project, refresh: false });
|
|
8011
|
+
const map = report ? null : q.codeGraphMap({ project, refresh: false, maxDirs: 8 });
|
|
8012
|
+
const body = report ? rep.formatReport(report) : map ? fmt.formatMap(map) : "";
|
|
7475
8013
|
const fresh = q.codeGraphCheck(project);
|
|
7476
8014
|
const stale = fresh && fresh.stale > 0 ? `
|
|
7477
8015
|
${fresh.stale} file(s) changed since the last index; the next query refreshes them.` : "";
|
|
7478
|
-
writeStatuslineSnapshot(projectRoot(cg, project, dir), map?.totals.symbols ?? 0, fresh?.stale ?? 0);
|
|
7479
|
-
emit("SessionStart",
|
|
8016
|
+
writeStatuslineSnapshot(projectRoot(cg, project, dir), report?.totals.symbols ?? map?.totals.symbols ?? 0, fresh?.stale ?? 0);
|
|
8017
|
+
emit("SessionStart", body ? `${directive}
|
|
7480
8018
|
|
|
7481
|
-
${
|
|
8019
|
+
${body.trimEnd()}${stale}` : directive);
|
|
7482
8020
|
}
|
|
7483
8021
|
function coveringProject(cg, dir) {
|
|
7484
8022
|
const project = cg.findProjectForCwd(dir);
|