Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e8dcb28
test(hook-augment): pin worktree payload-cwd resolution
bmcnaboe Aug 20, 2026
dadc57d
fix(smoke): keep MCP tool inventory exact
tamerbak Sep 20, 2026
cda5e3c
docs(ci): avoid hard-coded MCP tool count
tamerbak Sep 20, 2026
6115e57
Merge branch 'main' into fix/hook-augment-worktree-tests
DeusData Sep 21, 2026
183cd72
fix(test): isolate smoke harness daemon runtime
astandrik Sep 13, 2026
fa138ac
fix(test): arm smoke runtime cleanup at init, retire the daemon first
astandrik Sep 21, 2026
da4bd25
fix(test): the ENOSPC bootstrap test no longer lets a deadline decide…
DeusData Sep 21, 2026
f3bdf11
feat(pipeline): link markdown file references into the graph
cdeust Aug 25, 2026
6ae6cd4
fix(pipeline): address doclinks review feedback
cdeust Sep 22, 2026
9a9534c
docs: list REFERENCES_FILE in the README edge types table
cdeust Sep 22, 2026
1d2bbc4
fix(windows): preserve worker recovery errors
knewstimek Sep 22, 2026
9b5bf70
Merge branch 'main' into fix/test-daemon-bootstrap-sync-spawn
DeusData Sep 24, 2026
f07c187
Merge branch 'main' into fix/1696-smoke-runtime-isolation
DeusData Sep 24, 2026
fcdc6be
Merge branch 'main' into fix/smoke-tool-inventory
DeusData Sep 24, 2026
92cafd1
Merge branch 'main' into fix/hook-augment-worktree-tests
DeusData Sep 24, 2026
5c69c14
Merge pull request #2272 from DeusData/fix/test-daemon-bootstrap-sync…
DeusData Sep 25, 2026
a5550e1
Merge pull request #1768 from bmcnaboe/fix/hook-augment-worktree-tests
DeusData Sep 25, 2026
c98323b
Merge pull request #2199 from astandrik/fix/1696-smoke-runtime-isolation
DeusData Sep 25, 2026
8ab13c9
Merge pull request #2263 from tamerbak/fix/smoke-tool-inventory
DeusData Sep 25, 2026
da3ea36
Merge pull request #1832 from cdeust/feat/doclinks-markdown
DeusData Sep 25, 2026
64c23fa
Merge pull request #2282 from knewstimek/pr/windows-worker-recovery-fix
DeusData Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Smoke invariants across the WIDEST set of GitHub-hosted runners. Builds the
# production source path with explicit fault-injection test seams and runs
# scripts/smoke-invariants.sh
# (version/help, MCP initialize handshake [#513], all 14 tools invocable, malformed-
# (version/help, MCP initialize handshake [#513], all registered tools invocable, malformed-
# input resilience, clean EOF exit, shared-lib resolution, install dry-run).
#
# Maximizing platforms is the point: ubuntu-22.04 (older glibc → AlmaLinux/#182
Expand Down
3 changes: 2 additions & 1 deletion Makefile.cbm
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ PIPELINE_SRCS = \
src/pipeline/pass_gitdiff.c \
src/pipeline/pass_configures.c \
src/pipeline/pass_configlink.c \
src/pipeline/pass_doclinks.c \
src/pipeline/pass_route_nodes.c \
src/pipeline/pass_enrichment.c \
src/pipeline/pass_envscan.c \
Expand Down Expand Up @@ -675,7 +676,7 @@ TEST_DISCOVER_SRCS = \

TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c

TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_importance.c tests/test_cross_repo.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 tests/test_index_format.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_importance.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_doclinks.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_index_format.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c

TEST_WATCHER_SRCS = tests/test_watcher.c

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ JSON arguments can also be piped on stdin, for tools that take arguments. A tool

### Edge Types

`CONTAINS_PACKAGE`, `CONTAINS_FOLDER`, `CONTAINS_FILE`, `DEFINES`, `DEFINES_METHOD`, `IMPORTS`, `CALLS`, `CALL_REFERENCE`, `HTTP_CALLS`, `ASYNC_CALLS`, `IMPLEMENTS`, `HANDLES`, `USAGE`, `CONFIGURES`, `WRITES`, `MEMBER_OF`, `TESTS`, `USES_TYPE`, `FILE_CHANGES_WITH`
`CONTAINS_PACKAGE`, `CONTAINS_FOLDER`, `CONTAINS_FILE`, `DEFINES`, `DEFINES_METHOD`, `IMPORTS`, `CALLS`, `CALL_REFERENCE`, `HTTP_CALLS`, `ASYNC_CALLS`, `IMPLEMENTS`, `HANDLES`, `USAGE`, `CONFIGURES`, `REFERENCES_FILE`, `WRITES`, `MEMBER_OF`, `TESTS`, `USES_TYPE`, `FILE_CHANGES_WITH`

### Qualified Names

Expand Down
28 changes: 21 additions & 7 deletions scripts/smoke-invariants.sh
Original file line number Diff line number Diff line change
Expand Up @@ -520,10 +520,10 @@ inv_mcp_initialize() {
return 0
}

# ── Invariant 4: tools/list returns all expected tools ─────────────────────
# Cross-check against the canonical 14-tool list (TOOLS[] in src/mcp/mcp.c).
EXPECTED_TOOLS="index_repository search_graph query_graph trace_path get_code_snippet get_graph_schema get_architecture search_code list_projects delete_project index_status detect_changes manage_adr ingest_traces"
EXPECTED_TOOL_COUNT=14
# ── Invariant 4: tools/list returns exactly the expected tools ─────────────
# Cross-check against the canonical registry (TOOLS[] in src/mcp/mcp.c).
EXPECTED_TOOLS="index_repository search_graph query_graph trace_path get_code_snippet get_file_outline get_graph_schema compare_graphs get_architecture search_code list_projects delete_project index_status check_index_coverage detect_changes manage_adr ingest_traces"
EXPECTED_TOOL_COUNT="$(printf '%s\n' $EXPECTED_TOOLS | grep -c .)"
inv_tools_list() {
if ! mcp_alive; then
fail "tools-list" "server not alive"
Expand Down Expand Up @@ -573,18 +573,29 @@ print(r.get("nextCursor") or "")' 2>/dev/null)"
fail "tools-list" "got $got_count tools across $page page(s), expected $EXPECTED_TOOL_COUNT; names=[$got_names]"
return
fi
local missing=""
local missing="" extra="" duplicates=""
local t
for t in $EXPECTED_TOOLS; do
case " $got_names " in
*" $t "*) ;;
*) missing="$missing $t" ;;
esac
done
for t in $got_names; do
case " $EXPECTED_TOOLS " in
*" $t "*) ;;
*) extra="$extra $t" ;;
esac
done
duplicates="$(printf '%s\n' $got_names | sort | uniq -d | tr '\n' ' ')"
if [ -n "$missing" ]; then
fail "tools-list" "missing tools:$missing"
elif [ -n "$extra" ]; then
fail "tools-list" "unexpected tools:$extra"
elif [ -n "$duplicates" ]; then
fail "tools-list" "duplicate tools: $duplicates"
else
pass "tools-list (all $EXPECTED_TOOL_COUNT tools present)"
pass "tools-list (exactly $EXPECTED_TOOL_COUNT unique tools present)"
fi
}

Expand Down Expand Up @@ -626,7 +637,7 @@ inv_every_tool() {
return
fi

# name|minimal-args (JSON object) for the remaining 13 tools.
# name|minimal-args (JSON object) for the remaining 16 tools.
# Args chosen to be minimally valid per TOOLS[] required fields.
local p="$PROJ_NAME"
local -a CALLS
Expand All @@ -635,11 +646,14 @@ inv_every_tool() {
"query_graph|{\"project\":\"$p\",\"query\":\"MATCH (n) RETURN n.name LIMIT 5\"}"
"trace_path|{\"project\":\"$p\",\"function_name\":\"compute\",\"direction\":\"both\"}"
"get_code_snippet|{\"project\":\"$p\",\"qualified_name\":\"compute\"}"
"get_file_outline|{\"project\":\"$p\",\"file_path\":\"src/main.py\"}"
"get_graph_schema|{\"project\":\"$p\"}"
"compare_graphs|{\"base_project\":\"$p\",\"target_project\":\"$p\"}"
"get_architecture|{\"project\":\"$p\"}"
"search_code|{\"project\":\"$p\",\"pattern\":\"def \"}"
"list_projects|{}"
"index_status|{\"project\":\"$p\"}"
"check_index_coverage|{\"project\":\"$p\",\"paths\":[\"src/main.py\"]}"
"detect_changes|{\"project\":\"$p\"}"
"manage_adr|{\"project\":\"$p\",\"mode\":\"get\"}"
"ingest_traces|{\"project\":\"$p\",\"traces\":[]}"
Expand Down
26 changes: 23 additions & 3 deletions scripts/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ they stage the release fixture, start the fixture server, and sandbox
HOME/TEMP/agent-config destinations. Called bare, the download/checksum/
install-script phases (12-13) SKIP for lack of a fixture server, and the run
mutates the REAL profile — the venue-parity contract forbids that in any venue.
The daemon runtime and cache are private to the run either way: every product
process is started under a CBM_RUNTIME_DIR/CBM_CACHE_DIR this harness owns.

Arguments:
<binary-path> product binary to smoke
Expand All @@ -41,6 +43,20 @@ if [ -n "$SMOKE_MODE" ] && [ "$SMOKE_MODE" != "--agent-config-only" ]; then
fi
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)"

# Every product process below — the phases, the install/update E2E and the
# daemon retirements — must reach a daemon rendezvous this run owns. Only
# CBM_RUNTIME_DIR moves that rendezvous; the wrappers' HOME/TMPDIR/CBM_CACHE_DIR
# sandbox does not, so without this the retirements land on the operator's live
# account daemon (#1691, #1696).
# shellcheck source=test-runtime.sh
source "$REPO_ROOT/scripts/test-runtime.sh"
cbm_test_runtime_init
# Armed here rather than only with the fixture trap below: the fixture mktemp
# and its cygpath conversion sit between the two, and under `set -e` a failure
# there would otherwise leave the private root behind. The fixture trap
# replaces this one and keeps the same cleanup as its first step.
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT

smoke_mktemp_file() {
if [ -n "${SMOKE_TEMP_ROOT:-}" ]; then
mktemp "$SMOKE_TEMP_ROOT/cbm-smoke.XXXXXX"
Expand Down Expand Up @@ -100,8 +116,8 @@ copy_smoke_binary() {
cp "$BINARY" "$destination"
}

# Retire the shared account daemon (if one is running) and wait until it
# reports not-running. Install/uninstall flows leave an ephemeral daemon
# Retire this run's private account daemon (if one is running) and wait until
# it reports not-running. Install/uninstall flows leave an ephemeral daemon
# draining asynchronously whose mapped generation backing and open logs
# block rm on Windows (POSIX rm doesn't care) — so every cleanup of a
# fixture HOME that received an install, and the final cache removal, must
Expand Down Expand Up @@ -149,7 +165,11 @@ CODEX_LIFECYCLE_HOME=""
if command -v cygpath &>/dev/null; then
TMPDIR=$(cygpath -m "$TMPDIR")
fi
trap 'smoke_rmtree "$TMPDIR" "${DRYRUN_HOME:-}" "${CODEX_LIFECYCLE_HOME:-}"' EXIT
# Runtime cleanup first, so no earlier cleanup step stands between the exit
# and the private daemon's retirement; on Windows that retirement is also what
# unblocks the fixture rm (mapped binary, open logs). smoke_rmtree never fails,
# so the fixture removal still runs after it.
trap 'cbm_test_runtime_cleanup "$BINARY"; smoke_rmtree "$TMPDIR" "${DRYRUN_HOME:-}" "${CODEX_LIFECYCLE_HOME:-}"' EXIT

CLI_STDERR=$(smoke_mktemp_file)
# 10 of the cli call sites assign directly (VAR=$(cli ...)). Under
Expand Down
3 changes: 3 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ bash "$ROOT/tests/test_release_gate_chain_contract.sh"
echo "=== Step 0t: test runtime isolation contract (#1691) ==="
bash "$ROOT/tests/test_runtime_isolation_contract.sh"

echo "=== Step 0t2: smoke harness runtime isolation contract (#1696) ==="
bash "$ROOT/tests/test_smoke_runtime_isolation_contract.sh"

echo "=== Step 0u: shell line-ending contract ==="
bash "$ROOT/tests/test_shell_line_endings.sh"

Expand Down
4 changes: 2 additions & 2 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -1513,8 +1513,8 @@ static const char skill_content[] =
"\n"
"## Edge Types\n"
"CALLS, HTTP_CALLS, ASYNC_CALLS, DATA_FLOWS, IMPORTS, DEFINES, DEFINES_METHOD,\n"
"HANDLES, IMPLEMENTS, OVERRIDE, USAGE, CALL_REFERENCE, CONFIGURES, FILE_CHANGES_WITH,\n"
"SIMILAR_TO, SEMANTICALLY_RELATED, CONTAINS_FILE, CONTAINS_FOLDER,\n"
"HANDLES, IMPLEMENTS, OVERRIDE, USAGE, CALL_REFERENCE, CONFIGURES, REFERENCES_FILE,\n"
"FILE_CHANGES_WITH, SIMILAR_TO, SEMANTICALLY_RELATED, CONTAINS_FILE, CONTAINS_FOLDER,\n"
"CONTAINS_PACKAGE\n"
"\n"
"## Cypher Examples (for query_graph)\n"
Expand Down
52 changes: 35 additions & 17 deletions src/foundation/compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,36 +269,54 @@ int cbm_mkstemp(char *tmpl) {
errno = ENAMETOOLONG;
return CBM_NOT_FOUND;
}
/* Wide-API expansion and open: worker staging files land inside
* CBM_CACHE_DIR, which users may place at non-ASCII paths; the ANSI CRT
* (_mktemp/_open) mangles those bytes in the local codepage. */
/* Keep the six-character mkstemp contract, but do not use _wmktemp:
* that CRT helper has a tiny name space on Windows and retained worker
* logs can exhaust it during recovery. The exclusive open closes races
* with other processes; collisions simply draw another random name. */
wchar_t *wide_template = cbm_utf8_to_wide(buf);
if (!wide_template || !_wmktemp(wide_template)) {
free(wide_template);
if (!wide_template) {
errno = EINVAL;
return CBM_NOT_FOUND;
}
char *expanded_for_open = cbm_wide_to_utf8(wide_template);
wchar_t *wide_open = expanded_for_open ? cbm_path_to_wide(expanded_for_open) : NULL;
free(expanded_for_open);
if (!wide_open) {
size_t wide_len = wcslen(wide_template);
if (wide_len < 6 || wcscmp(wide_template + wide_len - 6, L"XXXXXX") != 0) {
free(wide_template);
errno = EINVAL;
return CBM_NOT_FOUND;
}
int fd = _wopen(wide_open, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
free(wide_open);
if (fd >= 0) {
static const wchar_t hex[] = L"0123456789abcdef";
for (int attempt = 0; attempt < 128; attempt++) {
unsigned int random_bits = 0;
if (!cbm_secure_random(&random_bits, sizeof(random_bits))) {
errno = EIO;
break;
}
for (int digit = 0; digit < 6; digit++) {
wide_template[wide_len - 6 + digit] = hex[(random_bits >> (digit * 4)) & 0xf];
}
char *expanded = cbm_wide_to_utf8(wide_template);
if (!expanded || strlen(expanded) >= sizeof(buf)) {
wchar_t *wide_open = expanded ? cbm_path_to_wide(expanded) : NULL;
if (!expanded || !wide_open || strlen(expanded) >= sizeof(buf)) {
free(expanded);
free(wide_open);
errno = ENAMETOOLONG;
break;
}
int fd = _wopen(wide_open, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
free(wide_open);
if (fd >= 0) {
strcpy(tmpl, expanded);
free(expanded);
free(wide_template);
(void)_close(fd);
return CBM_NOT_FOUND;
return fd;
}
strcpy(tmpl, expanded);
free(expanded);
if (errno != EEXIST) {
break;
}
}
free(wide_template);
return fd;
return CBM_NOT_FOUND;
}
#endif

Expand Down
6 changes: 6 additions & 0 deletions src/mcp/index_supervisor.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "ui/http_server.h" /* cbm_http_server_resolve_binary_path */

#include <limits.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
Expand Down Expand Up @@ -705,6 +706,11 @@ int cbm_index_worker_start_with_log(const char *args_json, size_t memory_budget_
worker_result_init(&handle->result);
if (!worker_unique_file(handle->response_path, sizeof(handle->response_path), "response") ||
!worker_unique_file(handle->log_path, sizeof(handle->log_path), "log")) {
int saved_errno = errno;
char error_text[CBM_SZ_32];
(void)snprintf(error_text, sizeof(error_text), "%d", saved_errno);
cbm_log_error("index.supervisor.artifact_create_failed", "artifact",
handle->response_path[0] ? "log" : "response", "errno", error_text);
(void)cbm_unlink(handle->response_path);
(void)cbm_unlink(handle->log_path);
free(handle);
Expand Down
5 changes: 4 additions & 1 deletion src/mcp/mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -10808,7 +10808,10 @@ static char *index_run_supervised(cbm_mcp_server_t *srv, const char *args) {
cbm_mcp_supervised_result_disposition_t recovery_disposition =
cbm_mcp_supervised_result_disposition(rc2, &wr2);
if (recovery_disposition == CBM_MCP_SUPERVISED_RESULT_FALLBACK) {
last_outcome = wr2.outcome;
/* Keep the original worker failure if recovery setup fails. */
cbm_log_error("index.supervisor.recovery_start_failed", "original_outcome",
cbm_proc_outcome_str(last_outcome), "recovery_outcome",
cbm_proc_outcome_str(wr2.outcome));
cbm_index_worker_result_free(&wr2);
break; /* spawn failed mid-recovery — give up */
}
Expand Down
Loading
Loading