Skip to content

Commit bac535b

Browse files
authored
Merge branch 'main' into fix/slab-cross-thread-free
2 parents a1e5aa7 + 9871e7e commit bac535b

101 files changed

Lines changed: 8387 additions & 669 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/_security.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ jobs:
5151
CURRENT_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
5252
echo "Waiting for CodeQL to complete on $CURRENT_SHA..."
5353
for attempt in $(seq 1 90); do
54-
LATEST=$(gh api repos/${{ github.repository }}/actions/workflows/codeql.yml/runs?per_page=5 \
55-
--jq '.workflow_runs[] | select(.head_sha == "'"$CURRENT_SHA"'") | "\(.conclusion) \(.status)"' 2>/dev/null | head -1 || echo "")
54+
LATEST=$(gh api "repos/${{ github.repository }}/actions/workflows/codeql.yml/runs?head_sha=$CURRENT_SHA&per_page=1" \
55+
--jq '.workflow_runs[] | "\(.conclusion) \(.status)"' 2>/dev/null | head -1 || echo "")
5656
if [ -z "$LATEST" ]; then
5757
echo " $attempt/90: no run yet..."; sleep 30; continue
5858
fi

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ soak-results/
5959

6060
# LSP originality-check reference cache (scripts/check-lsp-originality.sh)
6161
.lsp-refs/
62+
63+
# Local npm cache
64+
graph-ui/.npm-cache-local/

Makefile.cbm

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
# Linux: gcc/g++ — system default with full sanitizer support
1313
# CI scripts pass CC/CXX explicitly; don't rely on defaults here
1414

15+
# Target architecture (macOS): build.sh/test.sh export ARCHFLAGS="-arch <arch>"
16+
# (see scripts/env.sh). Fold it into the compiler drivers with `override` so it
17+
# reaches EVERY compile and link recipe — including the vendored objects below
18+
# that use their own *_CFLAGS — and so it survives a command-line `CC=` override.
19+
# ARCHFLAGS is empty on Linux/Windows and for native direct `make` invocations,
20+
# leaving CC/CXX unchanged there.
21+
override CC := $(CC) $(ARCHFLAGS)
22+
override CXX := $(CXX) $(ARCHFLAGS)
23+
1524
# ── Common flags ─────────────────────────────────────────────────
1625

1726
# Include paths for:
@@ -112,7 +121,9 @@ FOUNDATION_SRCS = \
112121
src/foundation/mem.c \
113122
src/foundation/diagnostics.c \
114123
src/foundation/profile.c \
115-
src/foundation/dump_verify.c
124+
src/foundation/dump_verify.c \
125+
src/foundation/limits.c \
126+
src/foundation/subprocess.c
116127

117128
# Existing extraction C code (compiled from current location)
118129
EXTRACTION_SRCS = \
@@ -172,7 +183,7 @@ STORE_SRCS = src/store/store.c
172183
CYPHER_SRCS = src/cypher/cypher.c
173184

174185
# MCP server module (new)
175-
MCP_SRCS = src/mcp/mcp.c
186+
MCP_SRCS = src/mcp/mcp.c src/mcp/index_supervisor.c
176187

177188
# Discover module (new)
178189
DISCOVER_SRCS = \
@@ -309,7 +320,8 @@ TEST_FOUNDATION_SRCS = \
309320
tests/test_log.c \
310321
tests/test_str_util.c \
311322
tests/test_platform.c \
312-
tests/test_dump_verify.c
323+
tests/test_dump_verify.c \
324+
tests/test_subprocess.c
313325

314326
TEST_EXTRACTION_SRCS = \
315327
tests/test_extraction.c \
@@ -345,7 +357,7 @@ TEST_DISCOVER_SRCS = \
345357

346358
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
347359

348-
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c
360+
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c
349361

350362
TEST_WATCHER_SRCS = tests/test_watcher.c
351363

@@ -567,7 +579,7 @@ $(UNIXCODER_OBJ): $(UNIXCODER_BLOB_SRC) vendored/nomic/code_vectors.bin | $(BUIL
567579
OBJS_VENDORED_TEST = $(MIMALLOC_OBJ_TEST) $(SQLITE3_OBJ_TEST) $(TRE_OBJ_TEST) $(GRAMMAR_OBJS_TEST) $(TS_RUNTIME_OBJ_TEST) $(LSP_OBJ_TEST) $(PP_OBJ_TEST) $(LZ4_OBJ_TEST) $(ZSTD_OBJ_TEST) $(UNIXCODER_OBJ)
568580

569581
$(BUILD_DIR)/test-runner: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR)
570-
$(CC) $(CFLAGS_TEST) -o $@ \
582+
$(CC) $(CFLAGS_TEST) -Itests -Itests/repro -o $@ \
571583
$(ALL_TEST_SRCS) $(PROD_SRCS) \
572584
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
573585
$(OBJS_VENDORED_TEST) \

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,16 @@ Invoke-WebRequest -Uri https://raw.githubusercontent.com/DeusData/codebase-memor
5959
# 2. (Optional but recommended) Inspect the script
6060
notepad install.ps1
6161
62-
# 3. Run it
62+
# 3. Unblock the downloaded file (removes Mark-of-the-Web restriction added by browsers/Invoke-WebRequest)
63+
Unblock-File .\install.ps1
64+
65+
# 4. Run it
6366
.\install.ps1
6467
6568
```
6669

70+
> **Note:** If you see a script execution policy error, run `Set-ExecutionPolicy -Scope Process Bypass` first, or invoke with `PowerShell -ExecutionPolicy Bypass -File .\install.ps1`.
71+
6772
Options: `--ui` (graph visualization), `--skip-config` (binary only, no agent setup), `--dir=<path>` (custom location).
6873

6974
Restart your coding agent. Say **"Index this project"** — done.
@@ -86,6 +91,7 @@ Restart your coding agent. Say **"Index this project"** — done.
8691
Windows (PowerShell):
8792
```powershell
8893
Expand-Archive codebase-memory-mcp-windows-amd64.zip -DestinationPath .
94+
Unblock-File .\install.ps1
8995
.\install.ps1
9096
```
9197

@@ -452,6 +458,8 @@ Anything outside this subset (write/`MERGE`/`CALL` clauses, unsupported function
452458

453459
Layered: hardcoded patterns (`.git`, `node_modules`, etc.) → `.gitignore` hierarchy → `.cbmignore` (project-specific, gitignore syntax). Symlinks are always skipped.
454460

461+
See [docs/cbmignore.md](docs/cbmignore.md) for the full `.cbmignore` how-to: syntax, precedence across the ignore layers, and negation semantics.
462+
455463
## Configuration
456464

457465
```bash

docs/cbmignore.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# `.cbmignore` — Excluding Files from Indexing
2+
3+
`.cbmignore` is a project-specific ignore file that controls which files the
4+
indexer sees. It uses gitignore-style syntax and is read from the **root of
5+
the indexed directory** (`<repo>/.cbmignore`). Nested `.cbmignore` files in
6+
subdirectories are not read.
7+
8+
It applies at **file discovery time** — the directory walk that selects files
9+
for parsing. Every indexing path uses the same discovery: the initial
10+
`index_repository`, manual re-indexing, and background auto-sync. A path
11+
matched by `.cbmignore` never enters the graph. Changes to `.cbmignore` take
12+
effect on the next (re-)index.
13+
14+
Unlike `.gitignore`, it has no effect on git itself — it only shapes what the
15+
indexer sees. Commit it to share indexing excludes with your team, or list it
16+
in `.gitignore` to keep personal excludes untracked.
17+
18+
To verify it works: directory subtrees skipped during discovery are reported
19+
in the `index_repository` response under `excluded`
20+
(`{"dirs": [up to 25 paths], "count": <total>, "truncated": <bool>}`).
21+
22+
## Syntax
23+
24+
One pattern per line. Blank lines are ignored, lines starting with `#` are
25+
comments, and trailing whitespace is trimmed.
26+
27+
| Feature | Meaning |
28+
|---|---|
29+
| `*` | matches any run of characters, except `/` |
30+
| `?` | matches exactly one character, except `/` |
31+
| `**` | matches across directory boundaries (`**/name`, `dir/**`, `a/**/b`) |
32+
| `[abc]`, `[a-z]` | character classes; `[!a-z]` / `[^a-z]` negate the class |
33+
| trailing `/` | pattern matches **directories only** |
34+
| `/` anywhere else | anchors the pattern to the repo root |
35+
| no `/` in pattern | matches the file/directory name at **any depth** |
36+
| leading `!` | negation — re-includes a previously matched path; the **last matching pattern wins** |
37+
38+
Examples:
39+
40+
```gitignore
41+
# Generated protobuf output, anywhere in the tree
42+
*.pb.go
43+
44+
# A specific top-level directory (leading / anchors to the repo root)
45+
/third_party/
46+
47+
# Any directory named "snapshots", at any depth (trailing / = directories only)
48+
snapshots/
49+
50+
# Everything under any fixtures directory
51+
**/fixtures/**
52+
53+
# Anchored glob: generated clients for any single-character API version
54+
/api/v?/generated/
55+
56+
# Character class: yearly log folders 2020-2029
57+
/logs/202[0-9]/
58+
59+
# Ignore all YAML, but keep CI configs (negation — last match wins)
60+
*.yaml
61+
!ci.yaml
62+
```
63+
64+
## Precedence
65+
66+
Discovery applies its filters in a fixed order — the first layer that rejects
67+
a path wins. For directories:
68+
69+
1. **Built-in skip list**`.git`, `node_modules`, `dist`, `target`,
70+
`vendor`, tool caches, etc. (60+ names; the fast/moderate index modes add
71+
more, e.g. `docs`, `examples`, `testdata`). Not overridable from any
72+
ignore file today.
73+
2. **Repo `.gitignore`**`<repo>/.gitignore` merged with
74+
`<git-common-dir>/info/exclude` (worktree-aware); later patterns win on
75+
conflict. Honored even when the indexed directory is not a git repo root.
76+
3. **Nested `.gitignore` files** — picked up during the walk and matched
77+
relative to their own directory.
78+
4. **`.cbmignore`** — a positive match skips the path; a negated match can
79+
only rescue paths from layer 5.
80+
5. **Git global excludes**`core.excludesFile` from `~/.gitconfig` or the
81+
XDG git config (default `$XDG_CONFIG_HOME/git/ignore`); consulted only
82+
when the project is a git repo with a config.
83+
84+
For files, built-in suffix filters (`.png`, `.o`, `.db`, …; fast modes add
85+
archives, media, lockfiles, `.min.js`, …) and fast-mode filename/substring
86+
filters run **before** the ignore files, and a maximum-file-size cap runs
87+
after them; none of these are overridable from `.cbmignore`. Symlinks are
88+
always skipped.
89+
90+
## Negation (`!`) — current behavior
91+
92+
- **Within `.cbmignore`**: standard gitignore semantics. Patterns are
93+
evaluated top to bottom and the last matching pattern wins, so
94+
`!pattern` re-includes something an earlier line excluded.
95+
- **Parent pruning** (same caveat as git): when a directory is excluded, the
96+
walk never descends into it — you cannot re-include a file whose parent
97+
directory is excluded. Negate the directory itself if you need its
98+
contents.
99+
- **Across layers**: a `.cbmignore` negation overrides the **git global
100+
excludes** layer only. Example: your `~/.config/git/ignore` ignores
101+
`*.sql`, but this project's SQL should be indexed — add `!*.sql` to
102+
`.cbmignore`. Negation cannot override the built-in skip lists, the repo
103+
`.gitignore`/`info/exclude`, nested `.gitignore` files, the built-in
104+
suffix/filename filters, or the size cap.
105+
106+
### Planned (not yet implemented)
107+
108+
The negation story is being unified; none of the following works yet:
109+
110+
- `!` in `.cbmignore` will be able to un-skip ordinary built-in skip
111+
directories (`obj/`, `dist/`, `target/`, …) so build-output-like
112+
directories that actually contain source can be indexed.
113+
- A small safety core stays non-negatable by design — `.git`,
114+
`node_modules`, and worktree-internal directories — because indexing them
115+
risks OOM and correctness issues (see issue #489).
116+
- Auxiliary filesystem walkers will honor the same ignore predicate as
117+
discovery, so every code path sees an identical ignore decision
118+
(unification tracked in a follow-up issue).
119+
120+
Until these land, the "Precedence" and "Negation — current behavior" sections
121+
above describe the actual behavior.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* @vitest-environment jsdom */
2+
import "@testing-library/jest-dom/vitest";
3+
import { fireEvent, render, screen } from "@testing-library/react";
4+
import { afterEach, describe, expect, it, vi } from "vitest";
5+
import { GraphTab } from "./GraphTab";
6+
import type { GraphData } from "../lib/types";
7+
8+
/* GraphScene renders a WebGL <Canvas> which jsdom can't run — stub it out. */
9+
vi.mock("./GraphScene", () => ({
10+
GraphScene: () => null,
11+
computeCameraTarget: () => null,
12+
}));
13+
14+
const SAMPLE: GraphData = {
15+
nodes: [
16+
{ id: 1, x: 0, y: 0, z: 0, label: "Function", name: "foo", size: 1, color: "#fff" },
17+
{ id: 2, x: 1, y: 0, z: 0, label: "Class", name: "Bar", size: 1, color: "#fff" },
18+
],
19+
edges: [{ source: 1, target: 2, type: "CALLS" }],
20+
total_nodes: 2,
21+
};
22+
23+
function mockLayoutFetch(data: GraphData) {
24+
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
25+
const url = String(input);
26+
if (url.startsWith("/api/layout")) {
27+
return new Response(JSON.stringify(data), {
28+
status: 200,
29+
headers: { "Content-Type": "application/json" },
30+
});
31+
}
32+
return new Response("{}", { status: 200 });
33+
});
34+
vi.stubGlobal("fetch", fetchMock);
35+
return fetchMock;
36+
}
37+
38+
describe("GraphTab filters", () => {
39+
afterEach(() => {
40+
vi.unstubAllGlobals();
41+
});
42+
43+
it("keeps the filter sidebar visible when all nodes are filtered out", async () => {
44+
mockLayoutFetch(SAMPLE);
45+
46+
render(<GraphTab project="demo" />);
47+
48+
/* Wait for the layout to load — the filter panel header appears. */
49+
expect(await screen.findByText("Filters")).toBeInTheDocument();
50+
51+
/* Disable every filter via the "None" shortcut. */
52+
fireEvent.click(screen.getByRole("button", { name: "None" }));
53+
54+
/* The graph area reports that everything is filtered out… */
55+
expect(screen.getByText("All nodes filtered out")).toBeInTheDocument();
56+
57+
/* …but the filter sidebar must stay so the user can re-enable filters
58+
instead of being forced to reset everything. */
59+
expect(screen.getByText("Filters")).toBeInTheDocument();
60+
expect(screen.getByRole("button", { name: "All" })).toBeInTheDocument();
61+
expect(screen.getByRole("button", { name: "None" })).toBeInTheDocument();
62+
});
63+
});

0 commit comments

Comments
 (0)