Repository navigation
[pull] main from DeusData:main - #150
Merged
Merged
Conversation
cbm_store_count_nodes and cbm_store_count_edges treat every sqlite3_step result other than SQLITE_ROW as a count of zero. A read that failed — SQLITE_CORRUPT, SQLITE_BUSY, SQLITE_IOERR — is therefore indistinguishable from a project that genuinely holds no rows, and index_status renders it as the positive assertion status "empty". A user or agent reading that concludes the repository was never indexed and starts a multi-minute re-index, while the corruption itself is never surfaced. Both functions already have an error channel: each returns CBM_STORE_ERR when prepare_cached fails. Only the step result was not reported through it. index_status is already written for that value — it sets degraded on a negative node count and clamps a negative edge count — so the guard existed and simply never fired. Initialise count to CBM_STORE_ERR so a non-row step is reported as a failed read. A successful step still overwrites it with the real count, so the healthy path is unchanged. Closes #2012 Signed-off-by: kavish-19 <63698788+kavish-19@users.noreply.github.com>
The store change alone does not close #2012. handle_index_status reads the counts directly and renders status as `nodes > 0 ? "ready" : "empty"`, so a negative count from a failed read still answered "empty" — now with a bare -1 printed as the count, and with no hint at all, because the "Project is empty" branch is guarded on `nodes == 0`. The guard cited earlier lives in build_index_success_response, which is index_repository's response builder, not this path. Treat a negative node or edge count as a failed read: report status "error", suppress the negative numbers rather than emitting them as counts, and replace the re-index hint with one that names the table that could not be read. Closes #2012 Signed-off-by: kavish-19 <63698788+kavish-19@users.noreply.github.com>
cppcheck 2.20 flags the nested ternary in handle_index_status:
src/mcp/mcp.c:6705:30: style: Condition 'nodes<0' is always false
[knownConditionTrueFalse]
After the `nodes < 0 && edges < 0` arm, cppcheck's value flow takes
`nodes < 0` to be the false half of that conjunction and rejects the
second test. Select the hint with a plain if / else-if / else chain into
a `const char *hint` and add it once, rather than suppressing the check.
The three messages are byte-identical to before — only the line wrapping
of the adjacent literals moved — so status, counts and hint text are
unchanged and the tests added in the previous commit still describe the
behaviour.
Verified with cppcheck 2.21 locally using the flags from
Makefile.cbm's lint-cppcheck target: the finding is reproduced on the
parent commit and gone here, with no new ones. clang-format reports no
drift over the changed lines.
Signed-off-by: kavish-19 <63698788+kavish-19@users.noreply.github.com>
Create missing parent directories recursively and require fixture nodes before negative graph assertions. Cover exact bytes, deep paths and blocked parents. Fixes #2034 Signed-off-by: astandrik <astandrik@yandex-team.ru>
Signed-off-by: astandrik <astandrik@yandex-team.ru>
OpenCode's V2 loader validates only the module's default export,
expecting {id, setup()|effect()}. cbm_client_adapter_opencode still
emitted the V1 named export (export const CodebaseMemory = ...), so
every server start failed with SchemaError: Missing key at ["default"].
Wrap the existing hook logic in a default export with an id and a
setup(ctx) function; the returned hooks object is unchanged.
Fixes #2077
Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
…bfolder resolve_git_common_dir() only stats repo_path/.git directly, so indexing a subfolder with no .git of its own never consulted any ancestor repo's .gitignore. Add resolve_enclosing_git_root() to walk up parent directories the way git itself does, and merge that ancestor's .gitignore (and info/exclude) ahead of the subfolder's own so the more specific file still wins on conflict. Fixes #510 Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
DeusData asked (2026-09-19T20:25:34Z on PR #2089) to place the hooks in the server entry rather than returning them from setup(), since the server runtime reads default.server and dispatches the hooks it returns, while the V2 config loader only reads id + setup()/effect() and has no tool domain yet to register against. Moved the existing hook body under server: async (ctx) => {...} and added an empty setup() {}. Updated the emission test to check for both and to assert the old async setup(ctx) { shape is gone. Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
…to their own directory
The previous commit found the enclosing repository when a git-less subfolder
is indexed, but merged its patterns into the matcher whose rel_path is
relative to repo_path. gitignore.c matches a rooted pattern with
`glob_match_bounded(p->pattern, rel_path)`, so every anchored pattern of the
enclosing repository was silently re-anchored onto the indexed subfolder.
That is wrong in both directions, and both were checked against real
`git check-ignore` run inside the subfolder:
* enclosing root .gitignore `/secret.py`, indexing pkg/
git INDEXES pkg/secret.py — we HID it. A rooted pattern means "in the
repository root", and re-anchored it became "in pkg". Silent index loss:
the file is gone from the graph with no diagnostic, the worst failure
mode discovery has.
* enclosing .git/info/exclude `pkg/scratch/`, indexing pkg/
git IGNORES pkg/scratch/tmp.py — we INDEXED it. The pattern is anchored
THROUGH pkg, so once rel_path lost the "pkg/" prefix it stopped matching
and an excluded directory got walked.
Only the enclosing root's .gitignore was consulted, too; git reads the
.gitignore of every directory between the repository root and the indexed
one, deeper overriding shallower.
Mechanism. discover.c already had the chain that encodes "the deepest file
with an opinion wins": gitignore_link_t carries a `prefix`, the walk-relative
directory a nested matcher came from, which local_rel_path() strips off. An
ancestor is the mirror image of that, so the chain is extended rather than
duplicated: each link now also carries a `base`, the walk root's path
relative to the directory the matcher came from, which gitignore_chain_result
PREPENDS instead of stripping (discover.c:569, 610). Exactly one of the two
offsets is ever non-empty, and both live in the link's flexible tail, so no
allocation site is added.
* ancestor_ignores_build() (discover.c:1282) loads the .gitignore of every
directory from the enclosing repository root down to — but not including —
the indexed one, shallow to deep, and chains them so the deepest wins,
negations included. The enclosing repository's info/exclude is merged into
the ROOT ancestor's matcher, which is where git anchors it, keeping the
precedence info/exclude already has over .gitignore at the same level.
* the indexed directory's own .gitignore stays its own matcher at the leaf
of that chain (discover.c:1480), so it still overrides every ancestor.
Nothing is merged into it any more, which also removes the earlier merge's
failure mode of dropping the local patterns and keeping the ancestor's.
* repo_path is canonicalized with cbm_canonical_path() before the lexical
parent walk (discover.c:1414), so `cbm index pkg` and
`cbm index /abs/path/pkg` discover the same files; pipeline.c strdups the
caller's path unchanged.
* walk_owned_gitignore_free() (discover.c:1019) is factored out of walk_dir
and shared with the ancestor chain, which owns its matchers the same way.
Proof on real input, before the suites. Indexing scripts/ of this checkout —
a git-less subfolder whose root .gitignore carries the rooted patterns
`/memlab-*` and `/soak*/`:
before 87 files memlab-drive.py MISSING memlab-report.py MISSING
after 89 files memlab-drive.py present memlab-report.py present
`git check-ignore` inside scripts/ indexes both, so the two recovered files
are exactly the ones git keeps. No file is lost in the other direction.
RED on the previous commit, four new tests, each direction pinned to a
`git check-ignore` verdict on an identical fixture:
test_discover.c:1471: count == 1, expected 2 == 2 rooted_pattern_not_reanchored
test_discover.c:1498: count == 2, expected 1 == 1 info_exclude_rooted_subpath
test_discover.c:1526: count == 2, expected 1 == 1 intermediate_gitignore
test_discover.c:1555: count == 0, expected 1 == 1 deeper_ancestor_negation_wins
discover: 120 passed, 4 failed
The three tests already on the branch use unanchored patterns, which match at
any depth and so cannot see this bug; they pass before and after.
GREEN: discover 124 passed, 0 failed; discover + gitignore + pipeline +
git_context 425 passed, 0 failed.
Revert-check: reverting only the discover.c hunks and rebuilding puts the
four new tests RED again on the same four lines with the same counts
(120 passed, 4 failed); restoring returns 425 passed. Memory-core linter:
none grew (31 raw sites in discover.c before and after). clang-format clean.
Co-authored-by: Amir Fathi <amirfathi.me@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…check (#1735) cbm_subtract_macro_invocation_regions asked "is this region a call to a file-defined macro?" before "is it inside a function?". The first question (cbm_span_is_macro_invocation) walks the source from byte 0 to find the region's lines, so every file with parse-error regions paid regions x file bytes, whatever its language and whether or not it has a single function. On a multi-MB SQL dump that is up to 256 walks over the whole file. Both predicates are pure, so asking the cheap one first changes no answer: a region no function encloses never touches the source, and the regions that do share one cbm_line_offsets table built on first use. The table gives exactly the span the walk found; the walk stays as the fallback when the table cannot be allocated. #1071 behaviour is unchanged: same regions, same verdicts. Indexing this repository's src/ before and after gives identical graphs (7,339 nodes, 28,924 edges) and the same 46 parse_partial files with byte-identical ranges. Tests count the bytes read, never time them (test-seam counter cbm_test_macro_line_scan_bytes): - 40 top-level junk regions: 39,870 bytes read -> 0, still 40 regions - 60 in-body real errors: 85,725 bytes read -> 2,801 (one table = file size), still 60 regions Refs #1735 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A mysqldump-style .sql file is a few CREATE TABLEs followed by megabytes of `INSERT ... VALUES (..),(..),...` rows. Tree-sitter built a full tree for every row (~70 bytes of tree per source byte; 0.26 s/MB with MySQL \' escapes, which the grammar does not know and error-recovers from) until the 5 s parse budget ran out, and the whole file, tables included, was skipped as "parse timeout". The reporter's database/sql/world.sql was dropped this way. The rows contribute nothing to the graph. A linear, quote- and comment-aware pre-scan (internal/cbm/sql_values.c) finds INSERT/REPLACE ... VALUES lists and excludes every tuple after the first that consists only of literals: strings (standard '' and MySQL \' escapes, X'..'/B'..'/N'.., _charset introducers), numbers, NULL, TRUE, FALSE, DEFAULT. A tuple holding a subquery, a function call, an identifier or a quoted identifier stays in the parse, and so does one whose string spans a raw newline. The excluded bytes go to ts_parser_set_included_ranges as the complement, so byte offsets and line/column positions of all kept text are unchanged. The ranges are cleared right after the parse because the parser is thread-local and reused. This is a syntax rule, not a work cap: the same file always gives the same ranges, and the parse budget is unchanged. Measured on generated mysqldump-shaped dumps, release build, same machine, index worker's own numbers (base = origin/main): dump extraction pipeline peak RSS graph 8 MB std '' 1310 -> 23 ms 1431 -> 139 ms 625 -> 37 MB identical 8 MB MySQL \' 2712 -> 24 ms 2827 -> 146 ms 474 -> 37 MB identical 32 MB MySQL \' 5145 -> 82 ms 5467 -> 396 ms 1132 -> 62 MB timeout -> indexed 59 MB MySQL \' 5130 -> 138 ms 5667 -> 674 ms 1157 -> 89 MB timeout -> indexed "identical" = every stored node and edge, Module end line included. For the 32/59 MB dumps the base run skipped the file as "parse timeout" (Module + 3 Tables + their DEFINES/USAGE missing). The fixed graph matches the 8 MB graph except for the Module's end line. With standard escapes the extraction and the stored graph are identical to the full parse. With MySQL \' escapes the full parse's error recovery swallows rows next to a misread escape, a subquery row among them, and the cut parse keeps those rows. So it loses nothing the full parse found outside the dropped rows and recovers what the full parse lost. The only things the full parse extracted INSIDE the dropped rows were junk usages (DEFAULT and _binary read as identifiers, fragments of misread strings) that name nothing. Tests (parse_coverage, pipeline; a CBM_TEST_SQL_FULL_PARSE_ON seam turns the exclusion off for one file so the same source can be compared both ways): - scanner cases: comments, strings, dollar quotes, ON DUPLICATE KEY UPDATE, VALUES outside INSERT, unclosed tuples, exact kept positions - extraction equals the full parse on a small dump (standard escapes); nothing lost outside dropped rows (MySQL escapes) - the subquery and function-call rows are still parsed - the tree does not grow when the row count doubles (a count, not a clock) - a 25 MB MySQL dump under the production budget is indexed, not "parse timeout" (asserts the outcome, not the time) - stored nodes and edges of a pipeline index equal the full parse's Refs #1735 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A 4.7 MB binary Godot resource (piko_walk_mesh.res) never finished indexing: `.res` maps to ReScript, so the resource was parsed as ReScript source and the definitions pass spun. Three defects, each fixed: 1. Quadratic usage walk (the hang). The ReScript `let` binding check (is_first_named_part_of, shared with Julia, Typst, Elm, PureScript and Nickel) climbed from every named leaf with ts_node_parent, which descends from the root and scans each level's children. A binary file parses into a flat error tree (~170k root children per MB), so each climb cost O(root children) and the file O(n^2): 1 MB spent 46 s in cbm_extract_unified, 4.7 MB never returned. The check now climbs the unified walk's own cursor (O(1) per hop), like the other occurrence classifiers; the node-based climb stays only for callers outside the walk. 1 MB: 46 s -> 0.03 s; 4.7 MB: 2.2 s end to end. 2. Quadratic lexing in error recovery. tree-sitter retries the lexer at every byte of an unparseable stretch with every external token valid; the ReScript scanner's template-string loop then ran to the next backtick/$/backslash/NUL (end of file if none) and discarded the result. That cost O(n^2) inside lexing, where the parse budget's progress callback never runs, so such files were dropped by the 5 s CPU budget instead of parsed -- plain text too (100 KB of non-ReScript text: 15 s -> 0.18 s). Local scanner patch: skip the template branch when NEWLINE is also valid, which only the ERROR state allows. Valid files parse to identical trees. Recorded in vendored/grammars/MANIFEST.md. 3. Binary content parsed as source. `.res` is also Godot's binary resource format and the Windows compiled-resource format. Following the .m/.cls/.frm/.inc/.cfc precedent, cbm_disambiguate_res reads the first 4 KB and reports a file holding a NUL byte as unsupported; ReScript text never contains one. Tests (deterministic work counters, no clocks): - extraction::extract_rescript_let_bindings_use_the_walk_cursor -- zero root-descending parent hops at 128 and 1024 statements (before: 515 / 4099). - complexity::complexity_rescript_error_recovery_lexing_is_linear -- lexer bytes pulled through a chunked TSInput, 4 KB vs 8 KB, ratio must be linear (before 3.99, after 2.00). - language::lang_res_binary_resource_unsupported and lang_res_rescript_stays_rescript. Fixes #2176 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cppcheck 2.20 (the version the lint job pins) reports
knownConditionTrueFalse on two comparators in src/mcp/mcp.c whose
tie-breakers compare one strcmp result after another, each call with
inline "x ? x : \"\"" fallbacks. Neither function differs between these trees; which of
them is flagged depends on the rest of the lint source list. With
scripts/lint.sh --ci and cppcheck 2.20.0 built from source:
- main itself fails on search_result_cmp ("file_order != 0 is always
false");
- adding one more language grammar and its sources moves the finding
to project_record_compare ("by_root != 0 is always false");
- carrying each comparator's order in one variable instead still fails,
now as redundantAssignment on project_record_compare.
Both comparators now call nullable_strcmp(), which orders NULL like ""
exactly as the inline fallbacks did. lint-cppcheck passes again on
main and with the additional sources. Comparison order and results are
unchanged for every input.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sarlette <282189207+SarletteD@users.noreply.github.com>
cbm_registry_add takes the symbol's `name`, discards it, and derives the
bare-name lookup key from the QN's last dot segment. That makes the QN's tail
load-bearing for the by-name index every language shares, and for one grammar
the tail is not a name anybody writes. rust_cfg_qualified_name mints a
`#[cfg(test)]` twin as "proj.lib.add#cfg(test)"; simple_name() has no '#'
handling, so that function is filed under the whole string "add#cfg(test)",
which no bare `add` callee can reach, leaving a test-only twin of a resolved
function absent from the by-name index entirely.
The caller always knows the name, because it is the argument. So the index
takes a second key from it, only where the derived key carries a fence:
const char *derived = simple_name(qualified_name);
index_under_name(r, derived, owned_qn);
if (name && name[0] && strchr(derived, '#') && strcmp(name, derived) != 0) {
index_under_name(r, name, owned_qn);
}
The gate keys on the fence rather than on the file's language, and that is the
part worth reviewing. It makes "unchanged for every other grammar" checkable:
the derived key is written unconditionally exactly as before, and every by-name
lookup keys through simple_name(), which splits on '.' and "::" only. A passed
name that differs from the derived key by anything other than a fence is
therefore a key no lookup can reach, so adding it would only widen the bucket
the scorer walks. A grammar that mints no '#' sees no new key at all.
Two of the rows a language gate would have had to cover turn out not to reach
this function. Registry membership is decided by cbm_label_is_registry_symbol
at all three production callers (pass_definitions.c, pass_parallel.c,
pipeline_incremental.c), and it admits neither "Module" nor "Section". So a
file's Module node never registers, and neither does a Markdown heading. What
does register, under label "Class", is the dotted-name direction:
grammar def->name QN tail
HCL resource.aws_instance.web web
TOML tool.poetry.dependencies dependencies
INI tool.isort isort
These are why the derived key stays unconditional. `instance =
aws_instance.web.id` reaches the block through the tail `web`, so re-keying the
index on the passed name would drop that edge outright. The fence gate leaves
all three byte-identical.
`name` is NULL or empty only for callers that have no symbol name to give;
those have the derived key and nothing else.
tests/test_registry.c: registry_indexes_by_passed_name_not_qn_tail resolves a
bare `add` against a cfg-fenced QN and is the reproducer, failing without this
commit. registry_indexes_a_dotted_name_under_its_tail_too pins the unconditional
derived key for the HCL and Module shapes.
tests/test_pipeline.c: pipeline_hcl_block_reference_resolves_to_its_block indexes
a two-resource Terraform file end to end and asserts the surviving USAGE edge.
The latter two pass without this commit and are guards: what would break them is
re-keying the by-name index, and an HCL block name is dotted where its QN tail is
not, so they pin the shape that regression would take.
A throwaway probe walked tests/grammar_cases.h (one fixture per grammar, all 162)
comparing def->name with simple_name(def->qualified_name), then targeted fixtures
for the dotted shapes above. HCL is the only fixture-level mismatch among
registry-eligible labels; TOML and INI mismatch on targeted fixtures. XML
namespaced elements (`ns:root`) do not mismatch, because ':' is not a QN
separator.
Signed-off-by: Chad <4307099+BobbieBarker@users.noreply.github.com>
Distilled from #1789 by Ulises Millan Guerrero, unchanged except for the five openspec/ scaffolding files that could not land. The indexing pipeline already logged its own phase error when artifact export failed after publication, but the MCP failure path returned the generic "Pipeline failed. Check repo_path" regardless. A caller whose export failed was told to check a repository path that was fine, while the one log line naming the real phase sat somewhere they were not looking. The fix carries the failing phase out of the pipeline and into the MCP error, so the message names artifact export when that is what failed. The existing fail-hard contract is preserved -- this changes what the caller is told, not whether the run fails. Why this is a distill rather than the original: five of the nine files in on main, no other contributor uses it, and adopting a spec framework is a project-level decision rather than something that rides in on a bug fix. That was explained on 2026-09-02 and the code half was approved on merit the same day; the scaffolding is the only reason it did not land then. Co-authored-by: Ulises Millan Guerrero <ulises.millanguerrero@gmail.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three iterative AST walkers kept their pending nodes in fixed-size stacks
and silently stopped pushing children once full:
- cbm_compute_complexity and count_branching_iter (helpers.c,
BRANCHING_STACK_CAP 4096) -> cyclomatic, cognitive, loop and access
metrics
- extract_body_ident_tokens (extract_defs.c, BT_STACK 512) -> the
body-token sample that feeds semantic search / SEMANTICALLY_RELATED
Children are pushed last-to-first, so a full stack dropped a wide body's
FIRST children. The metrics undercounted, and the token sampler read a
mid-body window instead of the first identifiers. That let a work cap decide
graph content.
Real input: tree-sitter-llvm's generated ts_lex (a 4654-case switch)
reported cyclomatic 11133 / cognitive 29303. It really has 8776 if + 4654
case + 1 default + 1 switch = 13432 branches, which is what it reports now
(cognitive 35639). On the Linux kernel no function reaches the old 4096 cap,
so complexity is byte-identical for every function. Three functions with
bodies over 512 pending nodes now sample their tokens from the start of the
body (for example rimob_panel_on), which moves 11 SEMANTICALLY_RELATED edges.
Index time is unchanged within noise (main 214.4/207.9 s, fix 197.6/192.2 s).
Fix: cbm_walk_stack_reserve()/cbm_walk_stack_release() in helpers.c. Each
walker keeps a small on-stack first chunk (512 frames) and spills to the heap
through the memory core (cbm_alloc/cbm_realloc/cbm_free, class EXTRACT) only
for deep or wide trees. Traversal order is unchanged. If the stack cannot
grow, the failure is logged ("extract.walk_stack_alloc_failed") and the
walker returns a documented sentinel instead of a truncated result:
CBM_WALK_METRIC_UNAVAILABLE (-1) for the metrics, or no token sample.
The body-token sampler's caps of 128 identifiers and a 2048-byte buffer are
its sampling contract and stay. The 512-entry stack was never part of that
contract, because it broke the "first identifiers in source order" property.
Tests (extraction suite): a C function with 5000 top-level if/while
statements must report exact cyclomatic/cognitive/loop counts through both
the extractor and cbm_count_branching, and a 600-call body must sample from
f0 on. All three are RED on main (4095 / 4095 / sample starting at f91) and
GREEN with the fix.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…onflict (#2277) A client refused because another CBM build owns the account daemon printed only "Close all CBM sessions and commands, then retry." The reporter had a 0.10.3 daemon kept alive by a running Codex session while starting 0.11.0 from a terminal, and nothing in the message said which process that was, that an agent session held it, or that `daemon stop` exists. The refusal itself is correct and stays unchanged: session-managed daemons serving live sessions must never be killed by a newer client, and cbm_daemon_conflict_format() is validated byte-for-byte across generations on the HELLO wire, so its text cannot change without breaking old/new daemon pairs. The fix adds a separate, client-side remedy sentence built from the cross-build STATUS probe (cbm_daemon_conflict_remedy_format): active daemon pid, version, permanent vs session-managed, "already stopping", the pids of the CBM sessions holding it, the likely cause (another editor or agent launched a different install; compare `which -a codebase-memory-mcp` with each client's MCP config) and `daemon stop` once no session uses it. When STATUS does not answer, the remedy points at `daemon status`. It is appended on every user-facing conflict exit: the MCP/CLI bootstrap path (after the older-permanent auto-drain policy declines), the one-shot CLI exact-build cohort refusal and the MCP client cohort refusal (the path the reporter hit). Hooks skip the extra probe to keep their fail-open budget. The probe uses the `daemon status` budget (3 s): the daemon hashes the ~300 MB peer image before answering, which overran the 1 s connect budget in the real-binary proof. No identity or admission check changes; STATUS is already the authenticated, cross-build observability op behind `daemon status`. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
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>
Add cbm_scope_lookup_binding so callers can read lexical presence, type, and callable identity in one traversal. Route Python expression, call, and callable-reference resolution through that complete binding and use the existing checked bind APIs for allocation-failure detection. The neutral 11-pair production matrix kept exact output parity. Python scale latency was 4.33% below merge parent 07255cf and 61.01% below upstream parent 0c64de9; incremental instructions were 3.09% and 54.07% lower. Focused ASan/UBSan scope and Python suites passed 33 and 125 tests. Signed-off-by: Andrew Hundt <ATHundt@gmail.com> (cherry picked from commit 4d3a103) Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Previous behavior: cbm_scope_lookup(), cbm_scope_contains(), and cbm_scope_lookup_callable() called an out-of-line complete-binding scan after the Python lookup consolidation. What changed: - internal/cbm/lsp/scope.h defines cbm_scope_lookup_binding() as the one static-inline scope-chain scan. - internal/cbm/lsp/scope.c keeps the type, presence, and callable wrappers on that shared implementation without another function boundary. Why: Python still reads type and callable identity in one traversal, while existing language resolvers retain the direct hot path used before the consolidation. Verification: - CBM_ONLY_SUITE=scope build/c/test-runner: 33 passed - CBM_ONLY_SUITE=cs_lsp build/c/test-runner: 62 passed - CBM_ONLY_SUITE=py_lsp build/c/test-runner: 125 passed - neutral 11-pair extraction matrix: output parity; C# latency -2.97% vs first parent and -1.64% vs upstream; retired instructions +0.29% vs first parent and -1.01% vs upstream; Python scale latency -4.25% and instructions -2.81% vs first parent Signed-off-by: Andrew Hundt <ATHundt@gmail.com> (cherry picked from commit 150c88e) Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…anner patch security-static (scripts/security-vendored.sh) failed on this branch: the #2176 fix patches internal/cbm/vendored/grammars/rescript/scanner.c (skip the template-string branch in the error-recovery state) and records that patch in vendored/grammars/MANIFEST.md, but the checked-in digests in scripts/vendored-checksums.txt still described the unpatched files. Both changes are intentional and reviewed in the parent commit; this records their SHA-256 via `scripts/security-vendored.sh --update`. Only the two affected lines change, and the integrity check passes again. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Two CI failures, both in the tests this branch added: 1. Windows (MinGW clang64) did not compile the test runner: test_parse_coverage.c and test_pipeline.c called POSIX setenv/unsetenv, which the Windows CRT does not declare. They now use the repo's cbm_setenv/cbm_unsetenv from foundation/compat.h, as the other suites do. 2. sql_dump_of_many_megabytes_is_indexed_not_timed_out_issue1735 failed on the ubuntu-24.04-arm ASan leg with "parse timeout". It extracted a 26 MB dump under CBM_EXTRACT_BUDGET, which is CPU time, so the verdict depended on runner speed. It passed on the fast legs and tripped on the slow one, and the early FAIL return leaked the result (the LSan report in that log). The property behind "indexed, not timed out" is a count: parse work is set by the statements, not the rows. The test now extracts unbudgeted, asserts the outcome (the view and the subquery usage are in the graph), and bounds the tree by the same 330 statements with 4 rows each. Results are freed before any assertion. Binds to the fix: forcing the full parse for the big dump through the CBM_TEST_SQL_FULL_PARSE_ON seam fails the bound (5,158,222 tree nodes vs a bound of 52,872); with the row exclusion it passes. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com> # Conflicts: # tests/test_pipeline.c
fix(rescript): stop binary .res resources hanging the indexer (#2176)
fix(sql,coverage): make large SQL dumps cheap to parse (#1735)
fix(daemon): name the conflicting daemon and the way out on a build conflict (#2277)
fix(install): migrate v0.10.8 OpenCode agent profiles on upgrade (#2264)
…e-name fix(registry): reach a cfg-fenced function from its bare name
fix(tests): create nested pipeline fixtures reliably
…rfacing fix(mcp): name the artifact phase when a post-publish export fails
…t-cap fix(extract): AST walker stacks grow instead of silently capping content
fix(store): report a failed COUNT read instead of returning zero
style(mcp): compare nullable strings through one helper
fix(cli): emit the OpenCode plugin as a V2 default export
…t-inherited fix(discover): honor an enclosing repo's .gitignore for a git-less subfolder
perf(lsp): collapse repeated Python scope-chain lookups
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )