Upgrading from v0.10.8 to v0.11.0 on a machine with OpenCode reported all
three OpenCode subagent profiles as "preserved modified profile" plus an
op=agent_install error each, and the activation stopped ("one or more agent
configurations failed; the published/current executable was kept"), although
nobody had edited those files.
Root cause: #1933 (v0.11.0) added the tool_search / tool_search_regex
permissions to the rendered OpenCode profile. Install and uninstall recognise
a profile as ours only when it matches the current rendering or one of the
known released renderings (the other access mode, the v0.9.1-rc.1 Codex
shape, the pre-tier Verify file). The v0.10.8 OpenCode shape was not in that
set, so cbm_text_migrate_owned_document classified the untouched v0.10.8
files as user-modified. Reproduced end to end: install with the v0.10.8
release binary, then install with v0.11.0/main, into an isolated HOME; the
three profiles are 1729/1880/1910 bytes, the same sizes as in the report.
cbm_render_graph_profile_opencode_v0108() renders the pre-#1933 shape
(both access modes), and install/uninstall now list it as a released
rendering, so those files are upgraded in place and uninstall removes them
as owned. A profile the user actually edited is still preserved. The
released-list assembly that install and uninstall duplicated moves into one
helper, and the two legacy renderers share one implementation (cli.c drops
three raw free sites; the memory-core baseline is tightened 173 -> 170).
The Codex and OpenCode op=mcp_install errors in the same report do not
reproduce from a pristine v0.10.8 config and depend on the reporter's own
config.toml / opencode.json; they are not addressed here.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
watcher_sustained_failure_logs_once_issue2015 failed on macos-14 CI with
`failing_index_calls == 13, expected 14`. Its loop ran exactly 14
touch+poll rounds and assumed each one reached the index callback. A poll
whose git probe fails transiently (spawn refused under runner load) is a
fail-closed no-op by design (#937): the baseline stays uncommitted and the
next poll retries. So one refused spawn costs one attempt, and the count
ends at 13. This is the same test race as watcher_unwatch_drains_pending_free,
not a production defect. Holding one exhausted spawn mid-loop with
cbm_subprocess_force_spawn_eagain_for_testing reproduces the CI line
exactly.
The loop now polls until the 14th failed attempt instead of counting
polls. Each poll runs at most one attempt for the single project, so it
stops at exactly 14. A watcher that never reindexes hangs, and the runner
reports that as a failure, never a pass. The log-once contract means the
same thing as before: watcher.index.sustained_failure is emitted only on a
failed index attempt when the streak equals the threshold, so extra no-op
polls cannot add lines. An `==` to `>=` mutation in watcher.c still turns
it red (`sustained_log_hits == 5, expected 1`).
The change is now a dirty worktree instead of a commit. If the baseline
poll itself failed, the old commit would be adopted as the baseline HEAD
and never reindexed, so the wait would never end. The baseline keeps the
dirty signature at "clean known", so a dirty change is seen whenever the
baseline lands. With spawns forced to fail at the baseline poll and again
mid-loop, the test passes after 16 polls for 14 attempts.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
walk_defs keeps its pending frames in a growable stack that was bounded by
an 8M-frame ceiling (env CBM_WALK_DEFS_MAX). At the ceiling wd_push logged
"extract.walk_defs_capped" once and then stopped pushing. Children are
pushed last-to-first, so a file wider than the ceiling lost its FIRST
definitions: a work cap decided graph content.
Fix: no ceiling. The stack doubles on demand, heap frames now come from the
memory core (cbm_realloc/cbm_free, class EXTRACT) instead of safe_realloc and
raw free, and the doubling is overflow-guarded (int capacity and size_t
byte count). The only thing that can stop the walk is an allocation failure,
and that is loud: logged at error level as "extract.walk_stack_alloc_failed"
(walker=walk_defs, pending, path) -- the same event the complexity walkers
use -- and the file's result is marked has_error with
"definitions walk: stack allocation failed", which the definitions passes
already report as a per-file extract error. The arena-backed path
(ctx->scratch) gets the same failure handling. Traversal order is unchanged.
CBM_WALK_DEFS_MAX is removed. It was read only in wd_stack_max(); it was not
documented anywhere (docs, --help, config, tests), so nothing else changes.
extract_defs.c loses its raw free() call, so the memory-core baseline for
the file drops from 5 to 4.
Test (extraction suite): walk_defs_wide_file_extracts_every_definition
extracts a C file with 1000 top-level functions with CBM_WALK_DEFS_MAX=256
set, and asserts every wd0..wd999 definition is present. RED on main
(extract.walk_defs_capped limit=256; wd0 missing), GREEN with the fix, RED
again with the production change reverted. The extraction (379), complexity
(5), ast_profile (10) and semantic (35) suites pass.
Real input: Linux kernel, index-only, fresh cache and runtime dirs, same
machine. main and fix both produce 8,530,336 nodes and 15,764,128 edges,
SEMANTICALLY_RELATED 607 each, and the full node set (label, QN, lines) and
edge set (type, source QN, target QN) hash identically -- no kernel file
approaches the old ceiling. Index wall time 210.0 s (main) vs 194.4 s (fix),
within run-to-run noise.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
`codebase-memory-mcp cli <tool> --flag value` builds the tool's JSON
arguments from typed flags. Array-typed parameters such as
`semantic_query` used to accept only repeated flags; the JSON spelling
shown in the MCP schema (`--semantic-query '["a","b"]'`) was stored as a
single opaque string element (#2150). `cli_add_typed` now expands such a
literal; this pins the edge cases of that contract so it cannot regress:
- a well-formed array of strings contributes its elements
- an explicit empty array `[]` contributes nothing (not the keyword "[]")
- a malformed literal stays one literal string, without an error
- non-string items are copied as typed values, not stringified
- literals and repeated flags mix, accumulating in argument order
- repeated literals accumulate
Signed-off-by: Hubert Tarnacki <hubert.tarnacki@gmail.com>
watcher_unwatch_drains_pending_free failed three times on macos-14 CI
with `index_call_count == 0, expected 1` at its dirty-poll precondition.
That precondition needs every git subprocess of one poll (rev-parse
HEAD, status, submodule foreach) to succeed. A transient probe failure
(spawn refused under runner load, a non-zero exit) makes that poll a
fail-closed no-op by design: #937 leaves the baseline uncommitted and
the next poll retries. Several of those paths return without a log
line, which matches the CI output exactly: watch, baseline, FAIL, with
no watcher.changed and no watcher.git.failed. Holding a spawn failure
with cbm_subprocess_force_spawn_eagain_for_testing reproduces that
output deterministically. This is a test race, not a production defect.
The precondition was also unrelated to the contract under test. A poll
that stopped draining would neither crash nor leak, because
cbm_watcher_free frees the same deferred list. So the old test could not
see a broken drain at all.
Add a test-only seam, cbm_watcher_test_pending_free_count, and assert
the stable states the watcher holds: 0 parked before unwatch, 1 after,
0 after the next poll with no index work, and still 0 after a further
poll. No assertion depends on a git probe succeeding. Dirty-poll
reindexing stays covered by watcher_detects_dirty_worktree and
watcher_modify_tracked_file.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolve the RUN_TEST list conflict in tests/test_daemon_application.c by keeping both sides' new tests.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sync pkg/scoop/codebase-memory-mcp.json with the Scoop Main bucket
manifest (v0.11.0): add arm64, notes, and matching hashes/URLs.
Signed-off-by: Ivan Lin <35771002+ivanlinhf@users.noreply.github.com>
Accepting relative JAX-RS templates made route_path_from_string_node return
"" for @Path(""), which is non-NULL and so replaced the "/" fallback with an
empty method path. @Path("") is legal JAX-RS and means "the class path
itself"; on main the empty value was rejected and the method fell back to
"/" like a method without @Path. Return NULL for the empty case so that
behaviour is unchanged.
The regression covers both shapes: with a class-level @Path the method
composes to the class path, and without one the route stays "/" (with the
empty string it was dropped entirely, because join_route_paths returns the
NULL prefix for an empty path).
Signed-off-by: Hubert Tarnacki <hubert.tarnacki@gmail.com>
The full Edge Types list near the graph schema section still missed
the new REFERENCES_FILE type added by the doclinks pass. The
"selected" list earlier in the file is explicitly a subset and is
left as is.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Clément Deust <clement.deust@gmail.com>
Replace raw malloc/free with cbm_alloc/cbm_free (CBM_MEM_CLASS_EXTRACT)
and a growable CBM_DYN_ARRAY for matches. Remove the 4KiB line-copy
cap; lines are split in place instead of copied. Remove the
DOCLINK_MAX_REFS(256) cap; the ref vector now grows, is sorted by
target id, then collapsed. Track reference kind (bare, relative,
rooted) so /x never attempts a directory join and ./x never falls
back to root. Report per-file skips via cbm_pipeline_add_file_error
and cbm_log_warn, and check cbm_pipeline_check_cancel in the per-file
loop.
Add negative-control tests for all of the above, plus CRLF handling,
fenced-code-block pinning, bare URLs, backtick-basename exactness,
and ".." escaping.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Clément Deust <clement.deust@gmail.com>
Markdown docs reference other repo files constantly (a coding-standards
doc links the modules it governs, a README points at entry points) but
none of that surfaced as edges, so fan-in queries were blind to
documentation hubs. In an A/B retrieval benchmark I ran in August on a
docs-heavy repo, the graph's top fan-in answer was off by roughly 17x:
the most-referenced file was a standards doc with 173 references and
zero inbound edges.
New pre-dump pass, pass_doclinks.c, modeled on pass_configlink.c: three
strategies emit REFERENCES_FILE edges between existing File nodes only
(unresolvable targets are dropped, the pass never invents nodes):
inline links, backtick paths, bare path mentions. Targets resolve
against the referencing file's directory and the repo root; repeated
references collapse into one edge carrying strategy, confidence and
count. Registered in the pre-dump sequence after configlink and in the
incremental post-passes; REFERENCES_FILE added to the skill's edge-type
list and the structural/language contract tests.
Tests mirror test_configlink.c (real files in a tmpdir, File nodes in a
gbuf, run the pass, assert edges): inline link, backtick, bare mention,
http/anchor ignored, anchor-suffixed file link, dedupe with count,
relative-vs-root resolution, unresolvable-target guard, NULL repo_path
skip.
A companion change adds the same linking for shell files (source lines
and script invocations); split out to keep each change reviewable.
Validation: scripts/build.sh clean; focused serial runner (doclinks,
configlink, pipeline, edge_structural, lang_contract) 340 passed under
ASan/UBSan; cppcheck clean on the new files with the repo's flags.
Signed-off-by: Clément Deust <clement.deust@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CLI_USAGE described outer --json but did not mention tool-level
--format json. Add a help-only pointer that tools which accept format
support --format tree|json, and distinguish payload JSON from the full
MCP envelope. No new flags, env vars, or output-default changes.
Fixes#2102
Signed-off-by: Lowpower <3133734+Lowpower@users.noreply.github.com>