@polycode-projects/seonix 0.1.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/src/config.mjs ADDED
@@ -0,0 +1,28 @@
1
+ // Configuration for the seonix MCP server. Unlike the marginalia original
2
+ // (remote API + key), this server reads a LOCAL graph artifact written by the
3
+ // deterministic indexer — so the only knob is where that artifact lives.
4
+ //
5
+ // SEONIX_GRAPH_FILE — path to the JSON graph artifact. Default:
6
+ // <cwd>/.seonix/graph.json. MCP launchers start the server with
7
+ // cwd = the indexed worktree, so the default resolves to the
8
+ // artifact `cli index_repository` wrote there — no config needed.
9
+
10
+ import { join } from "node:path";
11
+
12
+ export const DEFAULT_GRAPH_REL = join(".seonix", "graph.json");
13
+
14
+ /** A clean, caller-facing tool error — message shown to the MCP client verbatim,
15
+ * never a stack. */
16
+ export class ToolError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "ToolError";
20
+ }
21
+ }
22
+
23
+ export function loadConfig(env = process.env, cwd = process.cwd()) {
24
+ const graphFile = env.SEONIX_GRAPH_FILE && env.SEONIX_GRAPH_FILE.trim()
25
+ ? env.SEONIX_GRAPH_FILE.trim()
26
+ : join(cwd, DEFAULT_GRAPH_REL);
27
+ return { graphFile };
28
+ }
@@ -0,0 +1,38 @@
1
+ // C# extractor — Roslyn (syntax-level) via the bundled dotnet tool. PICKED C#
2
+ // candidate when dotnet is present. Spawns roslyn/publish/roslyn-extract which
3
+ // prints the {modules:[…]} contract; this wrapper just shells out and parses it.
4
+ // Falls back is the caller's job (cs_treesitter) when dotnet/the tool is absent.
5
+ import { spawn } from "node:child_process";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { access } from "node:fs/promises";
9
+
10
+ const here = dirname(fileURLToPath(import.meta.url));
11
+ const TOOL_DLL = join(here, "..", "roslyn", "publish", "roslyn-extract.dll");
12
+
13
+ export async function toolAvailable() {
14
+ try { await access(TOOL_DLL); return true; } catch { return false; }
15
+ }
16
+
17
+ function run(cmd, args) {
18
+ return new Promise((resolve) => {
19
+ const child = spawn(cmd, args, { env: { ...process.env, DOTNET_CLI_TELEMETRY_OPTOUT: "1", DOTNET_NOLOGO: "1" } });
20
+ let out = ""; let err = "";
21
+ child.stdout.on("data", (d) => (out += d));
22
+ child.stderr.on("data", (d) => (err += d));
23
+ child.on("close", (code) => resolve({ code: code ?? -1, out, err }));
24
+ child.on("error", (e) => resolve({ code: -1, out, err: String(e) }));
25
+ });
26
+ }
27
+
28
+ export async function ingest(root) {
29
+ if (!(await toolAvailable())) throw new Error("roslyn-extract.dll not built (run dotnet publish in roslyn/)");
30
+ const { code, out, err } = await run("dotnet", [TOOL_DLL, root]);
31
+ if (code !== 0) throw new Error(`roslyn-extract failed (exit ${code}): ${err.slice(-300)}`);
32
+ let parsed;
33
+ try { parsed = JSON.parse(out); }
34
+ catch { throw new Error(`roslyn-extract produced non-JSON (${out.slice(0, 120)})`); }
35
+ return { modules: parsed.modules || [], failures: parsed.failures || [], fileCount: parsed.fileCount || 0 };
36
+ }
37
+
38
+ export const meta = { id: "roslyn", language: "c#", lib: "Roslyn (Microsoft.CodeAnalysis.CSharp, syntax-level)", exts: [".cs"] };
@@ -0,0 +1,140 @@
1
+ // C# extractor — tree-sitter-c-sharp (CST-only) candidate for the bake-off. The
2
+ // no-.NET fallback: a uniform CST, no semantic model (no resolved call targets /
3
+ // types). Emits the same contract shape so codegraph.mjs/digest consume it. Module
4
+ // identity is per-file; `dotted` is the file's primary namespace.
5
+ import { readFile } from "node:fs/promises";
6
+ import { createRequire } from "node:module";
7
+ import { walk, relPath } from "./walk.mjs";
8
+
9
+ // tree-sitter is the OPTIONAL fallback backend (used only when Roslyn/dotnet is
10
+ // absent). It is a native module and NOT a declared seonix dependency, so the
11
+ // require is LAZY — importing this module (e.g. via extract_lang's REGISTRY) must
12
+ // not force the native build. Resolved on first parse; a clear error if missing.
13
+ const require = createRequire(import.meta.url);
14
+ let _Parser = null;
15
+ let _CSharp = null;
16
+ function loadTreeSitter() {
17
+ if (_Parser) return { Parser: _Parser, CSharp: _CSharp };
18
+ try {
19
+ _Parser = require("tree-sitter");
20
+ _CSharp = require("tree-sitter-c-sharp");
21
+ } catch (e) {
22
+ throw new Error(
23
+ `tree-sitter C# fallback unavailable (install tree-sitter + tree-sitter-c-sharp, or use the Roslyn backend): ${e?.message || e}`,
24
+ );
25
+ }
26
+ return { Parser: _Parser, CSharp: _CSharp };
27
+ }
28
+
29
+ const EXTS = [".cs"];
30
+ const line = (n) => n.startPosition.row + 1;
31
+ const endLine = (n) => n.endPosition.row + 1;
32
+ const fieldText = (n, f) => n.childForFieldName(f)?.text || "";
33
+
34
+ const TYPE_DECLS = new Set(["class_declaration", "interface_declaration", "struct_declaration", "record_declaration", "enum_declaration"]);
35
+
36
+ function collectCalls(node) {
37
+ const out = new Set();
38
+ const stack = [node];
39
+ while (stack.length) {
40
+ const n = stack.pop();
41
+ if (n.type === "invocation_expression") {
42
+ const fn = n.childForFieldName("function");
43
+ if (fn) out.add(fn.text.replace(/\s+/g, "").slice(0, 80));
44
+ }
45
+ for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
46
+ }
47
+ return [...out].sort();
48
+ }
49
+
50
+ function modifiersText(node) {
51
+ // tree-sitter-c-sharp surfaces modifiers as anonymous children before the name
52
+ const mods = [];
53
+ for (let i = 0; i < node.childCount; i++) {
54
+ const c = node.child(i);
55
+ if (c.type === "modifier") mods.push(c.text);
56
+ }
57
+ return mods;
58
+ }
59
+ const visFrom = (mods) => mods.includes("private") ? "private" : mods.includes("protected") ? "protected" : "";
60
+
61
+ export async function extractFile(absPath, root) {
62
+ const text = await readFile(absPath, "utf8").catch(() => null);
63
+ const path = relPath(root, absPath);
64
+ if (text == null) return { failed: true, path };
65
+ let tree;
66
+ try {
67
+ const { Parser, CSharp } = loadTreeSitter();
68
+ const p = new Parser(); p.setLanguage(CSharp);
69
+ tree = p.parse(text);
70
+ } catch { return { failed: true, path }; }
71
+
72
+ const imports = new Set();
73
+ const defines = [];
74
+ const exports = new Set();
75
+ let primaryNs = "";
76
+
77
+ const stack = [tree.rootNode];
78
+ while (stack.length) {
79
+ const n = stack.pop();
80
+ if (n.type === "using_directive") {
81
+ const nm = (n.childForFieldName("name") || n.namedChildren.find((c) => /name/.test(c.type)));
82
+ const t = (nm?.text || n.text.replace(/^using\s+|;$/g, "")).trim();
83
+ if (t && !/^static\s/.test(t)) imports.add(t.replace(/\s+/g, ""));
84
+ } else if ((n.type === "namespace_declaration" || n.type === "file_scoped_namespace_declaration") && !primaryNs) {
85
+ primaryNs = fieldText(n, "name");
86
+ } else if (TYPE_DECLS.has(n.type)) {
87
+ const cname = fieldText(n, "name");
88
+ if (cname) {
89
+ const mods = modifiersText(n);
90
+ const bases = [];
91
+ const baseList = n.childForFieldName("bases") || n.namedChildren.find((c) => c.type === "base_list");
92
+ if (baseList) for (let i = 0; i < baseList.namedChildCount; i++) bases.push(baseList.namedChild(i).text.slice(0, 80));
93
+ defines.push({ name: cname, kind: "class", lineno: line(n), end_lineno: endLine(n), bases, decorators: [],
94
+ ...(visFrom(mods) ? { visibility: visFrom(mods) } : {}) });
95
+ if (mods.includes("public")) exports.add(cname);
96
+ const body = n.childForFieldName("body");
97
+ if (body) {
98
+ for (let i = 0; i < body.namedChildCount; i++) {
99
+ const mem = body.namedChild(i);
100
+ if (mem.type === "method_declaration" || mem.type === "constructor_declaration" || mem.type === "local_function_statement") {
101
+ const mn = fieldText(mem, "name") || cname;
102
+ const mmods = modifiersText(mem);
103
+ const params = (mem.childForFieldName("parameters")?.text || "").replace(/\s+/g, " ").replace(/^\(|\)$/g, "").slice(0, 160);
104
+ const returns = (mem.childForFieldName("returns") || mem.childForFieldName("type"))?.text?.slice(0, 80) || "";
105
+ defines.push({ name: `${cname}.${mn}`, kind: "method", lineno: line(mem), end_lineno: endLine(mem),
106
+ decorators: [], params, returns, calls: collectCalls(mem),
107
+ ...(mmods.includes("static") ? { is_static: true } : {}),
108
+ ...(visFrom(mmods) ? { visibility: visFrom(mmods) } : {}) });
109
+ } else if (mem.type === "property_declaration") {
110
+ const mn = fieldText(mem, "name");
111
+ if (mn) defines.push({ name: `${cname}.${mn}`, kind: "attribute", lineno: line(mem), end_lineno: endLine(mem), decorators: [] });
112
+ } else if (mem.type === "field_declaration") {
113
+ const v = mem.descendantsOfType?.("variable_declarator")?.[0];
114
+ const mn = v ? fieldText(v, "name") || v.text : "";
115
+ if (mn) defines.push({ name: `${cname}.${mn}`, kind: "attribute", lineno: line(mem), end_lineno: endLine(mem), decorators: [] });
116
+ }
117
+ }
118
+ }
119
+ // don't descend into the type body again for nested types beyond members
120
+ }
121
+ }
122
+ if (!TYPE_DECLS.has(n.type)) for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
123
+ }
124
+
125
+ return { path, dotted: primaryNs || path.replace(/\.cs$/i, "").split("/").join("."),
126
+ imports: [...imports].sort(), defines, calls: collectCalls(tree.rootNode), exports: [...exports].sort() };
127
+ }
128
+
129
+ export async function ingest(root) {
130
+ const files = await walk(root, EXTS);
131
+ const modules = [];
132
+ const failures = [];
133
+ for (const f of files) {
134
+ const mod = await extractFile(f, root);
135
+ if (mod.failed) failures.push(mod.path); else modules.push(mod);
136
+ }
137
+ return { modules, failures, fileCount: files.length };
138
+ }
139
+
140
+ export const meta = { id: "treesitter-cs", language: "c#", lib: "tree-sitter-c-sharp", exts: EXTS };