enigma-cli 1.59.1 → 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/commands/handoff.md +48 -0
- package/assets/mods/enigma-relay/.claude-plugin/plugin.json +1 -0
- package/assets/mods/enigma-relay/hooks/hooks.json +1 -0
- package/assets/mods/enigma-relay/hooks/register.ts +86 -0
- package/assets/mods/enigma-relay/tests/relay.test.ts +65 -0
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/skill.json +1 -1
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/dist/ci-watch.js +2 -0
- package/dist/codegraph-hook.js +576 -48
- package/dist/post-edit.js +576 -48
- package/package.json +1 -1
package/dist/post-edit.js
CHANGED
|
@@ -304,6 +304,8 @@ var init_config = __esm({
|
|
|
304
304
|
gateTrivialLines: 20,
|
|
305
305
|
subagentLimit: 4,
|
|
306
306
|
compactWindow: 4e5,
|
|
307
|
+
relay: true,
|
|
308
|
+
relayAt: 3e5,
|
|
307
309
|
planSessionLimit: 0,
|
|
308
310
|
planWeeklyLimit: 0,
|
|
309
311
|
planWeeklySonnetLimit: 0,
|
|
@@ -5686,10 +5688,222 @@ var init_codegraph_rank = __esm({
|
|
|
5686
5688
|
}
|
|
5687
5689
|
});
|
|
5688
5690
|
|
|
5691
|
+
// src/codegraph-extract-docs.ts
|
|
5692
|
+
function cap(text) {
|
|
5693
|
+
const t = text.trim();
|
|
5694
|
+
return t.length > MAX_SIGNATURE ? `${t.slice(0, MAX_SIGNATURE)}...` : t;
|
|
5695
|
+
}
|
|
5696
|
+
function indentOf(line) {
|
|
5697
|
+
let n = 0;
|
|
5698
|
+
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
5699
|
+
return n;
|
|
5700
|
+
}
|
|
5701
|
+
function fencedLines(lines) {
|
|
5702
|
+
const inside = new Uint8Array(lines.length);
|
|
5703
|
+
let fence = null;
|
|
5704
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5705
|
+
const m = /^\s*(`{3,}|~{3,})/.exec(lines[i]);
|
|
5706
|
+
if (fence) {
|
|
5707
|
+
inside[i] = 1;
|
|
5708
|
+
if (m && m[1][0] === fence[0] && m[1].length >= fence.length) fence = null;
|
|
5709
|
+
continue;
|
|
5710
|
+
}
|
|
5711
|
+
if (m) {
|
|
5712
|
+
fence = m[1];
|
|
5713
|
+
inside[i] = 1;
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
return inside;
|
|
5717
|
+
}
|
|
5718
|
+
function markdownSymbols(lines) {
|
|
5719
|
+
const fenced = fencedLines(lines);
|
|
5720
|
+
const heads = [];
|
|
5721
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5722
|
+
if (fenced[i]) continue;
|
|
5723
|
+
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(lines[i]);
|
|
5724
|
+
if (m && m[1].length <= MAX_HEADING_LEVEL) heads.push({ level: m[1].length, name: m[2].replace(/[`*_]/g, "").trim(), line: i + 1 });
|
|
5725
|
+
}
|
|
5726
|
+
return heads.filter((h) => h.name).map((h, idx) => {
|
|
5727
|
+
const next = heads.slice(idx + 1).find((o) => o.level <= h.level);
|
|
5728
|
+
return { name: h.name, kind: "section", line: h.line, endLine: next ? next.line - 1 : lines.length, signature: cap(lines[h.line - 1]) };
|
|
5729
|
+
});
|
|
5730
|
+
}
|
|
5731
|
+
function sqlEnd(lines, start) {
|
|
5732
|
+
let dollar = false;
|
|
5733
|
+
for (let i = start; i < lines.length; i++) {
|
|
5734
|
+
const parts = lines[i].replace(/--.*$/, "").split("$$");
|
|
5735
|
+
for (let p = 0; p < parts.length; p++) {
|
|
5736
|
+
if (p > 0) dollar = !dollar;
|
|
5737
|
+
if (!dollar && parts[p].includes(";")) return i + 1;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
return lines.length;
|
|
5741
|
+
}
|
|
5742
|
+
function sqlSymbols(lines) {
|
|
5743
|
+
const rules = [
|
|
5744
|
+
["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")],
|
|
5745
|
+
["function", new RegExp(String.raw`^\s*create\s+(?:or\s+replace\s+)?(?:function|procedure)\s+${SQL_NAME}`, "i")],
|
|
5746
|
+
["type", new RegExp(String.raw`^\s*create\s+type\s+${SQL_NAME}`, "i")]
|
|
5747
|
+
];
|
|
5748
|
+
const out = [];
|
|
5749
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5750
|
+
for (const [kind, re] of rules) {
|
|
5751
|
+
const m = re.exec(lines[i]);
|
|
5752
|
+
if (!m) continue;
|
|
5753
|
+
out.push({ name: m[1], kind, line: i + 1, endLine: sqlEnd(lines, i), signature: cap(lines[i].replace(/\(.*$/, "")) });
|
|
5754
|
+
break;
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
return out;
|
|
5758
|
+
}
|
|
5759
|
+
function prismaSymbols(lines) {
|
|
5760
|
+
const out = [];
|
|
5761
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5762
|
+
const m = /^\s*(model|view|enum|type)\s+([A-Za-z_]\w*)\s*\{/.exec(lines[i]);
|
|
5763
|
+
if (!m) continue;
|
|
5764
|
+
let end = i;
|
|
5765
|
+
while (end < lines.length - 1 && !/^\s*\}/.test(lines[end])) end++;
|
|
5766
|
+
const kind = m[1] === "enum" ? "enum" : m[1] === "type" ? "type" : "table";
|
|
5767
|
+
out.push({ name: m[2], kind, line: i + 1, endLine: end + 1, signature: cap(`${m[1]} ${m[2]}`) });
|
|
5768
|
+
}
|
|
5769
|
+
return out;
|
|
5770
|
+
}
|
|
5771
|
+
function yamlEnd(lines, start) {
|
|
5772
|
+
const base = indentOf(lines[start]);
|
|
5773
|
+
let end = start + 1;
|
|
5774
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
5775
|
+
const line = lines[i];
|
|
5776
|
+
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
|
5777
|
+
if (indentOf(line) <= base && !line.trimStart().startsWith("- ")) break;
|
|
5778
|
+
end = i + 1;
|
|
5779
|
+
}
|
|
5780
|
+
return end;
|
|
5781
|
+
}
|
|
5782
|
+
function yamlSymbols(lines) {
|
|
5783
|
+
const out = [];
|
|
5784
|
+
const KEY = /^(\s*)([A-Za-z0-9_][\w.-]*)\s*:(?:\s|$)/;
|
|
5785
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5786
|
+
const m = KEY.exec(lines[i]);
|
|
5787
|
+
if (!m || m[1].length) continue;
|
|
5788
|
+
const end = yamlEnd(lines, i);
|
|
5789
|
+
out.push({ name: m[2], kind: "key", line: i + 1, endLine: end, signature: cap(lines[i]) });
|
|
5790
|
+
if (!YAML_CONTAINERS.has(m[2])) continue;
|
|
5791
|
+
let childIndent = -1;
|
|
5792
|
+
for (let j = i + 1; j < end; j++) {
|
|
5793
|
+
const c = KEY.exec(lines[j]);
|
|
5794
|
+
if (!c || !c[1].length) continue;
|
|
5795
|
+
if (childIndent === -1) childIndent = c[1].length;
|
|
5796
|
+
if (c[1].length !== childIndent) continue;
|
|
5797
|
+
out.push({ name: c[2], kind: "key", line: j + 1, endLine: yamlEnd(lines, j), signature: cap(`${m[2]}.${c[2]}`) });
|
|
5798
|
+
}
|
|
5799
|
+
}
|
|
5800
|
+
return out;
|
|
5801
|
+
}
|
|
5802
|
+
function tomlSymbols(lines) {
|
|
5803
|
+
const heads = [];
|
|
5804
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5805
|
+
const m = /^\s*\[\[?\s*([^\]]+?)\s*\]\]?\s*(?:#.*)?$/.exec(lines[i]);
|
|
5806
|
+
if (m) heads.push({ name: m[1], line: i + 1 });
|
|
5807
|
+
}
|
|
5808
|
+
return heads.map((h, idx) => ({
|
|
5809
|
+
name: h.name,
|
|
5810
|
+
kind: "key",
|
|
5811
|
+
line: h.line,
|
|
5812
|
+
endLine: idx + 1 < heads.length ? heads[idx + 1].line - 1 : lines.length,
|
|
5813
|
+
signature: cap(lines[h.line - 1])
|
|
5814
|
+
}));
|
|
5815
|
+
}
|
|
5816
|
+
function markdownLinks(lines) {
|
|
5817
|
+
const fenced = fencedLines(lines);
|
|
5818
|
+
const out = /* @__PURE__ */ new Set();
|
|
5819
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5820
|
+
if (fenced[i]) continue;
|
|
5821
|
+
const re = /\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g;
|
|
5822
|
+
let m;
|
|
5823
|
+
while (m = re.exec(lines[i])) {
|
|
5824
|
+
const target = m[1].replace(/[#?].*$/, "");
|
|
5825
|
+
if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//")) continue;
|
|
5826
|
+
try {
|
|
5827
|
+
out.add(decodeURI(target));
|
|
5828
|
+
} catch {
|
|
5829
|
+
out.add(target);
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
return [...out];
|
|
5834
|
+
}
|
|
5835
|
+
function markdownCodeMentions(content) {
|
|
5836
|
+
const lines = content.split("\n");
|
|
5837
|
+
const fenced = fencedLines(lines);
|
|
5838
|
+
const out = [];
|
|
5839
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5840
|
+
if (fenced[i]) continue;
|
|
5841
|
+
const re = /`([A-Za-z_$][\w$]*)(?:\(\))?`/g;
|
|
5842
|
+
let m;
|
|
5843
|
+
while (m = re.exec(lines[i])) out.push({ name: m[1], line: i + 1 });
|
|
5844
|
+
}
|
|
5845
|
+
return out;
|
|
5846
|
+
}
|
|
5847
|
+
function extractDocFile(lang, content) {
|
|
5848
|
+
const lines = content.split("\n");
|
|
5849
|
+
const out = (() => {
|
|
5850
|
+
switch (lang) {
|
|
5851
|
+
case "markdown":
|
|
5852
|
+
return { symbols: markdownSymbols(lines), imports: markdownLinks(lines) };
|
|
5853
|
+
case "sql":
|
|
5854
|
+
return { symbols: sqlSymbols(lines), imports: [] };
|
|
5855
|
+
case "prisma":
|
|
5856
|
+
return { symbols: prismaSymbols(lines), imports: [] };
|
|
5857
|
+
case "yaml":
|
|
5858
|
+
return { symbols: yamlSymbols(lines), imports: [] };
|
|
5859
|
+
case "toml":
|
|
5860
|
+
return { symbols: tomlSymbols(lines), imports: [] };
|
|
5861
|
+
default:
|
|
5862
|
+
return { symbols: [], imports: [] };
|
|
5863
|
+
}
|
|
5864
|
+
})();
|
|
5865
|
+
for (const s of out.symbols) while (s.endLine > s.line && !(lines[s.endLine - 1] ?? "").trim()) s.endLine--;
|
|
5866
|
+
return out;
|
|
5867
|
+
}
|
|
5868
|
+
var DOC_LANG_BY_EXT, DOC_LANGS, DOC_REFERENCE_LANGS, YAML_CONTAINERS, MAX_SIGNATURE, MAX_HEADING_LEVEL, SQL_NAME;
|
|
5869
|
+
var init_codegraph_extract_docs = __esm({
|
|
5870
|
+
"src/codegraph-extract-docs.ts"() {
|
|
5871
|
+
"use strict";
|
|
5872
|
+
DOC_LANG_BY_EXT = {
|
|
5873
|
+
".md": "markdown",
|
|
5874
|
+
".mdx": "markdown",
|
|
5875
|
+
".sql": "sql",
|
|
5876
|
+
".prisma": "prisma",
|
|
5877
|
+
".yaml": "yaml",
|
|
5878
|
+
".yml": "yaml",
|
|
5879
|
+
".toml": "toml"
|
|
5880
|
+
};
|
|
5881
|
+
DOC_LANGS = new Set(Object.values(DOC_LANG_BY_EXT));
|
|
5882
|
+
DOC_REFERENCE_LANGS = /* @__PURE__ */ new Set(["sql", "prisma"]);
|
|
5883
|
+
YAML_CONTAINERS = /* @__PURE__ */ new Set(["services", "jobs", "workflows", "volumes", "networks"]);
|
|
5884
|
+
MAX_SIGNATURE = 200;
|
|
5885
|
+
MAX_HEADING_LEVEL = 3;
|
|
5886
|
+
SQL_NAME = String.raw`(?:[\w"\x60\[\]]+\.)?["\x60\[]?([A-Za-z_][\w$]*)["\x60\]]?`;
|
|
5887
|
+
}
|
|
5888
|
+
});
|
|
5889
|
+
|
|
5689
5890
|
// src/codegraph-extract.ts
|
|
5690
5891
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5691
5892
|
import { extname as extname2, join as join6, relative as relative2, resolve as resolve3 } from "path";
|
|
5692
5893
|
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
5894
|
+
function isCodeLang(lang) {
|
|
5895
|
+
return !DOC_LANGS.has(lang);
|
|
5896
|
+
}
|
|
5897
|
+
function isDocPath(path) {
|
|
5898
|
+
return !!DOC_LANG_BY_EXT[extname2(path).toLowerCase()];
|
|
5899
|
+
}
|
|
5900
|
+
function isForeignPath(rel) {
|
|
5901
|
+
return FOREIGN_DOC_DIRS.test(rel);
|
|
5902
|
+
}
|
|
5903
|
+
function pickDocs(root, docs, room) {
|
|
5904
|
+
const depth = (p) => relPath(root, p).split("/").length;
|
|
5905
|
+
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)));
|
|
5906
|
+
}
|
|
5693
5907
|
function symRules(lang) {
|
|
5694
5908
|
switch (lang) {
|
|
5695
5909
|
case "ts":
|
|
@@ -5854,7 +6068,7 @@ function lineResolver(content) {
|
|
|
5854
6068
|
return lo + 1;
|
|
5855
6069
|
};
|
|
5856
6070
|
}
|
|
5857
|
-
function
|
|
6071
|
+
function indentOf2(line) {
|
|
5858
6072
|
let n = 0;
|
|
5859
6073
|
while (n < line.length && (line[n] === " " || line[n] === " ")) n++;
|
|
5860
6074
|
return n;
|
|
@@ -5920,18 +6134,18 @@ function braceEnd(lines, startIdx, lang) {
|
|
|
5920
6134
|
return lines.length;
|
|
5921
6135
|
}
|
|
5922
6136
|
function indentEnd(lines, startIdx) {
|
|
5923
|
-
const base =
|
|
6137
|
+
const base = indentOf2(lines[startIdx]);
|
|
5924
6138
|
let end = startIdx + 1;
|
|
5925
6139
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
5926
6140
|
const line = lines[i];
|
|
5927
6141
|
if (!line.trim()) continue;
|
|
5928
|
-
if (
|
|
6142
|
+
if (indentOf2(line) <= base) break;
|
|
5929
6143
|
end = i + 1;
|
|
5930
6144
|
}
|
|
5931
6145
|
return end;
|
|
5932
6146
|
}
|
|
5933
6147
|
function clampToSiblings(symbols, lines) {
|
|
5934
|
-
const indentAt = (line) =>
|
|
6148
|
+
const indentAt = (line) => indentOf2(lines[line - 1] ?? "");
|
|
5935
6149
|
for (let i = 0; i < symbols.length; i++) {
|
|
5936
6150
|
const s = symbols[i];
|
|
5937
6151
|
for (let j = i + 1; j < symbols.length; j++) {
|
|
@@ -5944,14 +6158,15 @@ function clampToSiblings(symbols, lines) {
|
|
|
5944
6158
|
}
|
|
5945
6159
|
}
|
|
5946
6160
|
function signatureOf(line) {
|
|
5947
|
-
const
|
|
6161
|
+
const MAX_SIGNATURE2 = 200;
|
|
5948
6162
|
let sig = line.trim();
|
|
5949
6163
|
const brace = sig.indexOf("{");
|
|
5950
6164
|
if (brace > 0) sig = sig.slice(0, brace).trim();
|
|
5951
6165
|
if (sig.endsWith(":")) sig = sig.slice(0, -1).trim();
|
|
5952
|
-
return sig.length >
|
|
6166
|
+
return sig.length > MAX_SIGNATURE2 ? `${sig.slice(0, MAX_SIGNATURE2)}...` : sig;
|
|
5953
6167
|
}
|
|
5954
6168
|
function extractFile(lang, content) {
|
|
6169
|
+
if (DOC_LANGS.has(lang)) return extractDocFile(lang, content);
|
|
5955
6170
|
const lineAt = lineResolver(content);
|
|
5956
6171
|
const lines = content.split("\n");
|
|
5957
6172
|
const braced = BRACE_LANGS.has(lang);
|
|
@@ -5993,25 +6208,27 @@ function gitSourceFiles(root) {
|
|
|
5993
6208
|
const out = gitList(root, ["--cached", "--others", "--exclude-standard"]);
|
|
5994
6209
|
if (out === null) return null;
|
|
5995
6210
|
const nested = existsSync5(join6(root, ".gitmodules")) ? gitList(root, ["--recurse-submodules"]) : null;
|
|
5996
|
-
const
|
|
6211
|
+
const code = [];
|
|
6212
|
+
const docs = [];
|
|
5997
6213
|
const seen = /* @__PURE__ */ new Set();
|
|
5998
6214
|
for (const rel of [...out.split("\0"), ...(nested ?? "").split("\0")]) {
|
|
5999
|
-
if (
|
|
6215
|
+
if (code.length >= MAX_FILES) break;
|
|
6000
6216
|
if (!rel || seen.has(rel)) continue;
|
|
6001
6217
|
seen.add(rel);
|
|
6002
6218
|
if (!LANG_BY_EXT[extname2(rel).toLowerCase()]) continue;
|
|
6003
6219
|
if (rel.split("/").some((seg) => seg.startsWith(".") || IGNORE_DIRS.has(seg))) continue;
|
|
6004
|
-
|
|
6220
|
+
(isDocPath(rel) ? docs : code).push(join6(root, rel));
|
|
6005
6221
|
}
|
|
6006
|
-
return
|
|
6222
|
+
return [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
6007
6223
|
}
|
|
6008
6224
|
function walk(root) {
|
|
6009
6225
|
const tracked = gitSourceFiles(root);
|
|
6010
6226
|
if (tracked && tracked.length) return { files: tracked, truncated: tracked.length >= MAX_FILES };
|
|
6011
|
-
const
|
|
6227
|
+
const code = [];
|
|
6228
|
+
const docs = [];
|
|
6012
6229
|
const queue = [root];
|
|
6013
6230
|
let visited = 0;
|
|
6014
|
-
for (let head = 0; head < queue.length &&
|
|
6231
|
+
for (let head = 0; head < queue.length && code.length < MAX_FILES && visited < MAX_ENTRIES; head++) {
|
|
6015
6232
|
let entries;
|
|
6016
6233
|
try {
|
|
6017
6234
|
entries = readdirSync3(queue[head], { withFileTypes: true });
|
|
@@ -6019,16 +6236,17 @@ function walk(root) {
|
|
|
6019
6236
|
continue;
|
|
6020
6237
|
}
|
|
6021
6238
|
for (const e of entries) {
|
|
6022
|
-
if (
|
|
6239
|
+
if (code.length >= MAX_FILES || ++visited >= MAX_ENTRIES) break;
|
|
6023
6240
|
if (e.isDirectory()) {
|
|
6024
6241
|
if (!e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) queue.push(join6(queue[head], e.name));
|
|
6025
6242
|
continue;
|
|
6026
6243
|
}
|
|
6027
|
-
if (!e.isFile()) continue;
|
|
6028
|
-
|
|
6244
|
+
if (!e.isFile() || !LANG_BY_EXT[extname2(e.name).toLowerCase()]) continue;
|
|
6245
|
+
(isDocPath(e.name) ? docs : code).push(join6(queue[head], e.name));
|
|
6029
6246
|
}
|
|
6030
6247
|
}
|
|
6031
|
-
|
|
6248
|
+
const ordered = [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
|
|
6249
|
+
return { files: ordered, truncated: code.length >= MAX_FILES || visited >= MAX_ENTRIES };
|
|
6032
6250
|
}
|
|
6033
6251
|
function relPath(root, abs) {
|
|
6034
6252
|
return relative2(root, abs).split("\\").join("/");
|
|
@@ -6086,10 +6304,11 @@ function statSourceFiles(root) {
|
|
|
6086
6304
|
}
|
|
6087
6305
|
return out;
|
|
6088
6306
|
}
|
|
6089
|
-
var IGNORE_DIRS, LANG_BY_EXT, MAX_FILES, MAX_FILE_BYTES, BRACE_LANGS, HASH_COMMENT_LANGS, PREPROCESSOR_LANGS, MAX_ENTRIES;
|
|
6307
|
+
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
6308
|
var init_codegraph_extract = __esm({
|
|
6091
6309
|
"src/codegraph-extract.ts"() {
|
|
6092
6310
|
"use strict";
|
|
6311
|
+
init_codegraph_extract_docs();
|
|
6093
6312
|
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
6094
6313
|
"node_modules",
|
|
6095
6314
|
".git",
|
|
@@ -6114,7 +6333,7 @@ var init_codegraph_extract = __esm({
|
|
|
6114
6333
|
".gradle",
|
|
6115
6334
|
".dart_tool"
|
|
6116
6335
|
]);
|
|
6117
|
-
|
|
6336
|
+
CODE_LANG_BY_EXT = {
|
|
6118
6337
|
".ts": "ts",
|
|
6119
6338
|
".tsx": "ts",
|
|
6120
6339
|
".mts": "ts",
|
|
@@ -6140,6 +6359,9 @@ var init_codegraph_extract = __esm({
|
|
|
6140
6359
|
".hpp": "cpp",
|
|
6141
6360
|
".hh": "cpp"
|
|
6142
6361
|
};
|
|
6362
|
+
LANG_BY_EXT = { ...CODE_LANG_BY_EXT, ...DOC_LANG_BY_EXT };
|
|
6363
|
+
FOREIGN_DOC_DIRS = /(?:^|\/)(?:references?|examples?|samples?|fixtures?|third[_-]party|vendor(?:ed)?)\//i;
|
|
6364
|
+
MAX_DOC_FILES = 1500;
|
|
6143
6365
|
MAX_FILES = 8e3;
|
|
6144
6366
|
MAX_FILE_BYTES = 512 * 1024;
|
|
6145
6367
|
BRACE_LANGS = /* @__PURE__ */ new Set(["ts", "js", "go", "rust", "java", "csharp", "kotlin", "php", "c", "cpp"]);
|
|
@@ -6355,12 +6577,12 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6355
6577
|
}
|
|
6356
6578
|
const edges = [];
|
|
6357
6579
|
const seen = /* @__PURE__ */ new Set();
|
|
6358
|
-
const push = (from, to, rel) => {
|
|
6580
|
+
const push = (from, to, rel, inferred = false) => {
|
|
6359
6581
|
if (edges.length >= MAX_EDGES || from === to) return;
|
|
6360
6582
|
const key = `${from}|${to}|${rel}`;
|
|
6361
6583
|
if (seen.has(key)) return;
|
|
6362
6584
|
seen.add(key);
|
|
6363
|
-
edges.push([from, to, rel]);
|
|
6585
|
+
edges.push(inferred ? [from, to, rel, 1] : [from, to, rel]);
|
|
6364
6586
|
};
|
|
6365
6587
|
for (const f of files) {
|
|
6366
6588
|
for (const span of spansByFile.get(f.path) ?? []) push(f.path, span.id, "contains");
|
|
@@ -6389,12 +6611,14 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6389
6611
|
const candidates = defsByName.get(name)?.filter((c) => imported?.has(c.path) && reachable(c.path));
|
|
6390
6612
|
return candidates?.length === 1 ? candidates[0].id : null;
|
|
6391
6613
|
};
|
|
6614
|
+
const extracted = (id) => id ? { id, inferred: false } : null;
|
|
6615
|
+
const inferred = (id) => id ? { id, inferred: true } : null;
|
|
6392
6616
|
if (!f.bindings) {
|
|
6393
6617
|
return (name) => {
|
|
6394
|
-
if (local.has(name)) return local.get(name)
|
|
6395
|
-
if (imported?.size) return unique(name);
|
|
6618
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
6619
|
+
if (imported?.size) return inferred(unique(name));
|
|
6396
6620
|
const all = defsByName.get(name)?.filter((c) => reachable(c.path));
|
|
6397
|
-
return all?.length === 1 ? all[0].id : null;
|
|
6621
|
+
return all?.length === 1 ? inferred(all[0].id) : null;
|
|
6398
6622
|
};
|
|
6399
6623
|
}
|
|
6400
6624
|
const bound = new Set(f.bindings.names);
|
|
@@ -6406,10 +6630,10 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6406
6630
|
return (name, memberOf) => {
|
|
6407
6631
|
if (memberOf !== void 0) {
|
|
6408
6632
|
const path = namespaces.get(memberOf);
|
|
6409
|
-
return path ? idByPathName.get(`${path}#${name}`)
|
|
6633
|
+
return path ? extracted(idByPathName.get(`${path}#${name}`)) : null;
|
|
6410
6634
|
}
|
|
6411
|
-
if (local.has(name)) return local.get(name)
|
|
6412
|
-
return bound.has(name) ? unique(name) : null;
|
|
6635
|
+
if (local.has(name)) return extracted(local.get(name));
|
|
6636
|
+
return bound.has(name) ? extracted(unique(name)) : null;
|
|
6413
6637
|
};
|
|
6414
6638
|
}
|
|
6415
6639
|
const bodies = [];
|
|
@@ -6423,7 +6647,7 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6423
6647
|
const spans = spansByFile.get(f.path) ?? [];
|
|
6424
6648
|
const declLines = new Set(f.symbols.map((s) => s.line));
|
|
6425
6649
|
const lines = content.split("\n");
|
|
6426
|
-
indexBodies(bodies, f.path, spans, lines);
|
|
6650
|
+
indexBodies(bodies, f.path, spans, lines, DOC_LANGS.has(f.lang) ? MAX_DOC_BODY_TOKENS : MAX_BODY_TOKENS);
|
|
6427
6651
|
const bind = binderFor(f);
|
|
6428
6652
|
const strict = !!f.bindings;
|
|
6429
6653
|
for (const s of f.symbols) {
|
|
@@ -6432,9 +6656,17 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6432
6656
|
if (!self) continue;
|
|
6433
6657
|
for (const [rel, name] of inheritanceTargets(s.signature)) {
|
|
6434
6658
|
const target = bind(name);
|
|
6435
|
-
if (target) push(self.id, target, rel);
|
|
6659
|
+
if (target) push(self.id, target.id, rel, target.inferred);
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
if (f.lang === "markdown") {
|
|
6663
|
+
for (const { name, line } of markdownCodeMentions(content)) {
|
|
6664
|
+
if (name.length < MIN_DOC_MENTION) continue;
|
|
6665
|
+
const code = defsByName.get(name)?.filter((c) => !DOC_LANGS.has(langByPath.get(c.path) ?? ""));
|
|
6666
|
+
if (code?.length === 1) push(enclosingSymbol(spans, line) ?? f.path, code[0].id, "references", true);
|
|
6436
6667
|
}
|
|
6437
6668
|
}
|
|
6669
|
+
if (DOC_LANGS.has(f.lang) && !DOC_REFERENCE_LANGS.has(f.lang)) continue;
|
|
6438
6670
|
for (let i = 0; i < lines.length; i++) {
|
|
6439
6671
|
const lineNo = i + 1;
|
|
6440
6672
|
if (declLines.has(lineNo)) continue;
|
|
@@ -6447,31 +6679,32 @@ function buildEdges(root, files, importEdges, resolveImport) {
|
|
|
6447
6679
|
if (name.length < MIN_REF_NAME || !defsByName.has(name)) continue;
|
|
6448
6680
|
const memberOf = strict ? /([A-Za-z_$][\w$]*)\s*\.\s*$/.exec(line.slice(0, m.index))?.[1] : void 0;
|
|
6449
6681
|
const target = bind(name, memberOf);
|
|
6450
|
-
if (!target || target === from) continue;
|
|
6682
|
+
if (!target || target.id === from) continue;
|
|
6451
6683
|
const after = line.slice(m.index + name.length);
|
|
6452
|
-
|
|
6684
|
+
const calls = !DOC_LANGS.has(f.lang) && /^\s*\(/.test(after);
|
|
6685
|
+
push(from, target.id, calls ? "calls" : "references", target.inferred);
|
|
6453
6686
|
}
|
|
6454
6687
|
}
|
|
6455
6688
|
}
|
|
6456
6689
|
return { edges, bodies };
|
|
6457
6690
|
}
|
|
6458
|
-
function indexBodies(out, path, spans, lines) {
|
|
6691
|
+
function indexBodies(out, path, spans, lines, limit) {
|
|
6459
6692
|
const covered = new Uint8Array(lines.length + 1);
|
|
6460
6693
|
for (const s of spans) {
|
|
6461
6694
|
const text = lines.slice(s.line - 1, s.endLine).join("\n");
|
|
6462
6695
|
for (let l = s.line; l <= Math.min(s.endLine, lines.length); l++) covered[l] = 1;
|
|
6463
|
-
const bag = topTokens(text);
|
|
6696
|
+
const bag = topTokens(text, limit);
|
|
6464
6697
|
if (bag.length) out.push([s.id, bag]);
|
|
6465
6698
|
}
|
|
6466
6699
|
const residual = [];
|
|
6467
6700
|
for (let l = 1; l <= lines.length; l++) if (!covered[l]) residual.push(lines[l - 1]);
|
|
6468
|
-
const fileBag = topTokens(residual.join("\n"));
|
|
6701
|
+
const fileBag = topTokens(residual.join("\n"), limit);
|
|
6469
6702
|
if (fileBag.length) out.push([path, fileBag]);
|
|
6470
6703
|
}
|
|
6471
|
-
function topTokens(text) {
|
|
6704
|
+
function topTokens(text, limit) {
|
|
6472
6705
|
const m = /* @__PURE__ */ new Map();
|
|
6473
6706
|
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,
|
|
6707
|
+
return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit);
|
|
6475
6708
|
}
|
|
6476
6709
|
function isManagedPath(dir) {
|
|
6477
6710
|
const managed = resolve4(join7(enigmaHome(), ".enigma"));
|
|
@@ -6689,17 +6922,18 @@ function codeGraphAvailable() {
|
|
|
6689
6922
|
function codeGraphStatus() {
|
|
6690
6923
|
return { enabled: readConfig().config.codeGraph, available: true, projects: listProjects().length };
|
|
6691
6924
|
}
|
|
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;
|
|
6925
|
+
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
6926
|
var init_codegraph = __esm({
|
|
6694
6927
|
"src/codegraph.ts"() {
|
|
6695
6928
|
"use strict";
|
|
6696
6929
|
init_util();
|
|
6697
6930
|
init_config();
|
|
6698
6931
|
init_codegraph_rank();
|
|
6932
|
+
init_codegraph_extract_docs();
|
|
6699
6933
|
init_codegraph_extract();
|
|
6700
6934
|
init_codegraph_extract();
|
|
6701
6935
|
init_codegraph_types();
|
|
6702
|
-
GRAPH_VERSION =
|
|
6936
|
+
GRAPH_VERSION = 5;
|
|
6703
6937
|
RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".rb", ".php", ".cs", ".kt"];
|
|
6704
6938
|
LANG_EXTS = {
|
|
6705
6939
|
ts: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
@@ -6715,6 +6949,7 @@ var init_codegraph = __esm({
|
|
|
6715
6949
|
};
|
|
6716
6950
|
LANG_INDEX = { python: ["__init__"], go: ["mod"], rust: ["mod"] };
|
|
6717
6951
|
MIN_REF_NAME = 3;
|
|
6952
|
+
MIN_DOC_MENTION = 4;
|
|
6718
6953
|
MAX_EDGES = 4e5;
|
|
6719
6954
|
LANG_FAMILY = {
|
|
6720
6955
|
ts: "js",
|
|
@@ -6731,6 +6966,7 @@ var init_codegraph = __esm({
|
|
|
6731
6966
|
EXT_ORDER = orders(RESOLVE_EXTS, LANG_EXTS);
|
|
6732
6967
|
INDEX_ORDER = orders(INDEX_NAMES, LANG_INDEX);
|
|
6733
6968
|
MAX_BODY_TOKENS = 80;
|
|
6969
|
+
MAX_DOC_BODY_TOKENS = 24;
|
|
6734
6970
|
ROOT_MARKERS = [".git", "package.json", "go.mod", "pyproject.toml", "Cargo.toml", "pom.xml", "composer.json"];
|
|
6735
6971
|
MAX_ROOT_WALK = 6;
|
|
6736
6972
|
}
|
|
@@ -6963,11 +7199,11 @@ ${result.note}` : structural.fallthrough;
|
|
|
6963
7199
|
}
|
|
6964
7200
|
function adjacency(edges, direction) {
|
|
6965
7201
|
const adj = /* @__PURE__ */ new Map();
|
|
6966
|
-
for (const [source, target, rel] of edges) {
|
|
7202
|
+
for (const [source, target, rel, guess] of edges) {
|
|
6967
7203
|
if (!WALK_RELATIONS.has(rel)) continue;
|
|
6968
7204
|
const key = direction === "in" ? target : source;
|
|
6969
7205
|
const other = direction === "in" ? source : target;
|
|
6970
|
-
const entry = { other, relation: rel };
|
|
7206
|
+
const entry = { other, relation: rel, inferred: guess === 1 };
|
|
6971
7207
|
const arr = adj.get(key);
|
|
6972
7208
|
if (arr) arr.push(entry);
|
|
6973
7209
|
else adj.set(key, [entry]);
|
|
@@ -6996,13 +7232,13 @@ function codeGraphTrace(symbol, opts = {}) {
|
|
|
6996
7232
|
for (let d = 1; d <= depth && frontier.length; d++) {
|
|
6997
7233
|
const next = [];
|
|
6998
7234
|
for (const current of frontier) {
|
|
6999
|
-
for (const { other, relation } of adj.get(current) ?? []) {
|
|
7235
|
+
for (const { other, relation, inferred } of adj.get(current) ?? []) {
|
|
7000
7236
|
if (visited.has(other)) continue;
|
|
7001
7237
|
visited.add(other);
|
|
7002
7238
|
const node = loaded.byId.get(other);
|
|
7003
7239
|
if (!node) continue;
|
|
7004
7240
|
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 });
|
|
7241
|
+
hits.push({ id: other, name: node.name, kind: node.kind, path: node.path, line: node.line, relation, depth: d, ...inferred ? { inferred } : {} });
|
|
7006
7242
|
next.push(other);
|
|
7007
7243
|
}
|
|
7008
7244
|
}
|
|
@@ -7041,8 +7277,8 @@ function dirKey(path, depth) {
|
|
|
7041
7277
|
const segments = path.split("/");
|
|
7042
7278
|
return segments.slice(0, Math.min(depth, segments.length)).join("/");
|
|
7043
7279
|
}
|
|
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,
|
|
7280
|
+
function topHubs(nodes, deg, cap2) {
|
|
7281
|
+
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
7282
|
}
|
|
7047
7283
|
function codeGraphMap(opts = {}) {
|
|
7048
7284
|
const loaded = load(opts);
|
|
@@ -7270,7 +7506,7 @@ ${r.note ?? empty}
|
|
|
7270
7506
|
const lines = [head, ""];
|
|
7271
7507
|
lines.push(`defined at: ${r.matched.map((m) => `${m.path}:${m.line}`).join(", ")}`, "");
|
|
7272
7508
|
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}` : ""}`);
|
|
7509
|
+
lines.push(`- ${h.name} - ${h.kind} - ${h.path}:${h.line} - ${h.relation}${h.inferred ? " (inferred)" : ""}${r.depth > 1 ? ` - depth ${h.depth}` : ""}`);
|
|
7274
7510
|
}
|
|
7275
7511
|
lines.push("", `${r.hits.length} node${r.hits.length === 1 ? "" : "s"}.`);
|
|
7276
7512
|
return `${lines.join("\n")}
|
|
@@ -7393,6 +7629,295 @@ var init_codegraph_format = __esm({
|
|
|
7393
7629
|
}
|
|
7394
7630
|
});
|
|
7395
7631
|
|
|
7632
|
+
// src/codegraph-cluster.ts
|
|
7633
|
+
function localMoves(level, totalWeight) {
|
|
7634
|
+
const { nodes, adj, selfLoops } = level;
|
|
7635
|
+
const degree = new Array(nodes).fill(0);
|
|
7636
|
+
for (let i = 0; i < nodes; i++) {
|
|
7637
|
+
let d = selfLoops[i] * 2;
|
|
7638
|
+
for (const w of adj[i].values()) d += w;
|
|
7639
|
+
degree[i] = d;
|
|
7640
|
+
}
|
|
7641
|
+
const comm = Array.from({ length: nodes }, (_, i) => i);
|
|
7642
|
+
const commDegree = degree.slice();
|
|
7643
|
+
const m2 = totalWeight * 2;
|
|
7644
|
+
for (let sweep = 0; sweep < MAX_SWEEPS; sweep++) {
|
|
7645
|
+
let moved = false;
|
|
7646
|
+
for (let i = 0; i < nodes; i++) {
|
|
7647
|
+
const current = comm[i];
|
|
7648
|
+
const toComm = /* @__PURE__ */ new Map();
|
|
7649
|
+
for (const [j, w] of adj[i]) toComm.set(comm[j], (toComm.get(comm[j]) ?? 0) + w);
|
|
7650
|
+
commDegree[current] -= degree[i];
|
|
7651
|
+
let best = current;
|
|
7652
|
+
let bestGain = (toComm.get(current) ?? 0) - commDegree[current] * degree[i] / m2;
|
|
7653
|
+
for (const [c, w] of [...toComm.entries()].sort((a, b) => a[0] - b[0])) {
|
|
7654
|
+
const gain = w - commDegree[c] * degree[i] / m2;
|
|
7655
|
+
if (gain > bestGain + 1e-12) {
|
|
7656
|
+
best = c;
|
|
7657
|
+
bestGain = gain;
|
|
7658
|
+
}
|
|
7659
|
+
}
|
|
7660
|
+
commDegree[best] += degree[i];
|
|
7661
|
+
if (best !== current) {
|
|
7662
|
+
comm[i] = best;
|
|
7663
|
+
moved = true;
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
if (!moved) break;
|
|
7667
|
+
}
|
|
7668
|
+
return comm;
|
|
7669
|
+
}
|
|
7670
|
+
function clusterFiles(files, links) {
|
|
7671
|
+
const sorted = [...new Set(files)].sort();
|
|
7672
|
+
const index = new Map(sorted.map((f, i) => [f, i]));
|
|
7673
|
+
let level = { nodes: sorted.length, adj: sorted.map(() => /* @__PURE__ */ new Map()), selfLoops: new Array(sorted.length).fill(0) };
|
|
7674
|
+
let totalWeight = 0;
|
|
7675
|
+
for (const [a, b, w] of links) {
|
|
7676
|
+
const i = index.get(a);
|
|
7677
|
+
const j = index.get(b);
|
|
7678
|
+
if (i === void 0 || j === void 0 || w <= 0) continue;
|
|
7679
|
+
totalWeight += w;
|
|
7680
|
+
if (i === j) {
|
|
7681
|
+
level.selfLoops[i] += w;
|
|
7682
|
+
continue;
|
|
7683
|
+
}
|
|
7684
|
+
level.adj[i].set(j, (level.adj[i].get(j) ?? 0) + w);
|
|
7685
|
+
level.adj[j].set(i, (level.adj[j].get(i) ?? 0) + w);
|
|
7686
|
+
}
|
|
7687
|
+
let membership = sorted.map((_, i) => i);
|
|
7688
|
+
if (totalWeight === 0) return new Map(sorted.map((f, i) => [f, i]));
|
|
7689
|
+
for (let l = 0; l < MAX_LEVELS; l++) {
|
|
7690
|
+
const comm = localMoves(level, totalWeight);
|
|
7691
|
+
const ids = /* @__PURE__ */ new Map();
|
|
7692
|
+
for (const c of comm) if (!ids.has(c)) ids.set(c, ids.size);
|
|
7693
|
+
if (ids.size === level.nodes) break;
|
|
7694
|
+
membership = membership.map((c) => ids.get(comm[c]));
|
|
7695
|
+
const next = { nodes: ids.size, adj: Array.from({ length: ids.size }, () => /* @__PURE__ */ new Map()), selfLoops: new Array(ids.size).fill(0) };
|
|
7696
|
+
for (let i = 0; i < level.nodes; i++) {
|
|
7697
|
+
const ci = ids.get(comm[i]);
|
|
7698
|
+
next.selfLoops[ci] += level.selfLoops[i];
|
|
7699
|
+
for (const [j, w] of level.adj[i]) {
|
|
7700
|
+
const cj = ids.get(comm[j]);
|
|
7701
|
+
if (ci === cj) {
|
|
7702
|
+
if (i < j) next.selfLoops[ci] += w;
|
|
7703
|
+
} else next.adj[ci].set(cj, (next.adj[ci].get(cj) ?? 0) + w);
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
level = next;
|
|
7707
|
+
}
|
|
7708
|
+
const members = /* @__PURE__ */ new Map();
|
|
7709
|
+
sorted.forEach((f, i) => {
|
|
7710
|
+
const c = membership[i];
|
|
7711
|
+
const list = members.get(c);
|
|
7712
|
+
if (list) list.push(f);
|
|
7713
|
+
else members.set(c, [f]);
|
|
7714
|
+
});
|
|
7715
|
+
const order = [...members.entries()].sort((a, b) => b[1].length - a[1].length || a[1][0].localeCompare(b[1][0]));
|
|
7716
|
+
const out = /* @__PURE__ */ new Map();
|
|
7717
|
+
order.forEach(([, list], rank) => {
|
|
7718
|
+
for (const f of list) out.set(f, rank);
|
|
7719
|
+
});
|
|
7720
|
+
return out;
|
|
7721
|
+
}
|
|
7722
|
+
var MAX_SWEEPS, MAX_LEVELS;
|
|
7723
|
+
var init_codegraph_cluster = __esm({
|
|
7724
|
+
"src/codegraph-cluster.ts"() {
|
|
7725
|
+
"use strict";
|
|
7726
|
+
MAX_SWEEPS = 20;
|
|
7727
|
+
MAX_LEVELS = 6;
|
|
7728
|
+
}
|
|
7729
|
+
});
|
|
7730
|
+
|
|
7731
|
+
// src/codegraph-report.ts
|
|
7732
|
+
var codegraph_report_exports = {};
|
|
7733
|
+
__export(codegraph_report_exports, {
|
|
7734
|
+
codeGraphReport: () => codeGraphReport,
|
|
7735
|
+
formatReport: () => formatReport
|
|
7736
|
+
});
|
|
7737
|
+
function areaLabel(paths) {
|
|
7738
|
+
const dirs = paths.map((p) => p.split("/").slice(0, -1));
|
|
7739
|
+
let label = [];
|
|
7740
|
+
for (let depth = 1; ; depth++) {
|
|
7741
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
7742
|
+
for (const d of dirs) if (d.length >= depth) {
|
|
7743
|
+
const k = d.slice(0, depth).join("/");
|
|
7744
|
+
counts2.set(k, (counts2.get(k) ?? 0) + 1);
|
|
7745
|
+
}
|
|
7746
|
+
const top2 = [...counts2.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
7747
|
+
if (!top2 || top2[1] / paths.length < LABEL_SHARE) break;
|
|
7748
|
+
label = top2[0].split("/");
|
|
7749
|
+
}
|
|
7750
|
+
if (label.length) return `${label.join("/")}/`;
|
|
7751
|
+
if (paths.length === 1) return paths[0];
|
|
7752
|
+
const first = /* @__PURE__ */ new Map();
|
|
7753
|
+
for (const d of dirs) {
|
|
7754
|
+
const k = d[0] ?? "(root)";
|
|
7755
|
+
first.set(k, (first.get(k) ?? 0) + 1);
|
|
7756
|
+
}
|
|
7757
|
+
const [top] = [...first.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
|
|
7758
|
+
return `${top}/ (mixed)`;
|
|
7759
|
+
}
|
|
7760
|
+
function codeGraphReport(opts = {}) {
|
|
7761
|
+
const graph = loadFreshGraph(opts.project, opts.refresh ?? true);
|
|
7762
|
+
if (!graph) return null;
|
|
7763
|
+
const nodes = graphNodes(graph);
|
|
7764
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
7765
|
+
const langByPath = new Map(graph.files.map((f) => [f.path, f.lang]));
|
|
7766
|
+
const own = graph.files.filter((f) => !isForeignPath(f.path));
|
|
7767
|
+
const codePaths = new Set(own.filter((f) => isCodeLang(f.lang)).map((f) => f.path));
|
|
7768
|
+
const docPaths = new Set(own.filter((f) => f.lang === "markdown").map((f) => f.path));
|
|
7769
|
+
const weights = /* @__PURE__ */ new Map();
|
|
7770
|
+
const directed = /* @__PURE__ */ new Map();
|
|
7771
|
+
const docRefs = /* @__PURE__ */ new Map();
|
|
7772
|
+
for (const e of graph.edges) {
|
|
7773
|
+
if (e[2] === "contains") continue;
|
|
7774
|
+
const a = byId.get(e[0])?.path;
|
|
7775
|
+
const b = byId.get(e[1])?.path;
|
|
7776
|
+
if (!a || !b || a === b) continue;
|
|
7777
|
+
if (docPaths.has(a) && codePaths.has(b)) {
|
|
7778
|
+
const refs = docRefs.get(a) ?? /* @__PURE__ */ new Map();
|
|
7779
|
+
refs.set(b, (refs.get(b) ?? 0) + 1);
|
|
7780
|
+
docRefs.set(a, refs);
|
|
7781
|
+
continue;
|
|
7782
|
+
}
|
|
7783
|
+
if (!codePaths.has(a) || !codePaths.has(b)) continue;
|
|
7784
|
+
const key = a < b ? `${a}
|
|
7785
|
+
${b}` : `${b}
|
|
7786
|
+
${a}`;
|
|
7787
|
+
weights.set(key, (weights.get(key) ?? 0) + 1);
|
|
7788
|
+
directed.set(`${a}
|
|
7789
|
+
${b}`, (directed.get(`${a}
|
|
7790
|
+
${b}`) ?? 0) + 1);
|
|
7791
|
+
}
|
|
7792
|
+
const links = [...weights.entries()].map(([k, w]) => {
|
|
7793
|
+
const [a, b] = k.split("\n");
|
|
7794
|
+
return [a, b, w];
|
|
7795
|
+
});
|
|
7796
|
+
const areaOf = clusterFiles([...codePaths], links);
|
|
7797
|
+
const members = /* @__PURE__ */ new Map();
|
|
7798
|
+
for (const [path, area] of areaOf) {
|
|
7799
|
+
const list = members.get(area);
|
|
7800
|
+
if (list) list.push(path);
|
|
7801
|
+
else members.set(area, [path]);
|
|
7802
|
+
}
|
|
7803
|
+
const deg = inDegree(graph.edges, true);
|
|
7804
|
+
const maxAreas = opts.maxAreas ?? DEFAULT_MAX_AREAS;
|
|
7805
|
+
const ranked = [...members.entries()].filter(([, paths]) => paths.length >= 2).sort((a, b) => b[1].length - a[1].length || a[0] - b[0]);
|
|
7806
|
+
const symbolsOf = /* @__PURE__ */ new Map();
|
|
7807
|
+
for (const n of nodes) {
|
|
7808
|
+
if (n.kind === "file" || isTestPath(n.path)) continue;
|
|
7809
|
+
const area = areaOf.get(n.path);
|
|
7810
|
+
if (area === void 0) continue;
|
|
7811
|
+
const list = symbolsOf.get(area);
|
|
7812
|
+
if (list) list.push(n);
|
|
7813
|
+
else symbolsOf.set(area, [n]);
|
|
7814
|
+
}
|
|
7815
|
+
const keys = /* @__PURE__ */ new Map();
|
|
7816
|
+
const keyOf = (area) => {
|
|
7817
|
+
let key = keys.get(area);
|
|
7818
|
+
if (key) return key;
|
|
7819
|
+
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);
|
|
7820
|
+
keys.set(area, key);
|
|
7821
|
+
return key;
|
|
7822
|
+
};
|
|
7823
|
+
const base = new Map(ranked.map(([area, paths]) => [area, areaLabel(paths)]));
|
|
7824
|
+
const seenLabel = /* @__PURE__ */ new Map();
|
|
7825
|
+
for (const label of base.values()) seenLabel.set(label, (seenLabel.get(label) ?? 0) + 1);
|
|
7826
|
+
const labels = new Map([...base.entries()].map(([area, label]) => {
|
|
7827
|
+
if ((seenLabel.get(label) ?? 0) < 2) return [area, label];
|
|
7828
|
+
const around = keyOf(area)[0]?.name;
|
|
7829
|
+
return [area, around ? `${label} ~ ${around}` : label];
|
|
7830
|
+
}));
|
|
7831
|
+
const docsOf = /* @__PURE__ */ new Map();
|
|
7832
|
+
for (const [doc, refs] of docRefs) {
|
|
7833
|
+
const byArea = /* @__PURE__ */ new Map();
|
|
7834
|
+
for (const [code, w] of refs) {
|
|
7835
|
+
const area = areaOf.get(code);
|
|
7836
|
+
if (area !== void 0) byArea.set(area, (byArea.get(area) ?? 0) + w);
|
|
7837
|
+
}
|
|
7838
|
+
const best = [...byArea.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0];
|
|
7839
|
+
if (!best) continue;
|
|
7840
|
+
const list = docsOf.get(best[0]) ?? [];
|
|
7841
|
+
list.push(doc);
|
|
7842
|
+
docsOf.set(best[0], list);
|
|
7843
|
+
}
|
|
7844
|
+
const areas = ranked.slice(0, maxAreas).map(([area, paths]) => {
|
|
7845
|
+
const inArea = new Set(paths);
|
|
7846
|
+
const out = /* @__PURE__ */ new Map();
|
|
7847
|
+
for (const [key, w] of directed) {
|
|
7848
|
+
const [a, b] = key.split("\n");
|
|
7849
|
+
if (!inArea.has(a) || inArea.has(b)) continue;
|
|
7850
|
+
const target = areaOf.get(b);
|
|
7851
|
+
if (target !== void 0 && target !== area && labels.has(target)) out.set(target, (out.get(target) ?? 0) + w);
|
|
7852
|
+
}
|
|
7853
|
+
return {
|
|
7854
|
+
label: labels.get(area),
|
|
7855
|
+
files: paths.length,
|
|
7856
|
+
languages: [...new Set(paths.map((p) => langByPath.get(p)).filter((l) => !!l))].sort(),
|
|
7857
|
+
keySymbols: keyOf(area),
|
|
7858
|
+
docs: (docsOf.get(area) ?? []).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, AREA_DOCS),
|
|
7859
|
+
dependsOn: [...out.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, DEPENDS_ON).map(([t]) => labels.get(t))
|
|
7860
|
+
};
|
|
7861
|
+
});
|
|
7862
|
+
const titleOf = /* @__PURE__ */ new Map();
|
|
7863
|
+
for (const n of nodes) if (n.kind === "section" && !titleOf.has(n.path)) titleOf.set(n.path, n.name);
|
|
7864
|
+
const docFiles = own.filter((f) => f.lang === "markdown").sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
|
|
7865
|
+
const maxDocs = opts.maxDocs ?? DEFAULT_MAX_DOCS;
|
|
7866
|
+
return {
|
|
7867
|
+
name: graph.name,
|
|
7868
|
+
totals: {
|
|
7869
|
+
files: graph.files.length,
|
|
7870
|
+
symbols: nodes.length - graph.files.length,
|
|
7871
|
+
edges: graph.edges.length,
|
|
7872
|
+
inferred: graph.edges.filter((e) => e[3] === 1).length,
|
|
7873
|
+
docs: docFiles.length
|
|
7874
|
+
},
|
|
7875
|
+
areas,
|
|
7876
|
+
moreAreas: Math.max(0, ranked.length - areas.length),
|
|
7877
|
+
docs: docFiles.slice(0, maxDocs).map((f) => ({ path: f.path, title: titleOf.get(f.path) ?? "" })),
|
|
7878
|
+
moreDocs: Math.max(0, docFiles.length - maxDocs),
|
|
7879
|
+
foreign: graph.files.length - own.length,
|
|
7880
|
+
truncated: graph.truncated
|
|
7881
|
+
};
|
|
7882
|
+
}
|
|
7883
|
+
function formatReport(r) {
|
|
7884
|
+
const t = r.totals;
|
|
7885
|
+
const lines = [`repo report - ${r.name} - ${t.files} files (${t.docs} docs) - ${t.symbols} symbols - ${t.edges} edges (${t.inferred} inferred by name)`];
|
|
7886
|
+
if (r.foreign) lines.push(`${r.foreign} file(s) of vendored, reference or example material are left out of the areas below.`);
|
|
7887
|
+
lines.push("", "areas (files that depend on each other, largest first):");
|
|
7888
|
+
for (const a of r.areas) {
|
|
7889
|
+
lines.push(`- ${a.label} - ${a.files} files${a.languages.length ? ` - ${a.languages.join(", ")}` : ""}`);
|
|
7890
|
+
if (a.keySymbols.length) lines.push(` key: ${a.keySymbols.map((s) => `${s.name} (${s.path}:${s.line}, ${s.inDegree} in)`).join(", ")}`);
|
|
7891
|
+
if (a.dependsOn.length) lines.push(` uses: ${a.dependsOn.join(", ")}`);
|
|
7892
|
+
if (a.docs.length) lines.push(` docs: ${a.docs.join(", ")}`);
|
|
7893
|
+
}
|
|
7894
|
+
if (r.moreAreas) lines.push(` +${r.moreAreas} smaller area(s)`);
|
|
7895
|
+
if (r.docs.length) {
|
|
7896
|
+
lines.push("", "docs:");
|
|
7897
|
+
for (const d of r.docs) lines.push(`- ${d.path}${d.title ? ` - ${d.title}` : ""}`);
|
|
7898
|
+
if (r.moreDocs) lines.push(` +${r.moreDocs} more (enigma_codegraph_ask finds them by topic)`);
|
|
7899
|
+
}
|
|
7900
|
+
if (r.truncated) lines.push("", "The index hit its size cap; some files are not covered.");
|
|
7901
|
+
return `${lines.join("\n")}
|
|
7902
|
+
`;
|
|
7903
|
+
}
|
|
7904
|
+
var DEFAULT_MAX_AREAS, DEFAULT_MAX_DOCS, KEY_SYMBOLS, AREA_DOCS, DEPENDS_ON, LABEL_SHARE;
|
|
7905
|
+
var init_codegraph_report = __esm({
|
|
7906
|
+
"src/codegraph-report.ts"() {
|
|
7907
|
+
"use strict";
|
|
7908
|
+
init_codegraph();
|
|
7909
|
+
init_codegraph_rank();
|
|
7910
|
+
init_codegraph_extract();
|
|
7911
|
+
init_codegraph_cluster();
|
|
7912
|
+
DEFAULT_MAX_AREAS = 8;
|
|
7913
|
+
DEFAULT_MAX_DOCS = 12;
|
|
7914
|
+
KEY_SYMBOLS = 3;
|
|
7915
|
+
AREA_DOCS = 2;
|
|
7916
|
+
DEPENDS_ON = 2;
|
|
7917
|
+
LABEL_SHARE = 0.6;
|
|
7918
|
+
}
|
|
7919
|
+
});
|
|
7920
|
+
|
|
7396
7921
|
// src/codegraph-hook.ts
|
|
7397
7922
|
var codegraph_hook_exports = {};
|
|
7398
7923
|
__export(codegraph_hook_exports, {
|
|
@@ -7471,14 +7996,17 @@ async function sessionStart(dir) {
|
|
|
7471
7996
|
" - enigma_codegraph_skeleton: a file's whole API surface, no bodies.",
|
|
7472
7997
|
" - enigma_codegraph_grep: every occurrence, grouped by enclosing symbol - when you need them ALL, not the top matches."
|
|
7473
7998
|
].join("\n");
|
|
7474
|
-
const
|
|
7999
|
+
const rep = await Promise.resolve().then(() => (init_codegraph_report(), codegraph_report_exports));
|
|
8000
|
+
const report = rep.codeGraphReport({ project, refresh: false });
|
|
8001
|
+
const map = report ? null : q.codeGraphMap({ project, refresh: false, maxDirs: 8 });
|
|
8002
|
+
const body = report ? rep.formatReport(report) : map ? fmt.formatMap(map) : "";
|
|
7475
8003
|
const fresh = q.codeGraphCheck(project);
|
|
7476
8004
|
const stale = fresh && fresh.stale > 0 ? `
|
|
7477
8005
|
${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",
|
|
8006
|
+
writeStatuslineSnapshot(projectRoot(cg, project, dir), report?.totals.symbols ?? map?.totals.symbols ?? 0, fresh?.stale ?? 0);
|
|
8007
|
+
emit("SessionStart", body ? `${directive}
|
|
7480
8008
|
|
|
7481
|
-
${
|
|
8009
|
+
${body.trimEnd()}${stale}` : directive);
|
|
7482
8010
|
}
|
|
7483
8011
|
function coveringProject(cg, dir) {
|
|
7484
8012
|
const project = cg.findProjectForCwd(dir);
|