Skip to content

Commit 2efba85

Browse files
committed
Merge upstream checkpoint 13N (DeusData 612eee2, upstream main) into fork
Ancestry-preserving merge of upstream DeusData/codebase-memory-mcp main at 612eee2 ("Merge pull request DeusData#2911 ... FreeBSD dispatch workflow"). First parent is the 13M-2 integration head; 39 upstream-only commits across 6 first-parent merges: DeusData#1862 persistent install hook opt-out, DeusData#2667 Java external-receiver resolution gate, DeusData#2749 POSIX shell-free search_code scan, DeusData#2840 watcher reindex cooldown, DeusData#2903 manifest walk skips reserved `nul`, DeusData#2911 FreeBSD build workflow (workflow_dispatch only, non-gating). Conflict resolution (CLI-first): - src/mcp/mcp.c, tests/test_mcp.c: kept deleted. DeusData#2749's search change is ported to src/operations/source_search.c in the next commit. - README.md, scripts/smoke-test.sh, scripts/memory-core-baseline.txt, src/main.c help text: fork side (CLI identity, CLI-first smoke, operations baseline entries; no src/mcp entries). - docs/CONFIGURATION.md: fork command list plus `config get install_hooks`. - src/watcher/watcher.{c,h}: upstream success cooldown (DeusData#2840) anchored at index completion; failures/busy-skip keep the fork's after-index anchor and overflow-guarded schedule. Duplicate cbm_watcher_test_next_poll_ns (identical semantics) removed; upstream clock seam kept. - src/cli/cli.c (DeusData#1862, resolved by a Codex gpt-6-luna agent, verified here): `install --no-hooks` removes CLI-owned hooks and persists install_hooks=false; plain install/update/install-hooks honour it; `install --hooks` persists true. Applied to the fork's hook installers only; user-owned hooks, Codex asset/hook surface split, activation path checks and dry-run no-write behaviour retained. No MCP registration or config writing restored. main.c install usage shows [--hooks|--no-hooks]. Checks: build-dev.sh and build/c/test-runner compile with -Werror; lint-memory-core passes (3201 raw sites, none grew); strict Actions policy passes (freebsd.yml is dispatch-only); the 4 DeusData#1862 tests and 5 DeusData#2840 watcher tests pass (9/9).
2 parents 6ec8d58 + 612eee2 commit 2efba85

24 files changed

Lines changed: 2037 additions & 159 deletions

‎.github/workflows/freebsd.yml‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# FreeBSD build, INFORMATIONAL ONLY. Builds the headless binary (the C core,
2+
# no embedded UI) inside a FreeBSD VM with the canonical scripts/build.sh,
3+
# then checks that the binary starts (--version). The UI build is not run here:
4+
# its npm dependency tree pulls in onnxruntime-node, which publishes no FreeBSD
5+
# build, so `npm ci` stops with EBADPLATFORM. The UI bundle itself is
6+
# platform-independent JavaScript and is built on every other leg.
7+
#
8+
# NON-GATING: workflow_dispatch only. This workflow is not part of the ci-ok
9+
# aggregate and is not called by release.yml or dry-run.yml, so a FreeBSD red
10+
# never blocks a pull request or a release. Nothing from it is uploaded or
11+
# published, and it runs with read-only permissions.
12+
name: FreeBSD build (informational)
13+
14+
on:
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
build-freebsd:
22+
name: build (freebsd-${{ matrix.release }}, ${{ matrix.arch }}, clang)
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
release: ['15.1', '14.4']
27+
arch: ['x86_64']
28+
# arch: ['x86_64', 'aarch64']
29+
# aarch64 is disabled for now: it has not been proven with this
30+
# workflow. Its earlier blocker was the frontend build (lightningcss
31+
# publishes no freebsd-arm64 binary), which this headless job no
32+
# longer runs. Re-enable after a green aarch64 proof run.
33+
include:
34+
- arch: x86_64
35+
sync: rsync
36+
# - arch: aarch64
37+
# sync: scp
38+
# Must be an x86_64 host (ubuntu-latest). QEMU guest emulation on the arm
39+
# runners (ubuntu-*-arm) degrades so badly that runs hit 3+ hour timeouts.
40+
runs-on: ubuntu-latest
41+
timeout-minutes: 90
42+
steps:
43+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44+
with:
45+
persist-credentials: false
46+
47+
- name: Build and run inside a FreeBSD ${{ matrix.release }} (${{ matrix.arch }}) VM
48+
# https://github.com/vmactions/freebsd-vm
49+
uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc # v1.5.9
50+
with:
51+
release: ${{ matrix.release }}
52+
arch: ${{ matrix.arch }}
53+
# Sync options: rsync, sshfs, nfs, scp
54+
sync: ${{ matrix.sync }}
55+
# Nothing from the VM is used after the run, so skip the copy back.
56+
copyback: false
57+
mem: 6144
58+
cpu: 4
59+
prepare: |
60+
pkg update -f
61+
pkg install -y gmake sqlite3 pkgconf bash
62+
which python3 >/dev/null 2>&1 || ln -sf $(which python3.15 python3.14 python3.13 python3.12 python3.11 python3.10 2>/dev/null | head -n 1) /usr/local/bin/python3
63+
run: |
64+
scripts/build.sh CC=clang CXX=clang++
65+
build/c/codebase-memory-mcp --version

‎Makefile.cbm‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,8 @@ TEST_PY_LSP_STRESS_SRCS = tests/test_py_lsp_stress.c
889889
TEST_PY_LSP_SCALE_SRCS = tests/test_py_lsp_scale.c
890890

891891
TEST_TS_LSP_SRCS = tests/test_ts_lsp.c
892-
TEST_JAVA_LSP_SRCS = tests/test_java_lsp.c tests/test_java_lsp_coverage.c
892+
TEST_JAVA_LSP_SRCS = tests/test_java_lsp.c tests/test_java_lsp_coverage.c \
893+
tests/test_java_typed_calls.c
893894
TEST_KOTLIN_LSP_SRCS = tests/test_kotlin_lsp.c tests/test_kotlin_typed_calls.c
894895
TEST_RUST_LSP_SRCS = tests/test_rust_lsp.c
895896

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ To configure detected agent assets without replacing an externally managed binar
162162
codebase-memory-cli install --skip-binary
163163
```
164164

165-
Use `install --plan` to inspect asset writes and `install-hooks --plan` to inspect hook writes before applying either surface. `--clients=<list>` is supported independently by both commands.
165+
Use `install --plan` to inspect asset writes and `install-hooks --plan` to inspect hook writes before applying either surface. `--clients=<list>` is supported independently by both commands. `install --no-hooks` removes CLI-owned hooks and stores the opt-out (`config get install_hooks`); later `install`, `update`, and `install-hooks` honour it until `install --hooks` clears it.
166166

167167
## Installation
168168

‎docs/CONFIGURATION.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ codebase-memory-cli config set watcher_enabled false
8181
codebase-memory-cli config set ignore_worktrees true
8282
codebase-memory-cli config set doc_candidates false
8383
codebase-memory-cli config reset doc_candidates
84+
codebase-memory-cli config get install_hooks
8485
```
8586

8687
`doc_candidates` is `auto` by default: unset, the index mode decides. A single
@@ -103,6 +104,9 @@ Current keys:
103104
| `index_max_source_mb` | `off` | Optional maximum accepted source size in MiB in one discovery run. |
104105
| `index_max_rss_mb` | `off` | Optional maximum charged memory in MiB for the complete contained index-worker process tree (`64..1048576`). On macOS this is phys_footprint, not resident size. |
105106
| `index_max_duration_seconds` | `off` | Optional maximum wall-clock duration in seconds for the whole index request, including crash/hang recovery (`1..86400`). Like `index_max_rss_mb`, it stops a worker only before it starts to publish the new index; see [INDEX_RESOURCE_LIMITS.md](INDEX_RESOURCE_LIMITS.md). |
107+
| `install_hooks` | `true` | Whether CLI-owned client hooks may be installed. `install --no-hooks` removes CLI-owned hooks and persists `false`; `install --hooks` restores `true`. |
108+
109+
While `install_hooks` is `false`, `install`, `update`, and `install-hooks` leave CLI-owned hooks off (`install` and `update` re-remove them); user-owned hook entries are never touched. `install --hooks` only clears the opt-out; run `install-hooks` afterwards to add hooks. Skills and instructions are unaffected.
106110

107111
> **`watcher_enabled` vs `auto_watch`.** `watcher_enabled` controls whether the
108112
> retained watcher subsystem starts at all. `auto_watch` is narrower: it controls

‎internal/cbm/lsp/java_lsp.c‎

Lines changed: 167 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,19 @@ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) {
510510
}
511511
}
512512

513-
/* 3. java.lang auto-import. Try registered first, then the conventional QN. */
513+
/* 3. Java package prefix. A type of the own package shadows every
514+
* on-demand import, java.lang's included (JLS 6.4.1). */
515+
if (ctx->package_name && ctx->package_name[0]) {
516+
char buf[JAVA_LSP_BUF];
517+
int n = snprintf(buf, sizeof(buf), "%s.%s", ctx->package_name, name);
518+
if (n > 0 && (size_t)n < sizeof(buf)) {
519+
if (cbm_registry_lookup_type(ctx->registry, buf)) {
520+
return cbm_arena_strdup(ctx->arena, buf);
521+
}
522+
}
523+
}
524+
525+
/* 4. java.lang auto-import. Try registered first, then the conventional QN. */
514526
for (int i = 0; JAVA_LANG_TYPES[i]; i++) {
515527
if (strcmp(JAVA_LANG_TYPES[i], name) == 0) {
516528
char buf[JAVA_LSP_BUF];
@@ -521,7 +533,7 @@ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) {
521533
}
522534
}
523535

524-
/* 4. On-demand imports. */
536+
/* 5. On-demand imports. */
525537
for (int i = 0; i < ctx->import_count; i++) {
526538
if (ctx->import_kinds[i] != CBM_JAVA_IMPORT_ON_DEMAND)
527539
continue;
@@ -534,17 +546,6 @@ const char *java_resolve_type_name(JavaLSPContext *ctx, const char *name) {
534546
}
535547
}
536548

537-
/* 5. Java package prefix. */
538-
if (ctx->package_name && ctx->package_name[0]) {
539-
char buf[JAVA_LSP_BUF];
540-
int n = snprintf(buf, sizeof(buf), "%s.%s", ctx->package_name, name);
541-
if (n > 0 && (size_t)n < sizeof(buf)) {
542-
if (cbm_registry_lookup_type(ctx->registry, buf)) {
543-
return cbm_arena_strdup(ctx->arena, buf);
544-
}
545-
}
546-
}
547-
548549
/* Exact already-qualified name. */
549550
if (cbm_registry_lookup_type(ctx->registry, name)) {
550551
return cbm_arena_strdup(ctx->arena, name);
@@ -969,6 +970,12 @@ const CBMRegisteredFunc *java_lookup_method(JavaLSPContext *ctx, const char *cla
969970
return fallback;
970971
}
971972

973+
/* A type declared in the project: the cross registrar and the own-file pass
974+
* stamp namespace_qn on every project type; the JDK seed leaves it NULL. */
975+
static bool java_is_project_type(const CBMRegisteredType *rt) {
976+
return rt && rt->namespace_qn && rt->namespace_qn[0];
977+
}
978+
972979
/* ── Method-invocation evaluation ─────────────────────────────────── */
973980

974981
static const CBMType *java_return_type_of(const CBMRegisteredFunc *f) {
@@ -1797,11 +1804,135 @@ void java_lsp_process_file(JavaLSPContext *ctx, TSNode root) {
17971804

17981805
/* ── Call-edge resolution ─────────────────────────────────────────── */
17991806

1807+
/* True when `callee_qn` names a member (or a constructor) of a registered type
1808+
* the project does not declare — a JDK seed type. Such a target has no graph
1809+
* node, and the pipeline must not hand it to a same-named project method. */
1810+
static bool java_callee_is_external(const JavaLSPContext *ctx, const char *callee_qn) {
1811+
if (!ctx->registry || !callee_qn)
1812+
return false;
1813+
const CBMRegisteredType *self = cbm_registry_lookup_type(ctx->registry, callee_qn);
1814+
if (self)
1815+
return !java_is_project_type(self);
1816+
const char *dot = strrchr(callee_qn, '.');
1817+
if (!dot || dot == callee_qn)
1818+
return false;
1819+
char owner[JAVA_LSP_BUF];
1820+
size_t n = (size_t)(dot - callee_qn);
1821+
if (n >= sizeof(owner))
1822+
return false;
1823+
memcpy(owner, callee_qn, n);
1824+
owner[n] = '\0';
1825+
const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, owner);
1826+
return rt && !java_is_project_type(rt);
1827+
}
1828+
1829+
/* `T`, `ID`, `SELF`: a type-variable spelling — one or two characters, or
1830+
* capitals, digits and '_' only — that the file imports neither by name nor
1831+
* through its package (`import org.jooq.impl.*;` makes `DSL` a type). A type
1832+
* variable the resolver failed to bind can reach a receiver qualified by some
1833+
* package; it is unknown, not a library type. */
1834+
static bool java_is_type_var_spelling(const JavaLSPContext *ctx, const char *pkg, size_t pkg_len,
1835+
const char *simple) {
1836+
size_t n = strlen(simple);
1837+
bool caps = n > 0;
1838+
for (size_t i = 0; i < n && caps; i++) {
1839+
caps = isupper((unsigned char)simple[i]) || isdigit((unsigned char)simple[i]) ||
1840+
simple[i] == '_';
1841+
}
1842+
if (!caps && n > 2) {
1843+
return false;
1844+
}
1845+
for (int i = 0; i < ctx->import_count; i++) {
1846+
const char *target = ctx->import_target_qns[i];
1847+
if (ctx->import_kinds[i] == CBM_JAVA_IMPORT_TYPE && ctx->import_local_names[i] &&
1848+
strcmp(ctx->import_local_names[i], simple) == 0) {
1849+
return false;
1850+
}
1851+
if (ctx->import_kinds[i] == CBM_JAVA_IMPORT_ON_DEMAND && target &&
1852+
strlen(target) == pkg_len && strncmp(target, pkg, pkg_len) == 0) {
1853+
return false;
1854+
}
1855+
}
1856+
return true;
1857+
}
1858+
1859+
/* Whether the shared registry holds no project type under `prefix`
1860+
* ("org.jooq."), memoised per package. */
1861+
static bool java_package_is_external(JavaLSPContext *ctx, const CBMTypeRegistry *base,
1862+
const char *prefix) {
1863+
for (int i = 0; i < JAVA_EXT_MEMO; i++) {
1864+
if (ctx->ext_memo_pkg[i] && strcmp(ctx->ext_memo_pkg[i], prefix) == 0) {
1865+
return ctx->ext_memo_val[i];
1866+
}
1867+
}
1868+
CBMArena scratch;
1869+
cbm_arena_init(&scratch);
1870+
CBMQNPrefixIter it;
1871+
bool project = false;
1872+
/* The prefix search reads one layer, and the layer above the shared
1873+
* registry is a per-file overlay that is often empty: walk the chain. */
1874+
for (const CBMTypeRegistry *reg = base; reg && !project; reg = reg->fallback) {
1875+
if (!cbm_registry_types_with_qn_prefix(reg, prefix, true, &scratch, &it))
1876+
continue;
1877+
for (int ti = cbm_qn_prefix_iter_next(&it); ti >= 0 && !project;
1878+
ti = cbm_qn_prefix_iter_next(&it)) {
1879+
project = java_is_project_type(&reg->types[ti]);
1880+
}
1881+
}
1882+
cbm_arena_destroy(&scratch);
1883+
int slot = ctx->ext_memo_next++ % JAVA_EXT_MEMO;
1884+
ctx->ext_memo_pkg[slot] = prefix;
1885+
ctx->ext_memo_val[slot] = !project;
1886+
return !project;
1887+
}
1888+
1889+
/* A receiver type the registry does not know, spelled with a package the
1890+
* project declares no type in (`org.jooq.DSLContext` from an import): a
1891+
* library type. The package is the segments before the first capitalised
1892+
* one, so a nested type (`com.acme.Order.OrderBuilder`, registered flattened
1893+
* as `com.acme.OrderBuilder`) is judged by `com.acme`. Cross-file runs only —
1894+
* the shared registry is the one that knows every project package, and its
1895+
* type order makes the probe a binary search. A path-derived QN
1896+
* (project-prefixed) is an unresolved project spelling, never a library. */
1897+
static bool java_receiver_is_external(JavaLSPContext *ctx, const char *recv_qn) {
1898+
const CBMTypeRegistry *base = ctx->registry ? ctx->registry->fallback : NULL;
1899+
if (!base || !recv_qn || !islower((unsigned char)recv_qn[0]))
1900+
return false;
1901+
const char *type = NULL;
1902+
for (const char *p = recv_qn; (p = strchr(p, '.')) != NULL; p++) {
1903+
if (isupper((unsigned char)p[1])) {
1904+
type = p + 1;
1905+
break;
1906+
}
1907+
}
1908+
if (!type)
1909+
return false;
1910+
size_t pkg_len = (size_t)(type - 1 - recv_qn);
1911+
const char *last = strrchr(recv_qn, '.');
1912+
if (java_is_type_var_spelling(ctx, recv_qn, pkg_len, last + 1))
1913+
return false;
1914+
/* The project segment; a file at the repository root has a module QN
1915+
* that is the project name alone. */
1916+
const char *mod_dot = ctx->module_qn ? strchr(ctx->module_qn, '.') : NULL;
1917+
size_t mod_len = mod_dot ? (size_t)(mod_dot - ctx->module_qn)
1918+
: (ctx->module_qn ? strlen(ctx->module_qn) : 0);
1919+
if (mod_len && strncmp(recv_qn, ctx->module_qn, mod_len) == 0 && recv_qn[mod_len] == '.')
1920+
return false;
1921+
if (ctx->package_name && strlen(ctx->package_name) == pkg_len &&
1922+
strncmp(ctx->package_name, recv_qn, pkg_len) == 0)
1923+
return false;
1924+
/* "org.jooq." */
1925+
return java_package_is_external(ctx, base, cbm_arena_strndup(ctx->arena, recv_qn, pkg_len + 1));
1926+
}
1927+
18001928
static void java_emit_resolved_orig_kind(JavaLSPContext *ctx, const char *callee_qn,
18011929
const char *orig, const char *strategy, float confidence,
18021930
CBMResolvedKind kind) {
18031931
if (!ctx->resolved_calls || !ctx->enclosing_method_qn || !callee_qn)
18041932
return;
1933+
if (kind == CBM_RESOLVED_INVOCATION && java_callee_is_external(ctx, callee_qn)) {
1934+
strategy = "lsp_external";
1935+
}
18051936
CBMResolvedCall rc = {0};
18061937
rc.caller_qn = ctx->enclosing_method_qn;
18071938
rc.callee_qn = callee_qn;
@@ -2145,6 +2276,15 @@ static void resolve_method_call(JavaLSPContext *ctx, TSNode call) {
21452276
"lsp_interface_dispatch", 0.80f);
21462277
return;
21472278
}
2279+
/* A library type the registry does not hold: no method of it can be
2280+
* looked up, but no project method is its target either. A seeded
2281+
* JDK type with no such method stays a diagnostic below, as Rust's
2282+
* gate (#2053) needs a positively resolved std method. */
2283+
if (!rt && java_receiver_is_external(ctx, recv_qn)) {
2284+
java_emit_resolved(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", recv_qn, mname),
2285+
"lsp_external", 0.90f);
2286+
return;
2287+
}
21482288
java_emit_unresolved(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", recv_qn, mname),
21492289
"no_method_match");
21502290
return;
@@ -3214,6 +3354,9 @@ static void register_local_func_or_type_from_file(JavaLSPContext *ctx, CBMTypeRe
32143354
stub.qualified_name = d->qualified_name;
32153355
stub.short_name = d->name;
32163356
stub.is_interface = (strcmp(d->label, "Interface") == 0);
3357+
stub.namespace_qn = (ctx->package_name && ctx->package_name[0]) ? ctx->package_name
3358+
: (module_qn && module_qn[0]) ? module_qn
3359+
: d->qualified_name;
32173360
cbm_registry_add_type(reg, stub);
32183361
}
32193362

@@ -3740,6 +3883,11 @@ void cbm_java_register_lsp_defs(CBMArena *arena, CBMTypeRegistry *reg, const CBM
37403883
rt.qualified_name = d->qualified_name;
37413884
rt.short_name = d->short_name;
37423885
rt.is_interface = (strcmp(d->label, "Interface") == 0) || d->is_interface;
3886+
/* Project marker (java_is_project_type) and the package that
3887+
* qualifies the type's own member signatures. */
3888+
rt.namespace_qn = (d->namespace_name && d->namespace_name[0]) ? d->namespace_name
3889+
: (d->def_module_qn && d->def_module_qn[0]) ? d->def_module_qn
3890+
: d->qualified_name;
37433891
/* Embedded types from "|"-separated text. */
37443892
if (d->embedded_types && d->embedded_types[0]) {
37453893
int n = 1;
@@ -3883,6 +4031,9 @@ CBMTypeRegistry *cbm_java_build_cross_registry(CBMArena *arena, CBMLSPDef *defs,
38834031
cbm_java_register_lsp_defs(arena, reg, jvm + type_count, total - type_count);
38844032
}
38854033
cbm_registry_finalize(reg);
4034+
/* java_receiver_is_external asks "does the project declare a type in
4035+
* this package?" per unresolved receiver: a binary search on this order. */
4036+
cbm_registry_build_type_qn_order(reg, arena);
38864037
reg->read_only = true; /* seal: shared Tier-2 registry is read-only during resolve */
38874038
return reg;
38884039
}
@@ -3958,6 +4109,9 @@ void cbm_run_java_lsp_cross_with_registry(CBMArena *arena, CBMFileResult *result
39584109
}
39594110
}
39604111

4112+
/* The per-file fallback when no shared cross registry was built. Its
4113+
* registry has no fallback layer, so java_receiver_is_external never fires
4114+
* here: library calls keep the textual registry fallback, as before. */
39614115
void cbm_run_java_lsp_cross(CBMArena *arena, const char *source, int source_len,
39624116
const char *module_qn, CBMLSPDef *defs, int def_count,
39634117
const char **import_names, const char **import_qns, int import_count,

‎internal/cbm/lsp/java_lsp.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "../cbm.h"
3636
#include "go_lsp.h" /* CBMLSPDef, CBMResolvedCallArray reused across languages */
3737

38+
#define JAVA_EXT_MEMO 8
39+
3840
/* Java `use`-style import kinds. Mirrors PHP's enum with Java semantics. */
3941
enum {
4042
CBM_JAVA_IMPORT_TYPE = 0, /* import com.foo.Bar; — type import */
@@ -90,6 +92,12 @@ typedef struct {
9092

9193
/* Debug mode (CBM_LSP_DEBUG env). */
9294
bool debug;
95+
96+
/* java_receiver_is_external's answers for the packages probed so far
97+
* (a few per file), so each costs one registry walk. */
98+
const char *ext_memo_pkg[JAVA_EXT_MEMO];
99+
bool ext_memo_val[JAVA_EXT_MEMO];
100+
int ext_memo_next;
93101
} JavaLSPContext;
94102

95103
/* ── Initialization / configuration ───────────────────────────────── */

0 commit comments

Comments
 (0)