1117 Commits

Author SHA1 Message Date
Alexander Musichen 104c9a7262 Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-21 04:26:56 +02:00
Martin Vogel 7b6363d0b6 Merge pull request #1772 from DeusData/fix/startup-coordination-lingering-lock
fix(cli): wait out a held startup transition instead of failing at 10s
2026-08-21 01:44:12 +02:00
Martin Vogel e830293ef1 Merge pull request #1065 from tmonestudio/codex/fix-json-edge-properties
fix(pipeline): escape dynamic edge properties as JSON
2026-08-20 19:11:33 +02:00
Martin Vogel 4a29f0da59 fix(cli): wait out a held startup transition instead of failing at 10s
A busy startup transition always resolves, by one of two routes: the live
peer holding it finishes its command, or the holder is already dead and
the operating system has not finished reclaiming the lock. Waiting was
capped at 10s regardless, so a clock decided a user-visible outcome and
commands were refused with "CLI startup coordination remained busy" while
nothing was actually wrong.

The second route is Windows-specific and is what makes this reachable in
practice. On POSIX the kernel drops flock the instant an owner dies. Windows
byte-range locks do not: Microsoft documents that after a process terminates
holding one, "the time it takes for the operating system to unlock these
locks depends upon available system resources", and that until then "access
to these files may be denied". A loaded CI runner is exactly where those
resources are scarce, and tests/windows/test_daemon_stability.py creates the
condition deliberately by hard-killing daemons with `taskkill /F`, including
a crash-recovery section -- so the next client meets a lock whose owner no
longer exists and is refused for a reason that no longer applies.

The cap becomes a backstop against a peer that never finishes rather than a
budget for healthy contention, and the message it prints now names both
explanations instead of just "busy", which sent reporters looking for a CBM
session that had already exited.

Clean exits already release via main_local_transition_close, so no new
release path is needed; this only affects what happens after an abrupt
termination or under genuine concurrency.

VERIFICATION LIMIT, stated plainly: this cannot be reproduced or verified on
macOS -- the mechanism requires Windows lock-reclaim semantics, and a local
12-client storm passes even under the old cap. 458 daemon/ipc/runtime/cli/
watcher tests pass and lint is clean, but whether this removes the CI flake
can only be established on Windows CI.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-20 18:17:28 +02:00
Martin Vogel 1af49dfe58 fix(watcher): stop nested non-git dirs and monorepo siblings retriggering
Three related defects in how the watcher decides what counts as a change.

1. `git rev-parse --git-dir` walks UP the tree, so an ordinary folder that
   merely sits under an unrelated repository answered yes. The watcher then
   inherited that ancestor's dirty state -- permanently non-empty and about
   entirely different files -- and reindexed on every poll forever. A
   directory is now git-managed only if it carries its own .git (directory
   or gitlink file) or the ancestor actually tracks something inside it, so
   a real monorepo sub-package still qualifies while a scratch or ignored
   folder does not.

2. `git status` reports the whole repository regardless of -C, so every
   package in a monorepo reindexed whenever any sibling was edited. The
   status call is now scoped with a `-- .` pathspec.

3. Porcelain paths are REPOSITORY-relative but were stat'ed against
   root_path. For a project watched at the repository root the two coincide
   and the bug is invisible; for a subdirectory project every stat missed,
   silently degrading the dirty signature to text-only and losing the
   size/mtime component that makes an edit to an already-dirty file
   detectable. Paths now resolve through `rev-parse --show-cdup`.
   --show-cdup rather than --show-toplevel because MSYS/Cygwin git returns a
   translated absolute path from the latter that will not join onto the
   native path we hold; a relative hop composes on every platform.

Distilled from #1001 by bethzyy, whose diagnosis identified all three.
Main independently fixed that PR's two headline problems (the unbounded
non-git file count, and per-poll churn via a dirty-state signature) while
it waited, and the watcher was rewritten onto a supervised argv-spawn
harness in between, so this is a reimplementation against the new code
rather than a rebase of theirs.

Both new tests are verified binding: reverting the classification guard
reddens the nested-directory test, and removing the pathspec reddens the
monorepo one, each without disturbing the other.

Addresses the remaining parts of #713, #841 and #937.

Co-authored-by: bethzyy <bethzyy@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-20 16:43:38 +02:00
Martin Vogel dfe67cc771 Merge pull request #955 from EightDoor/fix/windows-process-enumeration
fix(ui): enumerate codebase-memory-mcp processes on Windows
2026-08-20 15:31:08 +02:00
Martin Vogel 43ecc0988c feat(dbt): extract dbt model lineage from Jinja-templated SQL
A dbt model is an ordinary .sql file whose dependencies are written
{{ ref('other_model') }} or {{ source('group','table') }}, never as
literal table names. The SQL grammar cannot read those -- FROM {{ ref(
'x') }} is a parse error to it -- so the dependency structure of an
entire dbt project was invisible to the graph.

extract_dbt.c runs as a sub-extractor inside the normal indexing
pipeline. Per qualifying file it emits one Model definition (named by
the file stem, dbt's own model identity) plus one usage per ref()/
source() call; pass_usages resolves those into model -> relation
lineage edges like any other reference.

Model is a relation label alongside Table and View, which is what makes
the rest work without new plumbing: registry seeding, the central
relation veto, the incremental surface hash, search ranking and the
architecture queries all pick it up from cbm_label_is_relation. Sharing
one label class also means a model's source('raw','customers') resolves
onto a Table declared in a plain DDL migration in the same repository,
so dbt lineage and SQL lineage form one graph rather than two, while
the veto keeps model names out of every non-lineage consumer.

The pass is self-gating: SQL files only, and only those carrying a real
ref()/source() call. The dbt builtins are themselves the evidence, which
is cheaper than a dbt_project.yml lookup and more precise -- templated
SQL that is not dbt (an Airflow {{ ds }} parameter) produces no Model
node and no usages even inside a dbt repository.

{% macro %} definitions are deliberately excluded: the vendored
tree-sitter-jinja2 grammar has no node types for {% %} statements, so
they can only be recovered by a hand-written scanner that cannot see
comments or find {% endmacro %} for a correct span. Filed as follow-up
rather than approximated.

Tests cover the lineage, the last-string-argument semantics of both
builtins, the gate (against a plain-SQL control extraction), plain DDL
staying untouched, and an end-to-end pipeline case asserting model ->
model across files, model -> Table onto plain DDL, and cross-language
isolation. Disabling the pass reddens three of them; removing Model from
the relation set breaks lineage outright.

Implements the lineage half of #575.

Co-authored-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-20 10:23:48 +02:00
Alexander Musichen 495a79dc25 Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-20 04:14:09 +02:00
Martin Vogel 7ba84e3754 feat(sql): first-class Table/View nodes with FROM/JOIN lineage
CREATE TABLE / CREATE VIEW / CREATE MATERIALIZED VIEW now produce Table
and View nodes (previously generic Variable), CREATE PROCEDURE produces
a Function, and schema-qualified DDL names (schema.table) are named by
the table identifier instead of the schema. A view's FROM/JOIN relations
are emitted as usages and resolve into view -> table USAGE lineage
edges.

Relations join the cross-file name registry so lineage can resolve, with
two structural safeguards:

- Registry membership is defined once by cbm_label_is_registry_symbol
  (helpers.c); the full, parallel and incremental seed sites all call
  it, ending the KEEP-IN-SYNC copies the old label lists required.
- The default cbm_registry_resolve vetoes relation-labeled results:
  common table names (users, orders, config) collide with code
  identifiers in every language, and no CALLS/USAGE/READS/WRITES/THROWS/
  handler/decorator consumer may bind them. The SQL lineage path opts in
  through the new cbm_registry_resolve_lineage.

Table/View also join the registry-only per-file LSP surface labels so a
table rename invalidates dependent SQL files on incremental (no stale
lineage edges), rank with the type tier in BM25 search, and appear in
the architecture boundary/package/cluster queries via the pinned
CBM_SQL_RELATION_LABELS fragment.

Tests: extraction trio (labels, lineage usages, schema-qualified names),
grammar golden + probe updates, relation-label contract pin, and two
pipeline tests: cross-language isolation (binding: fails without the
veto) and incremental table-rename stale-lineage.

Closes #574.

Co-authored-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-20 00:12:28 +02:00
Alexander Musichen 0b22dfff7b Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-18 20:52:22 +02:00
Martin Vogel afebf0092b Merge pull request #1326 from Enferlain/fix/1287-persisted-coverage-summary
fix(index): preserve persisted coverage summaries
2026-08-18 18:57:53 +02:00
Martin Vogel 00beec9397 Merge pull request #1608 from ertankucukoglu/fix/search-code-cancellation
fix(mcp): bound and cancel Windows code search
2026-08-18 16:36:40 +02:00
Alexander Musichen 1f1832ab37 Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-18 13:37:23 +02:00
Martin Vogel fe4396f906 Merge branch 'main' into fix/1287-persisted-coverage-summary 2026-08-18 11:21:46 +02:00
Martin Vogel 58ef9f19ac fix(cypher): expansion materializes every matched row — the cap falsified aggregates
Second mechanism of #1196, exposed by the reporter's v0.10.6 retest: every
relationship-expansion site capped its output buffer at bind_cap*10, so edges
past the cap were silently dropped BEFORE WHERE and aggregation ever ran.
count() then reported the scanned prefix as if it were a fact — 9,360 of
13,691 DEFINES field-measured at --max-rows 1000 — and a LABEL on the source
did not protect you (the label workaround only ever fixed source
enumeration, which #1323 already made exact). max_rows is an output-row
limit per the public header; projection already enforces it.

All five capped sites now share one growable append (geometric growth,
size_t sizing): the per-hop expansion, its fixed/variable-length helpers and
process_edges, the bound-terminal driver, and the cross-join outer buffer.
Only allocation failure stops materialisation; match_count stays truthful
either way, so the #627 OPTIONAL contracts hold (a saturated buffer can no
longer exist, and the fallback rows share the same append). The #601
deadline still bounds pathological time, and hop caps keep bounding depth —
this removes only the silent row-dropping.

Regression test: 2 labeled sources x 30 edges with max_rows=2 — the old cap
returned count=20; ground truth 60 now holds, and the list form returns
exactly max_rows rows. Proven RED before and RED again on revert; cypher
181/181 and mcp suites green, including every OPTIONAL/#627 semantics test.

Fixes #1196 (together with #1323, which fixed the unlabeled source scan).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-18 11:19:32 +02:00
Martin Vogel 07cac7d6b3 Merge pull request #1371 from Joseph-MingEn/fix/py-aliased-from-import-calls
fix(python): resolve aliased from-import CALLS to real def
2026-08-18 11:19:19 +02:00
Martin Vogel e24ce80bdc Merge origin/main into fix/search-code-cancellation
Trivial insertion-point collision in tests/test_mcp.c: both this branch and
main (#1704's UTF-8 pin) add a Windows search_code test at the same spot.
Both tests kept.
2026-08-18 11:08:17 +02:00
Martin Vogel 4dd099679d fix(windows): pin the search_code PowerShell pipe to UTF-8
PowerShell 5.1 encodes stdout for a native-process pipe in the console
OEM codepage, so raw search content containing characters the inherited
CP cannot carry (Cyrillic under CP437/850, ...) reached
collect_grep_matches as '?' — and whether it degraded depended entirely
on which console the server happened to inherit. That surfaced as the
intermittent test_mcp raw-Русский mojibake on the windows CI leg and
means real Windows users in a default console get '?' for all
non-ASCII search_code raw content.

Every generated command now pins [Console]::OutputEncoding to UTF-8, so
the pipe is codepage-independent by construction. The read side needs
no pin: Select-String decodes BOM-less UTF-8 via .NET StreamReader
defaults. A Windows-side builder test asserts all five command variants
carry the prelude.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-18 09:31:59 +02:00
Martin Vogel 773bb037dc fix(test): the stdin gate's #1359 guards survive a registry with no zero-argument tool
#1181 gave list_projects pagination parameters, retiring the last
empty-properties schema — and the two #1359 regression tests leaned on
list_projects as their live zero-argument example, so main went red the
moment the merge train composed (the PR was green on its July base, which
predated these tests).

The gate's schema→decision core is split behind a CBM_CLI_ENABLE_TEST_API
seam, so the zero-argument branch stays pinned directly (empty properties,
absent properties, populated properties) regardless of what the registry
ships; list_projects now asserts its NEW truth (piped args accepted, TTY
still refused); and the schema↔gate parity sweep keeps running over every
tool without the impossible >=1 zero-argument floor. Production behavior
is unchanged — this is the tests catching up with an intended schema
change, plus a seam so they never again depend on a shipped example.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-18 07:15:09 +02:00
Alexander Musichen 4656a5f3e1 Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-18 04:20:37 +02:00
Alex Musichen 109299f262 fix(pi): match ToolDefinition.execute arity on 0.84.2
Pi 0.84.2 calls execute(toolCallId, params, signal, onUpdate, ctx).
The generated (args, ctx) shape bound the call id as the MCP arguments.

Forward params and signal, pin the @earendil-works/pi-coding-agent
contract in the generated header, and lock the 5-arg form in tests.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-18 04:11:49 +02:00
Martin Vogel 9a547b3399 Merge pull request #1253 from anfedoro/agent/fix-search-code-utf8
fix: preserve UTF-8 in search_code source
2026-08-18 00:14:32 +02:00
Martin Vogel 78d1b2f0a6 Merge pull request #1203 from ibaldr89/fix/extensionless-shebang-language
fix(discover): detect extensionless scripts by shebang
2026-08-18 00:14:17 +02:00
Martin Vogel b61949355b Merge branch 'main' into fix/1287-persisted-coverage-summary 2026-08-18 00:12:02 +02:00
Martin Vogel 3dab881b66 Merge pull request #1181 from tmonestudio/codex/fix-list-projects-coverage-scope
fix(mcp): restore scalable project discovery
2026-08-18 00:08:28 +02:00
Martin Vogel ce1f273efe Merge pull request #1467 from PR9000/fix/freebsd-platform-support
fix(daemon): add native FreeBSD process-image identity and /home log-path alias
2026-08-18 00:08:05 +02:00
Martin Vogel 415a64a2f9 Merge pull request #1263 from astandrik/fix/index-mode-capability-rebuild
fix: rebuild index when mode adds capabilities
2026-08-18 00:07:51 +02:00
Martin Vogel 89f0cd43c0 Merge pull request #1647 from rudi193-cmd/fix/725-cross-language-suffix-match
fix(registry): drop suffix_match CALLS across language boundaries
2026-08-18 00:05:33 +02:00
Martin Vogel 22d93dd12b Merge pull request #1323 from Enferlain/fix/1196-unlabeled-candidate-limit
fix(cypher): scan all unlabeled query candidates
2026-08-18 00:05:18 +02:00
Martin Vogel 2397d58da6 Merge pull request #1325 from Enferlain/fix/1284-list-valued-fields
fix(search): preserve compound requested fields
2026-08-18 00:04:55 +02:00
Martin Vogel de4ec9b5e9 Merge pull request #1319 from JJordan0C/fix/search-graph-semantic-only-results
fix(mcp): isolate semantic-only JSON search
2026-08-18 00:04:36 +02:00
Martin Vogel 33a3f0322a Merge pull request #1308 from Yyunozor/fix/issue-1294-is-test-tests-dir
fix(extract): converge Function/Method is_test with the tests/ path filter
2026-08-18 00:02:28 +02:00
astandrik d024f41e7f fix(pipeline): preserve coverage and artifact ordering
Keep caller-requested discovery scope while rebuilding changed weaker-mode requests at the stronger stored coverage. Export persistent artifacts only after the replacement database generation is published.

Signed-off-by: astandrik <astandrik@yandex-team.ru>
2026-08-17 13:03:40 +03:00
Martin Vogel c1a5de3bda Merge pull request #1685 from DeusData/fix/windows-acl-repair-v3
fix(windows): conditional DACL re-stamp + damaged-children repair, gated on the adoption-level owner-only predicate
2026-08-17 07:57:33 +02:00
Ertan 506151f6c1 fix(mcp): propagate cancellation to Windows code search
Signed-off-by: Ertan <ertan.kucukoglu@gmail.com>
2026-08-17 08:49:50 +03:00
Martin Vogel 93e93087b4 Merge pull request #1683 from DeusData/fix/install-cluster
fix(install): Hermes YAML constructs, goose required name, annotated MCP entry repair (#1631, #1675, #1630)
2026-08-17 07:10:29 +02:00
Martin Vogel 47bd4b6847 fix(windows): conditional DACL re-stamp + damaged-children repair
Re-stamp the runtime DACL only when it is actually wrong, and repair cache
children left unusable by the pre-v0.10.3 DACL regime.

The unconditional per-start re-stamp rewrote an already-correct security
descriptor and propagated it to children (#1601 counted eleven no-op
"Security change" USN records against one _config.db in a day), and every
rewrite is a window in which a concurrent atomic publish can be refused
DELETE on the destination (#1620). The old regime's PROTECTED,
non-inheritable ACE also left every child born unusable — the 0-byte
worker-log class behind #1416's diagnosis — so the secured directory now
walks its regular children and repairs any with an empty DACL or a foreign
owner.

The fast path is gated on the ADOPTION-level predicate, not the general
secure() check: lock-directory adoption (private_win_owner_only_dacl)
demands the exact protected owner-only single-ACE descriptor the stamp
writes, while secure() also admits SYSTEM/Administrators ACEs. A fresh
directory with an inherited DACL passed secure(), skipped the stamp, and
stranded every subsequent lock adoption — 59/77 daemon-suite failures on
the real Windows VM. With the ported predicate (SE_DACL_PROTECTED,
single non-inherited owner ACE, FILE_ALL_ACCESS/GENERIC_ALL) the same VM
runs 77/77 and the full suite 7346/0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 06:12:11 +02:00
Martin Vogel 20ad3e5b89 fix(cli): forward CBM_RUNTIME_DIR in the generated Codex configuration (#1664)
Codex sanitizes stdio MCP subprocess environments to the names listed in
env_vars. Since #1645 CBM_RUNTIME_DIR relocates the daemon rendezvous, so a
Codex subprocess that does not receive it looks for the daemon in the DEFAULT
location and never finds it — the same silent client/daemon split
CBM_CACHE_DIR caused in #1562. Both names decide WHICH daemon a process talks
to and are now forwarded unconditionally (forward-if-present semantics);
behavioural knobs (log level, workers, budgets) deliberately stay
unforwarded — that broader list remains #1664's open enhancement question.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 05:40:38 +02:00
Martin Vogel 276664ebff fix(cli): compare our binary path separator-insensitively in MCP ownership (#1582)
gotspatel's live opencode.json stores our entry with backslashes
(`C:\...\codebase-memory-mcp.exe`) while the installer compares its own path
with forward slashes — the same file on disk, refused over the separator
spelling, so op=mcp_install failed on a correctly-installed machine (and on
Windows the dead-path probe rightly reported the binary PRESENT, which turned
the mismatch into a hard refusal).

Ownership comparison now treats `\` and `/` as equal everywhere and folds
case on Windows only, where the filesystem is case-insensitive; POSIX
byte-exactness otherwise holds. The annotated entry that names this binary is
recognised as already satisfied and preserved byte-for-byte.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 05:12:08 +02:00
Martin Vogel 4cd84422ac fix(yaml): accept a UTF-8 BOM as a document prologue (#1656)
PowerShell 5.1's `Set-Content -Encoding UTF8` writes a BOM, so real
Windows-authored Hermes configs start with EF BB BF — and both edit ops
failed content-independently (the reporter's 26-byte reproduction is their
23-byte file plus exactly this BOM; reproduced RED on macOS with the same
bytes, so the platform was never the variable).

The document read now validates past a leading BOM and yaml_doc_init treats
it as a prologue: the first line's structure starts after it, the key lookup
still sees our own section when the BOM immediately precedes it (guarded by a
dedicated no-duplicate-section test), and every edit splices interior ranges,
so the BOM survives writes byte-for-byte. Non-document inputs — keys, entry
blocks, identity scalars — keep the strict no-BOM rule.

Also makes the moved-entry cli test fixture platform-correct: the Windows
dead-path probe can only prove a fixed-drive path absent, so the Windows
branch uses one; a POSIX-shaped path is refused there by design.

Fixes #1656.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 04:54:14 +02:00
Martin Vogel 33d1ecea1b fix(cli): install-entry compatibility repairs for goose and annotated MCP entries
Two install failures with the same root theme — the entry we write is a
compatibility contract with the agent's parser, and both sides of that
contract needed repair:

goose (#1675): ExtensionConfig::Stdio declares `name` as a required serde
field with no default, and goose's loader silently drops entries that fail to
deserialize — install reported success and the extension was invisible. The
goose block now carries `name: codebase-memory-mcp`; the non-goose YAML
schema stays name-free. A CBM_CLI_ENABLE_TEST_API seam asserts the exact
block bytes per schema.

annotated MCP entries (#1630, the deferred field-merge): dbd20eaa recognised
an entry the client annotated ("enabled": true beside our command/type) but
could only leave it untouched, because replacing the whole entry would drop
the client's keys. config_json_like gains
cbm_json_like_replace_field_raw_if_unchanged — splice ONE member's value,
preserving every other byte (comments, ordering, client keys) — and the
upsert flow uses it on the two AUTHORIZED repair channels only:

- a relocating update (the entry names the previous managed binary), and
- the existing Windows dead-path probe, which previously fell back to a
  wholesale rewrite and lost the annotations.

POSIX keeps its doctrine unchanged: a config-supplied path is never trusted,
so a moved-looking entry without that authority is preserved byte-for-byte
and install fails loudly (cli_editor_mcp_preserves_unrecorded_posix_absolute_
entries_without_probe holds). All repair/refusal paths are covered by tests
proven RED on the unfixed flow.

Fixes #1675.
Fixes #1630.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 04:23:50 +02:00
Martin Vogel b54998d242 fix(yaml): accept the #1631 constructs and repair prior owned entries
Four legal-YAML constructs from the reporters' real Hermes configs made
`install` fail permanently (any one of them aborted mcp_install and/or
pre_llm_hook_install):

- exact empty flow collections as values (`plugins: []`, `tool_choice: {}`)
  — now validated key-only in both the mapping-body and sequence document
  scans, mirroring the #1673 empty-mapping exception; non-empty flow
  collections stay rejected.
- block sequences at the same indent as their mapping key (column-0 `- item`)
  — item lines directly after a value-less key are structure, not malformed
  keys, in the root walker, the key matcher, and the sequence mapping-range
  walker.
- double-quoted scalars continued across lines with a trailing `\` — the doc
  loader now precomputes per-line continuation flags; continuation lines are
  value bytes every structural walker skips, and a document ending inside an
  open continuation stays an error.
- mid-word quote characters in plain scalars (`LET'S`) — quotes are scalar
  indicators only at a node start (range start, after `:`, after `-`),
  exactly like the #1639 anchor/alias rule; real quoted values keep their
  protection.

Byte-identity alone also froze users on canonicals older releases wrote:
galaxy's entry had `command:` unquoted, and the goose block gained `name:`
(#1675), so the existing entry was declared FOREIGN forever. An entry under
our key now repairs when it parses as a known prior shape (single command
line, or the pre-name goose block) with a codebase-memory-mcp[.exe] command
basename; anything else stays FOREIGN and the file untouched.

End-to-end: both reporters' full configs (iandol 15.6 KB, galaxy 15.2 KB) now
install with zero agent_config errors, every original line byte-preserved,
and the goose upgrade path rewrites the old block in place. Each construct
carries a distilled regression test proven RED on the unfixed editor.

Fixes #1631.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 04:23:50 +02:00
Martin Vogel 41d240accf Merge pull request #1681 from DeusData/feat/scaling-probe
perf(lsp): eliminate the cross-LSP O(n²) — shared Java registry, own-file overlay, complexity guard (#1669)
2026-08-17 02:05:47 +02:00
Martin Vogel e87b42ceba Merge pull request #1680 from pcristin/fix/goose-empty-flow-mapping
fix(yaml): accept empty flow mappings in mapping bodies
2026-08-17 01:37:52 +02:00
Alex Musichen d918fce287 style: fix clang-format violation in pi adapter
clang-format wants no spaces inside a braced initializer.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 22:58:58 +02:00
Alex Musichen cf5eb61c20 fix(pi): return a valid AgentToolResult and request raw JSON
The generated execute forwarded the raw MCP JSON directly, but pi's
ToolDefinition.execute must return { content: [{ type: 'text', text }],
details } — a result without a content array crashes the TUI's
getTextOutput on result.content.filter(...).

Request raw JSON from the CLI ('--json') so the bridge parses the MCP
result instead of the human-readable text, then wrap it: pass the content
array through, throw on transport errors, and stringify anything else.
Adds coverage asserting the corrected execute shape and the --json flag.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 22:26:34 +02:00
Martin Vogel a412642784 perf(store,ts): dedup coverage shadow-graph dirs; TS export re-export boundary
Two measured changes on the TypeScript corpus (81,397 files; v0.9.0 17.9 s,
before 44.1 s, after 37.5 s):

- cov_rebuild_shadow_graph upserted every directory segment for every
  failure row — 13,243 parse-partial baseline files under one tests/
  subtree meant ~80k redundant node/edge round-trips, 9.1 s of a 9.2 s
  coverage_replace. An in-rebuild path->id map creates each directory once;
  identical graph (edges deduped by unique key before, absent now).
  Coverage block 9,162 -> 2,920 ms. Sub-block timings
  (publish.timing.coverage: del/rows/prune/meta/commit + row_count +
  detail_bytes) are kept — the caller-level number could not name the
  culprit.

- JS/TS export_statement is an import CONTEXT only in its re-export forms
  (source field, or a bare specifier list without a declaration). The old
  is_export_of_declaration blacklist missed TS-only forms
  (ambient_declaration, function_signature, module_declaration), running
  declare-heavy subtrees (.d.ts, export namespace) behind inside_import:
  suppressed usages + per-identifier ancestor walks. Positive detection
  replaces the blacklist; +3,954 restored usage edges on the corpus,
  nodes identical. (Measured perf-neutral here — kept for correctness.)

Suites green incl. store_nodes/edges/search, mcp, extraction, ts_lsp,
complexity, and the 53-language calls-breadth contract.

Refs #1669.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-16 22:10:34 +02:00
Martin Vogel a4c0ffbce8 perf(cs): eliminate the C# corpus-proportional scans — 1211s -> 450s, +37% edges
Four measured changes on the dotnet/runtime corpus (58,656 files; every
step's numbers below are from full-corpus runs on the same host, baselines
captured this session; v0.9.0 = 444.3 s):

1. Two-phase cs registry build (types -> finalize -> funcs), the same
   pre-finalize linear-lookup disease fixed for Java: prepare 280 -> 140 s.

2. C# import-context carve-out: cs_import_types lists namespace_declaration
   (for namespace-name mapping) and using_statement (C#'s RAII block — a
   grammar-name collision), so EVERY namespaced C# file's whole body ran
   with inside_import=true. That both suppressed ordinary usage extraction
   under namespaces and sent every identifier through the ancestor-walking
   import-binding check (tree-sitter's ts_node_parent re-descends from the
   root, so wide files went quadratic: one 147 KB JIT torture file cost
   490 s; 6.8 s after). Only using_directive / namespace_use_declaration
   open an import scope now. Restores the suppressed usages:
   edges 4,291,387 -> 5,869,093 (+37%), nodes identical.

3. The usages walker maintains call/import ancestry as enter/exit counters
   on its explicit stack instead of per-node ancestor re-walks
   (extract_usages.c had grown from 6 to 100 ts_node_parent calls since
   v0.9.0; the two per-node gates are now O(1) with semantics preserved —
   strict ancestors only, emit before self-count).

4. Registry short-name indexes replace the two remaining full scans:
   cs_lookup_extension walked all 963k funcs per unresolved invocation
   (now the existing free-func short-name iterator, first-match order
   preserved via min-index selection), and cs_resolve_type_name's step-9
   fallback scanned every type per unresolved name IN BOTH the builder and
   per-file resolution (new type_short index in finalize, same
   reverse-insertion ascending-order pattern, best-score ties keep the
   first-in-registration-order winner). Builder 140,095 -> 500 ms; resolve
   cross-LSP CPU 5.5M -> 317k ms (us_per_file_per_kdef 191 -> 11).

End state: 449.9 s wall (1.01x of v0.9.0) with +48.6% edges vs v0.9.0 —
per-edge cost 32% BETTER than v0.9.0. Extract's remaining 359 s is the
24 MB hugeexpr1.cs parse floor both versions pay.

Guarded by the complexity suite; cs_lsp/extraction/edge/lang-contract
suites green including the 53-language calls-breadth contract.

Refs #1669.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-16 21:50:34 +02:00
Martin Vogel e24f0c6328 perf(lsp): shared Java cross-registry + own-file overlay (#1669)
Cross-file LSP for Java rebuilt a type registry for EVERY file from a def
set the module filter reduces by a constant factor, not to a constant
size: the JVM filter branch includes every def sharing the file's
namespace, so per-file work tracks the corpus (measured defs_per_file
1,292 -> 5,031 in lockstep as defs_total went 179k -> 689k). That makes
the pass O(files x corpus_defs) — 87% of a Java index and the bulk of
the v0.9.0 -> v0.10.x slowdown.

Three changes, one architecture (the pattern Go/Python/C/C#/TS already
use):

1. cbm_java_build_cross_registry — the JVM def universe (Java + Kotlin,
   for mixed source roots) built ONCE per run, sealed read-only, shared
   across resolve workers. Wired into both pipeline.c and
   pipeline_incremental.c.

2. Two-phase registration inside that build: all TYPES first, finalize
   (hash buckets exist), then FUNCS. Func registration parses signatures,
   and type-name qualification via cbm_registry_lookup_type is a LINEAR
   scan until finalize — a single mixed pass measured 0.44 ms/def at
   689k defs, a ~300 s sequential build that erased the sharing win.
   Two-phase: 306 s -> 2.8 s. Stable partition order because overload
   ties resolve to the first registered QN match.

3. cbm_run_java_lsp_cross_with_registry resolves each file against the
   shared base through an overlay holding exactly THIS FILE's defs
   (register_local_func_or_type_from_file). Own-file scope is the load-
   bearing choice: patch_one_method refines signatures from the AST and
   must write a private copy (its types live in the per-file arena, the
   base is sealed), and any wider scope re-imports the quadratic — a
   module/namespace-scoped overlay measured ratio 4.00 on the growing-
   package corpus, own-file measures 2.00.

Elasticsearch corpus (46,477 files), same host, CBM_PROFILE=1:

                      v0.10.5      this change      v0.9.0
  wall                419.9 s      85.6 s (4.9x)    61.5 s
  cross-LSP CPU       6,195,780ms  52,513ms (118x)  110,344 ms
  us_per_file         207,448      1,770            3,722
  us_per_file_per_kdef 300         2                —
  nodes               693,100      693,100          —
  edges               5,646,235    5,649,949        —

Cross-LSP CPU now beats v0.9.0. Nodes are identical; edges +0.066%,
consistent with the shared base resolving cross-package targets the old
per-file namespace/import filter could not see, plus source-order
independence from the two-phase build.

Guarded by the complexity suite's shared-package gate (RED at ratio 4.00
on the pre-change tree, 2.00 after — see the suite commit).

Refs #1669.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-16 18:43:44 +02:00
Alex Musichen 263cf22b9c fix(pi): emit parameters and execute in the generated pi adapter
The generated Pi extension registered each MCP tool as { name, run }, but
Pi's ToolDefinition requires label, description, parameters, and execute.
Tools registered that way carried no parameter schema, so strict providers
such as xAI/Grok reject the request with a 422 'missing field parameters',
and the tools were uncallable because Pi invokes execute, never run.

Emit the full tool shape from the registry: label/description via new
accessors, the input_schema embedded directly as a JSON object literal, and
execute instead of run.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 16:41:36 +02:00