diff --git a/devel/codebase-memory-mcp/Makefile b/devel/codebase-memory-mcp/Makefile index df1be77bdfa3..225be00f5ea5 100644 --- a/devel/codebase-memory-mcp/Makefile +++ b/devel/codebase-memory-mcp/Makefile @@ -1,46 +1,53 @@ PORTNAME= codebase-memory-mcp DISTVERSIONPREFIX=v -DISTVERSION= 0.10.6 +DISTVERSION= 0.10.8 CATEGORIES= devel MAINTAINER= olivier@FreeBSD.org COMMENT= MCP server that indexes codebases into a persistent knowledge graph WWW= https://github.com/DeusData/codebase-memory-mcp LICENSE= MIT LICENSE_FILE= ${WRKSRC}/LICENSE USES= gmake compiler:c11 USE_GITHUB= yes GH_ACCOUNT= DeusData # Assumes a 64-bit size_t; fails to build on 32-bit archs ONLY_FOR_ARCHS= aarch64 amd64 PLIST_FILES= bin/codebase-memory-mcp SUB_FILES= pkg-message OPTIONS_DEFINE= DOCS .include .if ${PORT_OPTIONS:MDOCS} PLIST_FILES+= ${DOCSDIR_REL}/freebsd-src.cbmignore .endif +# FreeBSD sysctl OID-tree extractor: two source files added by the port (wired +# into Makefile.cbm and the pipeline via patches). Not shippable as patches +# because patch(1) cannot create new files. +post-extract: + ${CP} ${FILESDIR}/extract_sysctl.c ${WRKSRC}/internal/cbm/extract_sysctl.c + ${CP} ${FILESDIR}/pass_sysctl.c ${WRKSRC}/src/pipeline/pass_sysctl.c + do-build: @cd ${WRKSRC} && \ ${SETENV} ${MAKE_ENV} \ ${GMAKE} -f Makefile.cbm cbm \ CC="${CC}" CXX="${CXX}" \ CFLAGS_EXTRA='-DCBM_VERSION=\"${DISTVERSION}\" -DCBM_PKG_PREFIX=\"${PREFIX}\"' do-install: ${INSTALL_PROGRAM} ${WRKSRC}/build/c/codebase-memory-mcp \ ${STAGEDIR}${PREFIX}/bin/codebase-memory-mcp do-install-DOCS-on: ${MKDIR} ${STAGEDIR}${DOCSDIR} ${INSTALL_DATA} ${FILESDIR}/freebsd-src.cbmignore ${STAGEDIR}${DOCSDIR} .include diff --git a/devel/codebase-memory-mcp/distinfo b/devel/codebase-memory-mcp/distinfo index 6dffbd1e769a..4357e62557e2 100644 --- a/devel/codebase-memory-mcp/distinfo +++ b/devel/codebase-memory-mcp/distinfo @@ -1,3 +1,3 @@ -TIMESTAMP = 1787071809 -SHA256 (DeusData-codebase-memory-mcp-v0.10.6_GH0.tar.gz) = dd4ea504eaed850c1e98950d6ea96acc91a46d7ac132950360df3ead9ca3688a -SIZE (DeusData-codebase-memory-mcp-v0.10.6_GH0.tar.gz) = 94878245 +TIMESTAMP = 1787154654 +SHA256 (DeusData-codebase-memory-mcp-v0.10.8_GH0.tar.gz) = bbb40b0af7860e518d5f11e1d7316634bfebaa0928a05928792a890e0222cbeb +SIZE (DeusData-codebase-memory-mcp-v0.10.8_GH0.tar.gz) = 94894604 diff --git a/devel/codebase-memory-mcp/files/extract_sysctl.c b/devel/codebase-memory-mcp/files/extract_sysctl.c new file mode 100644 index 000000000000..9502782fbedf --- /dev/null +++ b/devel/codebase-memory-mcp/files/extract_sysctl.c @@ -0,0 +1,598 @@ +/* + * extract_sysctl.c — FreeBSD/DragonFly sysctl OID collector (pass A). + * + * Collects SYSCTL_* macro invocations into CBMSysctl records. The runtime + * dotted path (kern.ipc.maxsockbuf) is NOT resolved here — it requires the + * cross-file parent walk done by the repo-level pass (pass_sysctl.c), because a + * leaf names a parent C-symbol whose defining SYSCTL_NODE often lives in + * another translation unit. + * + * Two parse shapes must both be handled (verified against vendored + * tree-sitter-c): + * - bare SYSCTL_INT(...) -> call_expression(identifier, argument_list) + * - static SYSCTL_NODE(...) -> ERROR node; the macro name is an identifier + * child and the args nest inside a second + * inner ERROR node. + * Arguments are therefore read by flattening the terminal tokens between the + * macro '(' and its matching ')' and splitting on top-level commas — robust to + * whichever container/nesting the grammar produced. + * + * Intra-procedural resolution (recovers the SYSCTL_ADD_* / SYSCTL_CHILDREN + * dynamic-node family, ~half of all leaves): within a translation unit we + * track two local-variable aliasings so a leaf whose parent is a local var can + * still be attributed: + * node = SYSCTL_ADD_NODE(ctx, parent, nbr, "seg", ...); + * children = SYSCTL_CHILDREN(node); + * SYSCTL_ADD_INT(ctx, children, ..., "leaf", ...); + * We synthesize a stable symbol "_" for such runtime nodes so + * the resolver joins them into the static tree, yielding the TEMPLATE path + * (e.g. dev.igb.rx_bytes) — the per-instance index (dev.igb.0.…) only exists at + * runtime and is intentionally out of scope. + * + * See docs/sysctl-extractor-design.md. + */ +#include "cbm.h" +#include "arena.h" +#include "helpers.h" +#include "foundation/constants.h" +#include "extract_node_stack.h" +#include "tree_sitter/api.h" +#include +#include + +enum { + SYSCTL_STACK_CAP = 8192, + SYSCTL_MAX_ARGS = 16, + SYSCTL_ALIAS_CAP = 512, /* local var aliases tracked per file */ + SYSCTL_TOKBUF = 256, /* max length of one flattened arg */ +}; + +/* ── local-variable alias table (intra-procedural) ───────────────── + * Maps a C local variable name to the sysctl node C-symbol it refers to. + * Populated by `x = SYSCTL_ADD_NODE(...)` and `x = SYSCTL_CHILDREN(y)`. */ +typedef struct { + const char *var; /* borrowed — arena */ + const char *symbol; /* node C-symbol this var aliases (borrowed) */ +} sysctl_alias_t; + +typedef struct { + sysctl_alias_t items[SYSCTL_ALIAS_CAP]; + int count; +} sysctl_alias_table_t; + +static void alias_put(sysctl_alias_table_t *t, const char *var, const char *symbol) { + if (!var || !symbol) { + return; + } + for (int i = 0; i < t->count; i++) { + if (strcmp(t->items[i].var, var) == 0) { + t->items[i].symbol = symbol; /* last assignment wins */ + return; + } + } + if (t->count < SYSCTL_ALIAS_CAP) { + t->items[t->count].var = var; + t->items[t->count].symbol = symbol; + t->count++; + } +} + +static const char *alias_get(const sysctl_alias_table_t *t, const char *var) { + if (!var) { + return NULL; + } + for (int i = 0; i < t->count; i++) { + if (strcmp(t->items[i].var, var) == 0) { + return t->items[i].symbol; + } + } + return NULL; +} + +/* ── macro classification ────────────────────────────────────────── + * Returns 0 = ignore, 1 = node, 2 = leaf. Sets *is_add for SYSCTL_ADD_* (extra + * leading ctx arg) and *is_root for root nodes (name in a shifted slot, no + * parent). */ +static int classify_macro(const char *m, bool *is_add, bool *is_root) { + *is_add = false; + *is_root = false; + if (strncmp(m, "SYSCTL_", 7) != 0) { + return 0; + } + const char *tail = m + 7; + if (strncmp(tail, "ADD_", 4) == 0) { + *is_add = true; + tail += 4; + } + static const char *ignore[] = { + "DECL", "CHILDREN", "PARENT", "STATIC_CHILDREN", "FOREACH", + "IN", "OUT", "OUT_STR", "HANDLER_ARGS", "SIZEOF", + "SIZEOF_STRUCT", "ENFORCE_FLAGS", "NODE_CHILDREN", NULL, + }; + for (int i = 0; ignore[i]; i++) { + if (strcmp(tail, ignore[i]) == 0) { + return 0; + } + } + if (strncmp(tail, "ROOT_NODE", 9) == 0) { + *is_root = true; + return 1; + } + if (strncmp(tail, "NODE", 4) == 0) { /* NODE, NODE_WITH_LABEL */ + return 1; + } + return 2; /* every remaining SYSCTL_* declares a leaf */ +} + +/* ── argument flattening ─────────────────────────────────────────── + * Collect the comma-separated argument slots of the invocation rooted at + * `inv` into `out`. Walks terminal tokens, tracks paren depth, splits on + * top-level commas. Each slot is arena-allocated, surrounding spaces trimmed. + * Returns the number of args (>= 0). */ +typedef struct { + CBMExtractCtx *ctx; + const char **out; + int max; + int n; + char cur[SYSCTL_TOKBUF]; + int cl; + int depth; +} argflat_t; + +static void af_flush(argflat_t *a) { + if (a->n >= a->max) { + a->cl = 0; + return; + } + /* trim trailing spaces */ + while (a->cl > 0 && a->cur[a->cl - 1] == ' ') { + a->cl--; + } + a->cur[a->cl] = '\0'; + const char *p = a->cur; + while (*p == ' ') { + p++; + } + a->out[a->n++] = cbm_arena_strndup(a->ctx->arena, p, strlen(p)); + a->cl = 0; +} + +/* Append a node's verbatim source span to the current slot, no added spaces. */ +static void af_emit_span(argflat_t *a, TSNode n) { + uint32_t sb = ts_node_start_byte(n); + uint32_t eb = ts_node_end_byte(n); + for (uint32_t i = sb; i < eb && a->cl < SYSCTL_TOKBUF - 1; i++) { + a->cur[a->cl++] = a->ctx->source[i]; + } +} + +static void af_walk(argflat_t *a, TSNode n) { + const char *t = ts_node_type(n); + + /* Treat string literals and nested calls (e.g. SYSCTL_CHILDREN(x)) as + * atomic argument tokens — recursing into them would inject spaces between + * their sub-tokens and corrupt the value. Only at arg depth. */ + if (a->depth >= 1 && + (strcmp(t, "string_literal") == 0 || strcmp(t, "call_expression") == 0 || + strcmp(t, "concatenated_string") == 0)) { + af_emit_span(a, n); + return; + } + + uint32_t nc = ts_node_child_count(n); + if (nc == 0) { + if (strcmp(t, ",") == 0) { + if (a->depth == 1) { + af_flush(a); + } else if (a->cl < SYSCTL_TOKBUF - 1) { + a->cur[a->cl++] = ','; + } + return; + } + if (strcmp(t, "(") == 0) { + a->depth++; + if (a->depth == 1) { + return; /* opening paren of the macro call */ + } + } + if (strcmp(t, ")") == 0) { + a->depth--; + if (a->depth == 0) { + return; /* closing paren of the macro call */ + } + } + if (a->depth >= 1) { + af_emit_span(a, n); /* verbatim, no space padding */ + } + return; + } + for (uint32_t i = 0; i < nc; i++) { + af_walk(a, ts_node_child(n, i)); + } +} + +static int collect_args(CBMExtractCtx *ctx, TSNode inv, const char **out, int max) { + argflat_t a = {.ctx = ctx, .out = out, .max = max, .n = 0, .cl = 0, .depth = 0}; + af_walk(&a, inv); + af_flush(&a); /* final slot */ + return a.n; +} + +/* ── string / identifier helpers ───────────────────────────────────── */ + +/* Strip surrounding double quotes from a string-literal arg; return the inner + * text (arena). Returns NULL if not a quoted literal. */ +static const char *unquote(CBMExtractCtx *ctx, const char *s) { + if (!s) { + return NULL; + } + size_t len = strlen(s); + if (len >= 2 && s[0] == '"' && s[len - 1] == '"') { + return cbm_arena_strndup(ctx->arena, s + 1, len - 2); + } + return NULL; +} + +/* Extract the inner symbol of a SYSCTL_CHILDREN(x) / SYSCTL_STATIC_CHILDREN(x) + * argument. Returns the "x" token (arena), or NULL if the arg is not such a + * wrapper. */ +static const char *children_inner(CBMExtractCtx *ctx, const char *arg) { + if (!arg) { + return NULL; + } + const char *open = NULL; + if (strncmp(arg, "SYSCTL_CHILDREN(", 16) == 0) { + open = arg + 16; + } else if (strncmp(arg, "SYSCTL_STATIC_CHILDREN(", 23) == 0) { + open = arg + 23; + } else { + return NULL; + } + const char *close = strchr(open, ')'); + size_t n = close ? (size_t)(close - open) : strlen(open); + /* trim spaces */ + while (n > 0 && open[0] == ' ') { + open++; + n--; + } + while (n > 0 && open[n - 1] == ' ') { + n--; + } + return cbm_arena_strndup(ctx->arena, open, n); +} + +/* Synthesize a stable node symbol "_" for a runtime-added node so + * the resolver can join it into the static tree as a template path. */ +static const char *synth_symbol(CBMExtractCtx *ctx, const char *parent_sym, const char *seg) { + if (!parent_sym || !seg) { + return NULL; + } + size_t n = strlen(parent_sym) + 1 + strlen(seg) + 1; + char *buf = cbm_arena_alloc(ctx->arena, n); + snprintf(buf, n, "%s_%s", parent_sym, seg); + return buf; +} + +/* Resolve an argument that should name a parent OID-list to a node C-symbol: + * - "SYSCTL_CHILDREN(v)" -> alias_get(v) (or "_" static node symbol) + * - a known local alias -> its symbol + * - a bare "_foo" static -> itself + * Returns NULL if not resolvable. */ +/* Derive the driver name for a device-tree sysctl from the source path, e.g. + * sys/dev/bnxt/bnxt_en/bnxt_sysctl.c -> "bnxt". Returns a synthetic pre-resolved + * root symbol "@dev." (the resolver treats a leading '@' as a literal + * path prefix), or NULL if the path is not under sys/dev/. These sysctls live in + * the runtime dev...* namespace; we recover the driver-level + * TEMPLATE path (unit index is runtime-only and intentionally omitted). */ +static const char *device_tree_root(CBMExtractCtx *ctx) { + const char *p = ctx->rel_path; + if (!p) { + return NULL; + } + const char *dev = strstr(p, "sys/dev/"); + if (!dev) { + dev = (strncmp(p, "dev/", 4) == 0) ? p : NULL; + if (!dev) { + return NULL; + } + dev += 4; + } else { + dev += 8; /* strlen("sys/dev/") */ + } + const char *slash = strchr(dev, '/'); + size_t n = slash ? (size_t)(slash - dev) : strlen(dev); + if (n == 0 || n > 48) { + return NULL; + } + char *sym = cbm_arena_alloc(ctx->arena, 5 + n + 1); /* "@dev." + name */ + snprintf(sym, 5 + n + 1, "@dev.%.*s", (int)n, dev); + return sym; +} + +static const char *resolve_parent_arg(CBMExtractCtx *ctx, const sysctl_alias_table_t *aliases, + const char *arg) { + if (!arg) { + return NULL; + } + /* Device drivers register under SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + * a runtime node. Recover the dev. template from the file path. */ + if (strstr(arg, "device_get_sysctl_tree") != NULL) { + return device_tree_root(ctx); + } + const char *inner = children_inner(ctx, arg); + if (inner) { + const char *sym = alias_get(aliases, inner); + if (sym) { + return sym; + } + if (strstr(inner, "device_get_sysctl_tree") != NULL) { + return device_tree_root(ctx); + } + /* SYSCTL_CHILDREN(&static_node) or SYSCTL_CHILDREN(static_sym) */ + if (inner[0] == '&') { + inner++; + } + return inner; /* static node C-symbol */ + } + const char *sym = alias_get(aliases, arg); + if (sym) { + return sym; + } + if (arg[0] == '_') { + return arg; /* bare static node symbol like _kern_ipc */ + } + return NULL; /* unresolved local (e.g. a function param) */ +} + +/* Best-effort handler identifier: for PROC/OID the function pointer arg. Scan + * from the tail for the first token that is a plain identifier (not a string, + * address-of, number, or NULL). */ +static const char *guess_handler(const char **args, int argc, int base) { + for (int i = argc - 1; i >= base; i--) { + const char *a = args[i]; + if (a && a[0] != '"' && a[0] != '&' && a[0] != '0' && a[0] != '(' && + strcmp(a, "NULL") != 0 && (a[0] == '_' || (a[0] >= 'a' && a[0] <= 'z') || + (a[0] >= 'A' && a[0] <= 'Z'))) { + return a; + } + } + return NULL; +} + +/* ── record emission ───────────────────────────────────────────────── */ + +/* Process one SYSCTL_* invocation. `assign_target` is the LHS var name when the + * invocation is the RHS of an assignment/declarator (for alias tracking), else + * NULL. */ +/* When alias_only is true, update the alias table but do NOT push a record — + * used during the pass-1 alias-collection sweeps to avoid duplicate records. */ +static void process_invocation_ex(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases, + const char *macro, TSNode site, const char *assign_target, + bool alias_only) { + bool is_add = false, is_root = false; + int kind = classify_macro(macro, &is_add, &is_root); + if (kind == 0) { + return; + } + + const char *args[SYSCTL_MAX_ARGS]; + int argc = collect_args(ctx, site, args, SYSCTL_MAX_ARGS); + int base = is_add ? 1 : 0; /* skip leading ctx for SYSCTL_ADD_* */ + + const char *parent_arg = NULL; + const char *seg = NULL; /* leaf/segment name */ + + if (is_root) { + /* ROOT_NODE(nbr, name, ...) / ADD_ROOT_NODE(ctx, nbr, name, ...) */ + if (argc > base + 1) { + seg = args[base + 1]; + } + } else { + if (argc > base + 0) { + parent_arg = args[base + 0]; + } + if (argc > base + 2) { + seg = args[base + 2]; + } + } + if (!seg) { + return; + } + /* seg may be a quoted string (runtime ADD_) or a bare token (static). */ + const char *seg_str = unquote(ctx, seg); + if (seg_str) { + seg = seg_str; + } + + /* Resolve parent to a node C-symbol (static or via local alias). */ + const char *parent_sym = + is_root ? NULL : resolve_parent_arg(ctx, aliases, parent_arg); + + const char *self_sym = NULL; + if (kind == 1) { + /* A node: its own symbol is _ (static) or synthesized. */ + if (is_root) { + size_t n = 1 + strlen(seg) + 1; + char *buf = cbm_arena_alloc(ctx->arena, n); + snprintf(buf, n, "_%s", seg); + self_sym = buf; + } else if (parent_sym) { + self_sym = synth_symbol(ctx, parent_sym, seg); + } + /* Track alias: `x = SYSCTL_ADD_NODE(...)` binds x to this node. */ + if (assign_target && self_sym) { + alias_put(aliases, assign_target, self_sym); + } + } + + if (alias_only) { + return; /* pass-1 sweep: alias recorded above, emit nothing */ + } + + CBMSysctl s = { + .leaf_name = seg, + .parent_symbol = parent_sym, + .self_symbol = self_sym, + .handler = guess_handler(args, argc, base), + .enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, site), + .file_rel = ctx->rel_path, + .is_node = (kind == 1), + .line = (int)ts_node_start_point(site).row + 1, + }; + cbm_sysctl_push(&ctx->result->sysctls, ctx->arena, s); +} + +static void process_invocation(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases, + const char *macro, TSNode site, const char *assign_target) { + process_invocation_ex(ctx, aliases, macro, site, assign_target, false); +} + +/* Handle `lhs = SYSCTL_CHILDREN(x)` / `lhs = SYSCTL_STATIC_CHILDREN(x)`: + * bind lhs to the same node x aliases (no record emitted — CHILDREN is an + * accessor, not a declaration). Returns true if consumed. */ +static bool try_children_assign(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases, + const char *lhs, const char *rhs_text) { + const char *inner = children_inner(ctx, rhs_text); + if (!inner || !lhs) { + return false; + } + const char *sym = alias_get(aliases, inner); + if (!sym) { + if (inner[0] == '&') { + inner++; + } + sym = inner; /* static node symbol */ + } + alias_put(aliases, lhs, sym); + return true; +} + +/* ── AST walk ─────────────────────────────────────────────────────── + * Single stack walk. For each node: + * - call_expression whose function is a SYSCTL_ identifier -> invocation + * - ERROR node whose first identifier child is SYSCTL_ -> invocation + * - init_declarator / assignment_expression -> capture LHS var for aliasing + * The assignment context is detected by inspecting the invocation's parent. */ + +/* Return the LHS variable name if `call` sits on the RHS of an assignment or + * an initialized declarator, else NULL. Also handles `lhs = SYSCTL_CHILDREN(x)` + * for alias-only binding (handled by caller via rhs text). */ +static const char *assign_lhs_of(CBMExtractCtx *ctx, TSNode call) { + TSNode parent = ts_node_parent(call); + if (ts_node_is_null(parent)) { + return NULL; + } + const char *pt = ts_node_type(parent); + if (strcmp(pt, "init_declarator") == 0) { + TSNode decl = ts_node_child_by_field_name(parent, TS_FIELD("declarator")); + if (!ts_node_is_null(decl)) { + return cbm_node_text(ctx->arena, decl, ctx->source); + } + } else if (strcmp(pt, "assignment_expression") == 0) { + TSNode lhs = ts_node_child_by_field_name(parent, TS_FIELD("left")); + if (!ts_node_is_null(lhs)) { + return cbm_node_text(ctx->arena, lhs, ctx->source); + } + } + return NULL; +} + +/* Pass 1: collect local-variable aliases regardless of source order. Kernel + * registration code frequently uses a node variable (SYSCTL_CHILDREN(x)) many + * lines BEFORE the `x = SYSCTL_ADD_NODE(...)` that defines it (helper split + * across functions), so a single source-order pass would resolve the uses + * before the def. We therefore seed every alias first, then emit. */ +static void collect_aliases(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases, TSNode root) { + TSNodeStack stack; + ts_nstack_init(&stack, ctx->arena, SYSCTL_STACK_CAP); + ts_nstack_push(&stack, ctx->arena, root); + while (stack.count > 0) { + TSNode node = ts_nstack_pop(&stack); + if (strcmp(ts_node_type(node), "call_expression") == 0) { + TSNode fn = ts_node_child_by_field_name(node, TS_FIELD("function")); + if (!ts_node_is_null(fn) && strcmp(ts_node_type(fn), "identifier") == 0) { + char *name = cbm_node_text(ctx->arena, fn, ctx->source); + if (name) { + const char *lhs = assign_lhs_of(ctx, node); + if (lhs && (strncmp(name, "SYSCTL_CHILDREN", 15) == 0 || + strncmp(name, "SYSCTL_STATIC_CHILDREN", 22) == 0)) { + char *rhs = cbm_node_text(ctx->arena, node, ctx->source); + try_children_assign(ctx, aliases, lhs, rhs); + } else if (lhs && strncmp(name, "SYSCTL_ADD_NODE", 15) == 0) { + /* Synthesize the node symbol from its resolved parent so + * the var binds to it. alias_only: do not emit a record + * here (the main pass emits it). Two sweeps chain + * forward references. */ + process_invocation_ex(ctx, aliases, name, node, lhs, true); + } + } + } + } + ts_nstack_push_children(&stack, ctx->arena, node); + } +} + +static void extract_sysctl_c(CBMExtractCtx *ctx) { + sysctl_alias_table_t aliases = {.count = 0}; + + /* Two alias-collection sweeps: the second resolves forward-referenced node + * chains that the first could not (a var defined after its use, whose own + * parent was also a not-yet-seen var). Two passes suffice for the depths + * seen in practice (root -> group -> leaf); deeper chains degrade to + * unresolved rather than loop. */ + collect_aliases(ctx, &aliases, ctx->root); + collect_aliases(ctx, &aliases, ctx->root); + + TSNodeStack stack; + ts_nstack_init(&stack, ctx->arena, SYSCTL_STACK_CAP); + ts_nstack_push(&stack, ctx->arena, ctx->root); + + while (stack.count > 0) { + TSNode node = ts_nstack_pop(&stack); + const char *nt = ts_node_type(node); + + if (strcmp(nt, "call_expression") == 0) { + TSNode fn = ts_node_child_by_field_name(node, TS_FIELD("function")); + if (!ts_node_is_null(fn) && + strcmp(ts_node_type(fn), "identifier") == 0) { + char *name = cbm_node_text(ctx->arena, fn, ctx->source); + if (name && strncmp(name, "SYSCTL_", 7) == 0) { + /* SYSCTL_CHILDREN assignments are alias-only (seeded in + * pass 1) — skip here. Everything else emits a record. */ + if (strncmp(name, "SYSCTL_CHILDREN", 15) != 0 && + strncmp(name, "SYSCTL_STATIC_CHILDREN", 22) != 0) { + process_invocation(ctx, &aliases, name, node, NULL); + } + } + } + } else if (strcmp(nt, "ERROR") == 0 || strcmp(nt, "macro_type_specifier") == 0) { + /* `static SYSCTL_NODE(...)` parses two ways depending on context: + * - as a bare ERROR node (isolated), or + * - as a `macro_type_specifier` inside a `declaration` (in situ, + * when the grammar treats the macro as a type). The SYSCTL_ + * identifier is a direct child either way; args nest below. */ + uint32_t nc = ts_node_child_count(node); + for (uint32_t i = 0; i < nc; i++) { + TSNode c = ts_node_child(node, i); + if (strcmp(ts_node_type(c), "identifier") != 0) { + continue; + } + char *name = cbm_node_text(ctx->arena, c, ctx->source); + if (!name || strncmp(name, "SYSCTL_", 7) != 0) { + continue; + } + /* Such a declaration is never an assignment RHS -> no alias. */ + process_invocation(ctx, &aliases, name, node, NULL); + break; /* one macro per node */ + } + } + ts_nstack_push_children(&stack, ctx->arena, node); + } +} + +void cbm_extract_sysctl(CBMExtractCtx *ctx) { + /* C only; C++ kernel modules are vanishingly rare and share the grammar. */ + if (ctx->language == CBM_LANG_C || ctx->language == CBM_LANG_CPP) { + extract_sysctl_c(ctx); + } +} diff --git a/devel/codebase-memory-mcp/files/pass_sysctl.c b/devel/codebase-memory-mcp/files/pass_sysctl.c new file mode 100644 index 000000000000..85308eb8b351 --- /dev/null +++ b/devel/codebase-memory-mcp/files/pass_sysctl.c @@ -0,0 +1,316 @@ +/* + * pass_sysctl.c — FreeBSD/DragonFly sysctl OID-tree resolution (pass B). + * + * Two stages: + * 1. cbm_sysctl_emit_raw_for_file() — called per file from the definitions / + * parallel consume step. Emits one provisional "SysctlDecl" node per + * collected CBMSysctl record, carrying the raw parent/self C-symbols in + * its properties. Runs in both the sequential and parallel pipelines + * (the per-file CBMFileResult is available there; result_cache is freed + * before predump). + * 2. cbm_pipeline_resolve_sysctl() — a graph-only predump pass. Reads back + * all SysctlDecl nodes, builds a symbol -> (segment, parent_symbol) table + * from the node-kind decls, resolves each leaf's parent chain to a dotted + * runtime path (kern.ipc.maxsockbuf), and emits a deduplicated "Sysctl" + * node per resolved path plus an IMPLEMENTS_SYSCTL edge from the handler / + * enclosing function. Being graph-only, it is path-independent. + * + * See docs/sysctl-extractor-design.md. + */ +#include "pipeline/pipeline.h" +#include "pipeline/pipeline_internal.h" +#include "graph_buffer/graph_buffer.h" +#include "foundation/log.h" +#include "foundation/constants.h" +#include "cbm.h" + +#include +#include +#include + +enum { + SR_MAX_DEPTH = 24, /* max OID tree depth for the parent walk */ + SR_SYM = 160, /* max C-symbol length */ + SR_SEG = 96, /* max segment length */ + SR_PATH = 640, /* max dotted path length */ +}; + +/* Fixed root OID symbols -> their segment, in case a root's SYSCTL_ROOT_NODE + * decl is in a file that was not indexed (headers, arch-specific). */ +static const struct { + const char *sym; + const char *seg; +} SR_ROOTS[] = { + {"_kern", "kern"}, {"_vm", "vm"}, {"_vfs", "vfs"}, + {"_net", "net"}, {"_debug", "debug"}, {"_hw", "hw"}, + {"_machdep", "machdep"}, {"_user", "user"}, {"_p1003_1b", "p1003_1b"}, + {"_security", "security"}, {"_dev", "dev"}, {"_compat", "compat"}, + {"_regression", "regression"}, {"_sysctl", "sysctl"}, {NULL, NULL}, +}; + +/* ── stage 1: provisional per-file emission ─────────────────────────── */ + +void cbm_sysctl_emit_raw_for_file(cbm_pipeline_ctx_t *ctx, const CBMFileResult *result, + const char *rel) { + if (!ctx || !result) { + return; + } + for (int j = 0; j < result->sysctls.count; j++) { + const CBMSysctl *s = &result->sysctls.items[j]; + if (!s->leaf_name || !s->leaf_name[0]) { + continue; + } + const char *file = s->file_rel ? s->file_rel : (rel ? rel : ""); + /* Stable, unique-per-site qn so upsert does not collapse distinct + * declarations that happen to share a leaf name. */ + char qn[CBM_SZ_512]; + snprintf(qn, sizeof(qn), "__sysctldecl__%s:%d:%s", file, s->line, s->leaf_name); + + /* All fields are C identifiers — no JSON escaping required. */ + char props[CBM_SZ_1K]; + snprintf(props, sizeof(props), + "{\"leaf\":\"%s\",\"parent_symbol\":\"%s\",\"self_symbol\":\"%s\"," + "\"handler\":\"%s\",\"enclosing\":\"%s\",\"is_node\":%s}", + s->leaf_name, s->parent_symbol ? s->parent_symbol : "", + s->self_symbol ? s->self_symbol : "", s->handler ? s->handler : "", + s->enclosing_func_qn ? s->enclosing_func_qn : "", + s->is_node ? "true" : "false"); + + cbm_gbuf_upsert_node(ctx->gbuf, "SysctlDecl", s->leaf_name, qn, file, s->line, + s->line, props); + } +} + +/* ── tiny JSON-field readers (props are flat, identifier-valued) ─────── */ + +/* Copy the string value of "key" from a flat JSON object into out. Returns + * true if found and non-empty. */ +static bool jget(const char *json, const char *key, char *out, size_t outsz) { + char pat[64]; + snprintf(pat, sizeof(pat), "\"%s\":\"", key); + const char *p = strstr(json, pat); + if (!p) { + return false; + } + p += strlen(pat); + const char *end = strchr(p, '"'); + if (!end) { + return false; + } + size_t n = (size_t)(end - p); + if (n == 0 || n >= outsz) { + if (n >= outsz) { + n = outsz - 1; + } else { + return false; + } + } + memcpy(out, p, n); + out[n] = '\0'; + return true; +} + +static bool jget_bool(const char *json, const char *key) { + char pat[64]; + snprintf(pat, sizeof(pat), "\"%s\":", key); + const char *p = strstr(json, pat); + if (!p) { + return false; + } + p += strlen(pat); + return strncmp(p, "true", 4) == 0; +} + +/* ── node symbol table for parent-chain resolution ──────────────────── */ + +typedef struct { + char sym[SR_SYM]; + char seg[SR_SEG]; + char parent[SR_SYM]; + bool has_parent; +} sr_node_t; + +typedef struct { + sr_node_t *items; + int count; + int cap; +} sr_table_t; + +static sr_node_t *sr_find(sr_table_t *t, const char *sym) { + for (int i = 0; i < t->count; i++) { + if (strcmp(t->items[i].sym, sym) == 0) { + return &t->items[i]; + } + } + return NULL; +} + +static void sr_add(sr_table_t *t, const char *sym, const char *seg, const char *parent) { + if (!sym || !sym[0] || sr_find(t, sym)) { + return; + } + if (t->count >= t->cap) { + int nc = t->cap ? t->cap * 2 : 1024; + sr_node_t *ni = realloc(t->items, (size_t)nc * sizeof(sr_node_t)); + if (!ni) { + return; + } + t->items = ni; + t->cap = nc; + } + sr_node_t *n = &t->items[t->count++]; + snprintf(n->sym, sizeof(n->sym), "%s", sym); + snprintf(n->seg, sizeof(n->seg), "%s", seg ? seg : ""); + if (parent && parent[0]) { + snprintf(n->parent, sizeof(n->parent), "%s", parent); + n->has_parent = true; + } else { + n->parent[0] = '\0'; + n->has_parent = false; + } +} + +/* Walk parent links from `parent_sym` up to a root, joining segments with '.'. + * Writes the ancestor path (without the leaf) into out. Returns true on a full + * resolve to a known root, false if a link is missing. */ +static bool sr_resolve(sr_table_t *t, const char *parent_sym, char *out, size_t outsz) { + const char *segs[SR_MAX_DEPTH]; + int sd = 0; + const char *cur = parent_sym; + for (int guard = 0; guard < SR_MAX_DEPTH && cur && cur[0]; guard++) { + /* A symbol beginning with '@' is a synthetic pre-resolved literal path + * prefix (e.g. "@dev.bnxt" from a device_get_sysctl_tree() parent): + * emit it verbatim as the top segment and stop the walk. */ + if (cur[0] == '@') { + if (sd < SR_MAX_DEPTH) { + segs[sd++] = cur + 1; /* skip '@' */ + } + break; + } + sr_node_t *n = sr_find(t, cur); + if (!n) { + return false; /* missing intermediate node */ + } + if (sd < SR_MAX_DEPTH) { + segs[sd++] = n->seg; + } + if (!n->has_parent) { + break; /* reached a root */ + } + cur = n->parent; + } + if (sd == 0) { + return false; + } + out[0] = '\0'; + size_t used = 0; + for (int i = sd - 1; i >= 0; i--) { + int w = snprintf(out + used, outsz - used, "%s%s", segs[i], i > 0 ? "." : ""); + if (w < 0 || (size_t)w >= outsz - used) { + return false; + } + used += (size_t)w; + } + return true; +} + +/* ── stage 2: graph-only resolve + emit ─────────────────────────────── */ + +void cbm_pipeline_resolve_sysctl(cbm_gbuf_t *gb) { + if (!gb) { + return; + } + const cbm_gbuf_node_t **decls = NULL; + int decl_count = 0; + if (cbm_gbuf_find_by_label(gb, "SysctlDecl", &decls, &decl_count) != 0 || decl_count == 0) { + cbm_log_info("sysctl.skip", "reason", "no_sysctl_decls"); + return; + } + + sr_table_t table = {0}; + /* Seed fixed roots first so a missing SYSCTL_ROOT_NODE decl still resolves. */ + for (int i = 0; SR_ROOTS[i].sym; i++) { + sr_add(&table, SR_ROOTS[i].sym, SR_ROOTS[i].seg, NULL); + } + + /* Pass 2a: register every node-kind decl into the symbol table. */ + for (int i = 0; i < decl_count; i++) { + const char *props = decls[i]->properties_json; + if (!props || !jget_bool(props, "is_node")) { + continue; + } + char self[SR_SYM], seg[SR_SEG], parent[SR_SYM]; + if (!jget(props, "self_symbol", self, sizeof(self))) { + continue; + } + if (!jget(props, "leaf", seg, sizeof(seg))) { + continue; + } + bool has_parent = jget(props, "parent_symbol", parent, sizeof(parent)); + sr_add(&table, self, seg, has_parent ? parent : NULL); + } + + /* Pass 2b: resolve each leaf-kind decl and emit the final Sysctl node. */ + int resolved = 0, unresolved = 0; + for (int i = 0; i < decl_count; i++) { + const char *props = decls[i]->properties_json; + if (!props || jget_bool(props, "is_node")) { + continue; /* skip node-kind decls; only leaves are user-facing */ + } + char leaf[SR_SEG], parent[SR_SYM]; + if (!jget(props, "leaf", leaf, sizeof(leaf))) { + continue; + } + bool have_parent = jget(props, "parent_symbol", parent, sizeof(parent)); + + char base[SR_PATH]; + char path[SR_PATH]; + bool ok = have_parent && sr_resolve(&table, parent, base, sizeof(base)); + if (ok) { + snprintf(path, sizeof(path), "%s.%s", base, leaf); + resolved++; + } else { + /* Emit with an unresolved-parent marker so the leaf is still + * discoverable; never silently drop. */ + snprintf(path, sizeof(path), ".%s", leaf); + unresolved++; + } + + char qn[SR_PATH + 16]; + snprintf(qn, sizeof(qn), "__sysctl__%s", path); + char sprops[CBM_SZ_256]; + snprintf(sprops, sizeof(sprops), "{\"path\":\"%s\",\"resolved\":%s}", path, + ok ? "true" : "false"); + int64_t sid = cbm_gbuf_upsert_node(gb, "Sysctl", path, qn, decls[i]->file_path, + decls[i]->start_line, decls[i]->end_line, sprops); + + /* Edge from the implementing function to the sysctl. Prefer the + * enclosing function QN; fall back to the named handler symbol. */ + if (sid > 0) { + const cbm_gbuf_node_t *src = NULL; + char enc[CBM_SZ_512]; + if (jget(props, "enclosing", enc, sizeof(enc)) && enc[0]) { + src = cbm_gbuf_find_by_qn(gb, enc); + } + if (!src) { + char h[SR_SYM]; + if (jget(props, "handler", h, sizeof(h)) && h[0]) { + const cbm_gbuf_node_t **hits = NULL; + int hc = 0; + if (cbm_gbuf_find_by_name(gb, h, &hits, &hc) == 0 && hc > 0) { + src = hits[0]; + } + } + } + if (src) { + cbm_gbuf_insert_edge(gb, src->id, sid, "IMPLEMENTS_SYSCTL", "{}"); + } + } + } + + free(table.items); + char rbuf[CBM_SZ_16], ubuf[CBM_SZ_16]; + snprintf(rbuf, sizeof(rbuf), "%d", resolved); + snprintf(ubuf, sizeof(ubuf), "%d", unresolved); + cbm_log_info("sysctl.resolved", "resolved", rbuf, "unresolved", ubuf); +} diff --git a/devel/codebase-memory-mcp/files/patch-Makefile.cbm b/devel/codebase-memory-mcp/files/patch-Makefile.cbm new file mode 100644 index 000000000000..dd40510a7e68 --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-Makefile.cbm @@ -0,0 +1,18 @@ +--- Makefile.cbm.orig 2026-08-18 20:39:59 UTC ++++ Makefile.cbm +@@ -244,6 +244,7 @@ EXTRACTION_SRCS = \ + $(CBM_DIR)/extract_type_assigns.c \ + $(CBM_DIR)/extract_env_accesses.c \ + $(CBM_DIR)/extract_channels.c \ ++ $(CBM_DIR)/extract_sysctl.c \ + $(CBM_DIR)/extract_k8s.c \ + $(CBM_DIR)/helpers.c \ + $(CBM_DIR)/lang_specs.c \ +@@ -355,6 +356,7 @@ PIPELINE_SRCS = \ + src/pipeline/pass_complexity.c \ + src/pipeline/pass_cross_repo.c \ + src/pipeline/artifact.c \ ++ src/pipeline/pass_sysctl.c \ + src/pipeline/pass_pkgmap.c + + # SimHash / MinHash module diff --git a/devel/codebase-memory-mcp/files/patch-internal_cbm_cbm.c b/devel/codebase-memory-mcp/files/patch-internal_cbm_cbm.c new file mode 100644 index 000000000000..8911e9114198 --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-internal_cbm_cbm.c @@ -0,0 +1,24 @@ +--- internal/cbm/cbm.c.orig 2026-08-18 20:39:59 UTC ++++ internal/cbm/cbm.c +@@ -188,6 +188,11 @@ void cbm_channels_push(CBMChannelArray *arr, CBMArena + arr->items[arr->count++] = ch; + } + ++void cbm_sysctl_push(CBMSysctlArray *arr, CBMArena *a, CBMSysctl s) { ++ GROW_ARRAY(arr, a); ++ arr->items[arr->count++] = s; ++} ++ + // --- String input reader (for parse_with_options) --- + + typedef struct { +@@ -1297,6 +1302,9 @@ CBMFileResult *cbm_extract_file_ex(const char *source, + + // Channel detection (Socket.IO / EventEmitter) — JS/TS only. + cbm_extract_channels(&ctx); ++ ++ // FreeBSD/DragonFly sysctl OID collection — C only (no-op elsewhere). ++ cbm_extract_sysctl(&ctx); + + // K8s / Kustomize semantic pass (additional structured extraction for YAML-based infra files). + if (ctx.language == CBM_LANG_KUSTOMIZE || ctx.language == CBM_LANG_K8S) { diff --git a/devel/codebase-memory-mcp/files/patch-internal_cbm_cbm.h b/devel/codebase-memory-mcp/files/patch-internal_cbm_cbm.h new file mode 100644 index 000000000000..73f007c586bf --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-internal_cbm_cbm.h @@ -0,0 +1,61 @@ +--- internal/cbm/cbm.h.orig 2026-08-18 20:39:59 UTC ++++ internal/cbm/cbm.h +@@ -355,6 +355,21 @@ typedef struct { + CBMChannelDirection direction; + } CBMChannel; + ++// FreeBSD/DragonFly sysctl OID declaration collected from a SYSCTL_* macro. ++// The runtime dotted path (kern.ipc.maxsockbuf) is NOT stored here — it is ++// resolved by the repo-level pass_sysctl walk, which joins parent_symbol chains ++// across files. See docs/sysctl-extractor-design.md. ++typedef struct { ++ const char *leaf_name; // arg segment, e.g. "maxsockbuf" or node "ipc" ++ const char *parent_symbol; // parent C-symbol, e.g. "_kern_ipc"; NULL for roots ++ const char *self_symbol; // this node's own C-symbol (nodes only), else NULL ++ const char *handler; // best-effort handler ident (PROC/OID), else NULL ++ const char *enclosing_func_qn; // QN of the enclosing function, if any ++ const char *file_rel; // borrowed rel path (for the emitted node) ++ bool is_node; // true = defines a subtree node; false = a leaf ++ int line; // 1-based source line ++} CBMSysctl; ++ + // Rust: impl Trait for Struct + typedef struct { + const char *trait_name; // trait name (raw text) +@@ -468,6 +483,12 @@ typedef struct { + int cap; + } CBMChannelArray; + ++typedef struct { ++ CBMSysctl *items; ++ int count; ++ int cap; ++} CBMSysctlArray; ++ + // Full extraction result for one file. + typedef struct CBMFileResult { + CBMArena arena; // owns local memory; composites may also retain child arenas below +@@ -486,6 +507,7 @@ typedef struct CBMFileResult { + CBMStringRefArray string_refs; // URL/config string literals from AST + CBMInfraBindingArray infra_bindings; // topic→URL pairs from IaC configs + CBMChannelArray channels; // Socket.IO / EventEmitter pub/sub participation ++ CBMSysctlArray sysctls; // FreeBSD/DragonFly SYSCTL_* OID declarations + + const char *module_qn; // module qualified name + const char *namespace_name; // declared namespace/package (Java/Kotlin/C#/PHP), NULL if none +@@ -722,6 +744,7 @@ void cbm_channels_push(CBMChannelArray *arr, CBMArena + void cbm_impltrait_push(CBMImplTraitArray *arr, CBMArena *a, CBMImplTrait it); + void cbm_resolvedcall_push(CBMResolvedCallArray *arr, CBMArena *a, CBMResolvedCall rc); + void cbm_channels_push(CBMChannelArray *arr, CBMArena *a, CBMChannel ch); ++void cbm_sysctl_push(CBMSysctlArray *arr, CBMArena *a, CBMSysctl s); + + // --- Sub-extractor entry points --- + +@@ -733,6 +756,7 @@ void cbm_extract_channels(CBMExtractCtx *ctx); + void cbm_extract_env_accesses(CBMExtractCtx *ctx); + void cbm_extract_type_assigns(CBMExtractCtx *ctx); + void cbm_extract_channels(CBMExtractCtx *ctx); ++void cbm_extract_sysctl(CBMExtractCtx *ctx); + + // Single-pass unified extraction (replaces the 7 calls above except defs+imports). + void cbm_extract_unified(CBMExtractCtx *ctx); diff --git a/devel/codebase-memory-mcp/files/patch-src_cli_cli.c b/devel/codebase-memory-mcp/files/patch-src_cli_cli.c index ff2e1ad198b7..3edabd31ea2d 100644 --- a/devel/codebase-memory-mcp/files/patch-src_cli_cli.c +++ b/devel/codebase-memory-mcp/files/patch-src_cli_cli.c @@ -1,56 +1,56 @@ ---- src/cli/cli.c.orig 2026-08-14 04:11:37 UTC +--- src/cli/cli.c.orig 2026-08-18 20:39:59 UTC +++ src/cli/cli.c -@@ -7963,7 +7963,12 @@ static void cbm_agent_installed_binary_path(const char +@@ -8178,7 +8178,12 @@ static void cbm_agent_installed_binary_path(const char static void cbm_agent_installed_binary_path(const char *home, char *binary_path, size_t binary_path_size) { -#ifdef _WIN32 +#if defined(__FreeBSD__) && defined(CBM_PKG_PREFIX) + /* The port/pkg install the binary under ${PREFIX}/bin, not ~/.local/bin, so + * agent configs (mcp.json, hooks) must point there. */ + (void)home; + snprintf(binary_path, binary_path_size, CBM_PKG_PREFIX "/bin/codebase-memory-mcp"); +#elif defined(_WIN32) snprintf(binary_path, binary_path_size, "%s/.local/bin/codebase-memory-mcp.exe", home); #else snprintf(binary_path, binary_path_size, "%s/.local/bin/codebase-memory-mcp", home); -@@ -9472,6 +9477,16 @@ static const char *cli_external_manager_name(const cha +@@ -9699,6 +9704,16 @@ static const char *cli_external_manager_name(const cha if (strstr(self_path, "/.cargo/bin/")) { return "cargo"; } +#if defined(__FreeBSD__) && defined(CBM_PKG_PREFIX) + /* FreeBSD ports/pkg install the binary under ${PREFIX}/bin (CBM_PKG_PREFIX + * is the port's PREFIX, default /usr/local). pkg owns that file, so install + * must not copy it into ~/.local/bin or edit PATH, and update must refuse + * and defer to pkg(8). Match only ${PREFIX}/bin/ so a manual --dir install + * elsewhere is still treated as ours. */ + if (strstr(self_path, CBM_PKG_PREFIX "/bin/") == self_path) { + return "FreeBSD pkg"; + } +#endif return NULL; } -@@ -9890,6 +9905,13 @@ int cbm_cmd_install(int argc, char **argv) { +@@ -10117,6 +10132,13 @@ int cbm_cmd_install(int argc, char **argv) { manager ? " by " : "", manager ? manager : "", self_path, bin_dir); } skip_binary = true; + /* We are not placing a binary, so agent configs must reference the one + * that is actually running, not the ~/.local/bin default that no file + * lives at (#pkg: FreeBSD ports install under /usr/local/bin). Retarget + * to the OS-reported self path when we have it. */ + if (self_path_exact && self_path[0]) { + snprintf(bin_target, sizeof(bin_target), "%s", self_path); + } } /* NOT stat(): on Windows it goes through the ANSI code page, so an -@@ -11939,6 +11961,8 @@ int cbm_cmd_update(int argc, char **argv) { +@@ -12227,6 +12249,8 @@ int cbm_cmd_update(int argc, char **argv) { (void)fprintf(stderr, " update it with: mise upgrade codebase-memory-mcp\n"); } else if (manager && strcmp(manager, "Homebrew") == 0) { (void)fprintf(stderr, " update it with: brew upgrade codebase-memory-mcp\n"); + } else if (manager && strcmp(manager, "FreeBSD pkg") == 0) { + (void)fprintf(stderr, " update it with: pkg upgrade codebase-memory-mcp\n"); } else { (void)fprintf(stderr, " update it through whichever tool installed it.\n"); } diff --git a/devel/codebase-memory-mcp/files/patch-src_daemon_ipc.c b/devel/codebase-memory-mcp/files/patch-src_daemon_ipc.c index c9e1d32101f4..d79b2027ee5f 100644 --- a/devel/codebase-memory-mcp/files/patch-src_daemon_ipc.c +++ b/devel/codebase-memory-mcp/files/patch-src_daemon_ipc.c @@ -1,32 +1,32 @@ ---- src/daemon/ipc.c.orig 2026-08-15 21:31:53 UTC +--- src/daemon/ipc.c.orig 2026-08-18 20:39:59 UTC +++ src/daemon/ipc.c @@ -313,6 +313,9 @@ int cbm_daemon_ipc_wait_pending(const cbm_ipc_pending_ #include #include #include +#if defined(__FreeBSD__) +#include // struct xucred (LOCAL_PEERCRED peer pid) +#endif #include #include -@@ -3060,6 +3063,19 @@ uint64_t cbm_daemon_ipc_connection_peer_pid(const cbm_ +@@ -3078,6 +3081,19 @@ uint64_t cbm_daemon_ipc_connection_peer_pid(const cbm_ return 0; } return (uint64_t)peer_pid; +#elif defined(__FreeBSD__) && defined(LOCAL_PEERCRED) + /* FreeBSD has neither SO_PEERCRED nor LOCAL_PEERPID; LOCAL_PEERCRED returns + * a struct xucred whose cr_pid (FreeBSD 13+) is the connecting peer's pid. + * cr_pid is only meaningful when the version matches and the socket is a + * connected stream, both true for our accepted control connection. */ + struct xucred credentials; + socklen_t length = sizeof(credentials); + if (getsockopt(connection->fd, SOL_LOCAL, LOCAL_PEERCRED, &credentials, &length) != 0 || + length != sizeof(credentials) || credentials.cr_version != XUCRED_VERSION || + credentials.cr_uid != geteuid() || credentials.cr_pid <= 0) { + return 0; + } + return (uint64_t)credentials.cr_pid; #else return 0; #endif diff --git a/devel/codebase-memory-mcp/files/patch-src_daemon_runtime.c b/devel/codebase-memory-mcp/files/patch-src_daemon_runtime.c deleted file mode 100644 index 052f38074c2d..000000000000 --- a/devel/codebase-memory-mcp/files/patch-src_daemon_runtime.c +++ /dev/null @@ -1,105 +0,0 @@ ---- src/daemon/runtime.c.orig 2026-08-15 16:26:11 UTC -+++ src/daemon/runtime.c -@@ -54,6 +54,12 @@ void cbm_daemon_runtime_force_peer_image_mismatch_for_ - #include - #include - #include -+#elif defined(__FreeBSD__) -+#include -+#include -+#include -+#include -+#include - #endif - - enum { -@@ -155,7 +161,7 @@ typedef struct { - HANDLE file; - BY_HANDLE_FILE_INFORMATION information; - LARGE_INTEGER size; --#elif defined(__APPLE__) || defined(__linux__) -+#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) - int fd; - struct stat status; - #endif -@@ -510,7 +516,7 @@ static uint64_t runtime_current_process_id(void) { - static uint64_t runtime_current_process_id(void) { - #ifdef _WIN32 - return (uint64_t)GetCurrentProcessId(); --#elif defined(__APPLE__) || defined(__linux__) -+#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) - return (uint64_t)getpid(); - #else - return 0; -@@ -524,7 +530,7 @@ static void runtime_process_image_reference_init(runti - memset(reference, 0, sizeof(*reference)); - #ifdef _WIN32 - reference->file = INVALID_HANDLE_VALUE; --#elif defined(__APPLE__) || defined(__linux__) -+#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) - reference->fd = -1; - #endif - } -@@ -538,7 +544,7 @@ static bool runtime_process_image_reference_release(ru - if (reference->file != INVALID_HANDLE_VALUE && !CloseHandle(reference->file)) { - ok = false; - } --#elif defined(__APPLE__) || defined(__linux__) -+#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) - if (reference->fd >= 0 && close(reference->fd) != 0) { - ok = false; - } -@@ -678,7 +684,7 @@ static bool runtime_mac_process_maps_file_executable(i - return false; - } - --#elif defined(__linux__) -+#elif defined(__linux__) || defined(__FreeBSD__) - - static bool runtime_linux_stat_same_image(const struct stat *first, const struct stat *second) { - return first && second && S_ISREG(first->st_mode) && S_ISREG(second->st_mode) && -@@ -814,6 +820,35 @@ static bool runtime_process_image_reference_acquire( - } else if (image_fd >= 0) { - (void)close(image_fd); - } -+#elif defined(__FreeBSD__) -+ /* FreeBSD mounts no /proc by default, so /proc//exe is unavailable. -+ * sysctl KERN_PROC_PATHNAME resolves any pid's executable path directly. -+ * Open that path and hold it as the image reference, mirroring the Linux -+ * branch's stat-bracketing to detect a swap under us. */ -+ if (process_id > INT_MAX) { -+ return false; -+ } -+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, (int)process_id}; -+ char image_path[PATH_MAX]; -+ size_t image_path_size = sizeof(image_path); -+ bool path_ok = sysctl(mib, 4, image_path, &image_path_size, NULL, 0) == 0 && -+ image_path_size > 1 && image_path_size <= sizeof(image_path); -+ int image_fd = path_ok ? open(image_path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK) : -1; -+ struct stat image_before; -+ struct stat image_after; -+ bool ok = image_fd >= 0 && fstat(image_fd, &image_before) == 0 && -+ S_ISREG(image_before.st_mode) && -+ (!fingerprint || -+ cbm_daemon_build_fingerprint_native_file((uintptr_t)image_fd, fingerprint)) && -+ fstat(image_fd, &image_after) == 0 && -+ runtime_linux_stat_same_image(&image_before, &image_after); -+ if (ok) { -+ reference->held = true; -+ reference->fd = image_fd; -+ reference->status = image_after; -+ } else if (image_fd >= 0) { -+ (void)close(image_fd); -+ } - #else - (void)process_id; - bool ok = false; -@@ -854,7 +889,7 @@ static bool runtime_process_image_reference_matches_pr - runtime_mac_stat_same(&active->status, &peer.status); - bool released = runtime_process_image_reference_release(&peer); - return same && released; --#elif defined(__linux__) -+#elif defined(__linux__) || defined(__FreeBSD__) - runtime_process_image_reference_t peer; - runtime_process_image_reference_init(&peer); - bool same = runtime_process_image_reference_acquire(process_id, &peer, NULL); diff --git a/devel/codebase-memory-mcp/files/patch-src_pipeline_pass__definitions.c b/devel/codebase-memory-mcp/files/patch-src_pipeline_pass__definitions.c new file mode 100644 index 000000000000..f4849773da7f --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-src_pipeline_pass__definitions.c @@ -0,0 +1,18 @@ +--- src/pipeline/pass_definitions.c.orig 2026-08-18 20:39:59 UTC ++++ src/pipeline/pass_definitions.c +@@ -812,6 +812,7 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t * + * map is available without the cache (single-file scope). */ + total_imports += create_import_edges_for_file(ctx, result, rel, NULL); + create_channel_edges_for_file(ctx, result, rel); ++ cbm_sysctl_emit_raw_for_file(ctx, result, rel); + cbm_pipeline_create_env_configures_for_file(ctx, result, rel); + cbm_free_result(result); + } +@@ -845,6 +846,7 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t * + total_imports += + create_import_edges_for_file(ctx, result, files[i].rel_path, namespace_map); + create_channel_edges_for_file(ctx, result, files[i].rel_path); ++ cbm_sysctl_emit_raw_for_file(ctx, result, files[i].rel_path); + cbm_pipeline_create_env_configures_for_file(ctx, result, files[i].rel_path); + } + cbm_pipeline_namespace_map_free(namespace_map); diff --git a/devel/codebase-memory-mcp/files/patch-src_pipeline_pass__parallel.c b/devel/codebase-memory-mcp/files/patch-src_pipeline_pass__parallel.c new file mode 100644 index 000000000000..0a5c72c9e6c5 --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-src_pipeline_pass__parallel.c @@ -0,0 +1,10 @@ +--- src/pipeline/pass_parallel.c.orig 2026-08-18 20:39:59 UTC ++++ src/pipeline/pass_parallel.c +@@ -1343,6 +1343,7 @@ int cbm_build_registry_from_cache(cbm_pipeline_ctx_t * + + imports_edges += create_imports_edges(ctx, result, rel, namespace_map); + create_channel_edges(ctx, result, rel); ++ cbm_sysctl_emit_raw_for_file(ctx, result, rel); + cbm_pipeline_create_env_configures_for_file(ctx, result, rel); + } + diff --git a/devel/codebase-memory-mcp/files/patch-src_pipeline_pipeline.c b/devel/codebase-memory-mcp/files/patch-src_pipeline_pipeline.c new file mode 100644 index 000000000000..f8518624c9e0 --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-src_pipeline_pipeline.c @@ -0,0 +1,23 @@ +--- src/pipeline/pipeline.c.orig 2026-08-18 20:39:59 UTC ++++ src/pipeline/pipeline.c +@@ -934,6 +934,9 @@ static void predump_complexity(cbm_pipeline_ctx_t *ctx + static void predump_complexity(cbm_pipeline_ctx_t *ctx) { + cbm_pipeline_pass_complexity(ctx); + } ++static void predump_sysctl(cbm_pipeline_ctx_t *ctx) { ++ cbm_pipeline_resolve_sysctl(ctx->gbuf); ++} + static void run_predump_passes(cbm_pipeline_t *p, cbm_pipeline_ctx_t *ctx) { + static const struct { + predump_pass_fn fn; +@@ -943,8 +946,9 @@ static void run_predump_passes(cbm_pipeline_t *p, cbm_ + {predump_deco, "decorator_tags", false}, {predump_cfg, "configlink", false}, + {predump_route, "route_match", false}, {predump_sim, "similarity", true}, + {predump_sem, "semantic_edges", true}, {predump_complexity, "complexity", false}, ++ {predump_sysctl, "sysctl_resolve", false}, + }; +- enum { PREDUMP_PASS_COUNT = 6 }; ++ enum { PREDUMP_PASS_COUNT = 7 }; + struct timespec t; + for (int i = 0; i < PREDUMP_PASS_COUNT && !check_cancel(p); i++) { + /* "moderate_only" passes (similarity/semantic edges) run in FULL, diff --git a/devel/codebase-memory-mcp/files/patch-src_pipeline_pipeline__internal.h b/devel/codebase-memory-mcp/files/patch-src_pipeline_pipeline__internal.h new file mode 100644 index 000000000000..f0dfdbe97c3c --- /dev/null +++ b/devel/codebase-memory-mcp/files/patch-src_pipeline_pipeline__internal.h @@ -0,0 +1,16 @@ +--- src/pipeline/pipeline_internal.h.orig 2026-08-18 20:39:59 UTC ++++ src/pipeline/pipeline_internal.h +@@ -570,6 +570,13 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t * + int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, + int file_count); + ++/* FreeBSD/DragonFly sysctl OID resolution (src/pipeline/pass_sysctl.c). ++ * emit_raw is called per file from the consume step; resolve is a graph-only ++ * predump pass. */ ++void cbm_sysctl_emit_raw_for_file(cbm_pipeline_ctx_t *ctx, const CBMFileResult *result, ++ const char *rel); ++void cbm_pipeline_resolve_sysctl(cbm_gbuf_t *gb); ++ + int cbm_pipeline_pass_k8s(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, int file_count); + + int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, int file_count);