The 300ms in-process SIGALRM budget in hook-augment self-terminated
with a silent _exit(0) on real cold starts (SQLite/mmap open under
load), so PreToolUse augmentation never appeared in real sessions
(reporter: 0/24 observed) while warm manual invocations worked - and a
fired deadline was indistinguishable from a legitimate no-match run.
Default budget is now 2000ms (the settings.json hook timeout stays the
outer backstop), overridable via CBM_HOOK_DEADLINE_MS (clamped
50..10000). When the deadline fires, the handler write()s a
pre-formatted breadcrumb (deadline, pid, the env knob to raise) to
~/.cache/codebase-memory-mcp/logs/hook-augment-timeouts.log before
exiting - fd and message are prepared at arm time so the handler stays
async-signal-safe; CBM_HOOK_TIMEOUT_LOG overrides the path for tests.
Windows keeps relying on the settings.json timeout (unchanged no-op).
Deterministic reproduction: stdin is a pipe whose writer never sends,
so the read blocks past a 60ms deadline - the timer must fire, exit 0
(fail-open), and leave the breadcrumb. RED before (no breadcrumb),
GREEN after.
Closes#858
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A persistently dirty or untracked worktree re-triggered a full reindex
(and its DB/artifact rewrite) on every poll cycle - git_is_dirty()
answered 'is the tree dirty' where the watcher needs 'did the tree
CHANGE'. Idle repos with one uncommitted file produced sustained disk
write amplification (reported at 1 TB/day across several repos).
The watcher now tracks a dirty-state signature: FNV-1a over the
entries of git status --porcelain -uall -z plus each listed path's
(size, mtime_ns). Same signature -> no reindex; editing a dirty file,
adding/removing one, or reverting the tree to clean each produce a new
signature and trigger exactly one reindex. Submodule status is folded
in on POSIX, mirroring the old submodule dirty probe. 0 is reserved
for a clean tree; a dirty-at-baseline tree still reindexes once (the
watcher cannot know whether that state reached the DB, e.g. after a
server restart with a stale artifact).
Baselines (HEAD and signature) are now committed only after a
SUCCESSFUL reindex: check_changes stages observations in pending_*
fields, and busy-skips (index_fn > 0, e.g. pipeline lock held) or
failed runs leave the baselines untouched so the change is retried on
the next poll instead of being recorded as seen and silently lost -
the old code stored the new HEAD at check time, dropping any commit
that arrived while the pipeline was busy.
Closes#937
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The compat.h include added for CBM_TLS pulled windows.h into cypher.c,
whose legacy 'far' macro (windef.h defines it empty) broke the
existing EXISTS-pattern local of that name on every Windows leg. Use
C11 _Thread_local for the depth-clamp flag instead; cypher.c stays
free of windows.h.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three of #797's four findings fixed (the fourth — repeated-variable
unification — already holds on main and is now pinned by the test):
- Walk padding: the BFS recursive CTE deduped (node, hop) PAIRS, so a
single self-loop minted every hop level for every node it could
reach — *k..k matched fabricated walks of arbitrary length (a
100-hop 'longest chain' artifact) and the row set exploded to
nodes x depth (the reported crash). The traversal now returns each
node once at its MINIMAL hop (MIN/GROUP BY): variable-length matches
nodes whose shortest path lies in [min,max]. This is deliberately
shortest-path semantics, not openCypher trail enumeration — right
for reachability/depth audits on call graphs and linear instead of
exponential; trace_path shares the traversal and now also reports
each node once on cyclic graphs.
- Silent 100-hop cap: a clamped hop range now surfaces as
result->warning ('clamped ... an empty result may mean clamped, not
no such path'), emitted by query_graph in both TOON and JSON forms,
on top of the existing log WARN.
Reproduce-first: cypher_exec_varlength_path_semantics_issue797 —
self-loop + real 2-chain fixture; RED on the walk semantics (*2..2
returned 3 endpoints instead of 1), GREEN now, incl. variable
unification, zero rows for nonexistent path lengths, and the clamp
warning assertion.
Closes#797
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The WHERE parser rejected coalesce() outright ('unexpected operator'),
blocking null-safe numeric filters over optional graph properties —
the exact audit-query shape from the report:
MATCH (f:Function) WHERE coalesce(f.transitive_loop_depth, 0) >= 2
RETURN-side coalesce already worked; only the WHERE leaf lacked it.
Implementation: the condition leaf gains a coalesce_default literal.
The operator/value tail of condition parsing is factored into a shared
parse_condition_op so both subjects (var[.prop] and
coalesce(var.prop, literal)) accept the full operator set (comparisons,
IS [NOT] NULL, IN). At eval time a missing/empty property value is
substituted with the default before the operator runs. Supported form
is coalesce(var.prop, literal) — matching openCypher's two-argument
null-substitution for this subset.
Reproduce-first: cypher_exec_where_coalesce_issue874 runs the
reporter's exact query — RED (rc=-1 parse error) on the old parser —
and pins both directions: a failing default excludes property-less
nodes, a passing default includes them.
Closes#874
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The previous commit accidentally staged a stale-main vs current-main
delta (a git reset --soft against an outdated origin/main ref),
reverting the #1028 wide-canonicalization work and #1027's regression
test. Restore every touched file to current main; the net branch diff
is now exactly the intended two-file #898 fix (pass_calls.c +
tests/test_mcp.c).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Two emission bugs in the sequential pipeline (pass_calls.c) — the
parallel path was correct, so the two pipelines produced different
bytes for the same input:
- Brokered ASYNC_CALLS edge props closed the method value's quote but
not the broker's, emitting "broker":"bullmq} on EVERY such edge;
the fixup below only repaired truncation, which never fires in the
normal case. Rebuilt as the parallel path's incremental snprintf.
- create_svc_route_node stored the RAW method/broker string as the
synthesized Route node's properties (literally 'bullmq' instead of
{"broker":"bullmq"}). Now emits the parallel path's exact JSON.
Consequences of the malformed rows: json_extract errors across the
query surfaces, the edges generated columns (local_name_gen, url_path
index) fail, and PRAGMA quick_check aborts with 'malformed JSON' —
which since the artifact deep-integrity check (#895) also means such
caches are refused at import.
Reproduce-first: sequential_service_edge_props_are_valid_json_issue898
indexes a small celery fixture (sequential path), asserts >=1
ASYNC_CALLS edge exists (non-vacuous), every edge/node properties blob
is json_valid, and the broker is json_extract-able exactly like the
parallel path emits it. RED on the old emitters (1 malformed edge +
1 malformed Route node), GREEN now.
Note: the reporter's original bullmq JS repro no longer classifies on
current main because CommonJS require()/ESM import resolution has a
separate gap — that is exactly open issue #871; probed and confirmed
while building this fix.
Closes#898
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three path-canonicalization sites routed UTF-8 paths through the ANSI
CRT (_access + _fullpath): the repo_path canonicalization in
handle_index_repository, project-name derivation in fqn.c, and
resolve_canonical_path (the containment check). ANSI decoding is
locale-dependent by construction — on a CJK system codepage (the
reporter runs Big5) the UTF-8 bytes of a CJK repo path re-decode into
different characters, corrupting canonicalization while our CI (en-US
codepage) sails through: that is why the existing non-ASCII guards
stayed green while v0.9.0 crashed in the field.
New cbm_canonical_path: realpath on POSIX (unchanged behavior); on
Windows a wide GetFileAttributesW existence check + GetFullPathNameW,
never touching the ANSI codepage. resolve_canonical_path now also
requires existence on Windows, aligning it with the POSIX realpath
semantics it mirrors.
Guard: the Windows non-ASCII CLI guard gains the reporter's exact
shape — Traditional-Chinese directory (雷達測試), flag-form
--repo-path + --mode fast, real supervised spawn. Honest caveat: CI
cannot switch the system codepage, so the guard validates the
mechanics under en-US; the locale-specific corruption is fixed by
construction (no ANSI path decoding remains on these sites).
Closes#973
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
SIGABRT (free of a non-libmalloc pointer in ts_stack_delete) on the
second index_repository in one process, whenever a sequential run is
followed by a parallel one. The sequential path parses on the CALLING
thread and left its TLS parser alive; the next parallel run switches
the global tree-sitter allocator to the slab (cbm_slab_install), the
calling thread participates in extraction, reuses the stale parser,
and its per-file teardown frees mimalloc-epoch memory through
slab_free's foreign-pointer fallback -> plain free() -> libmalloc
abort. The v0.9.0 supervisor masks this on the default MCP path (fresh
worker process per index); the in-process path and every embedded
consumer died. Root-caused with allocator-epoch instrumentation: the
victim parser is created at epoch 0 in cbm_pipeline_pass_definitions
and deleted at epoch 1 in extract_worker.
Two boundary fixes:
- run_sequential_pipeline destroys the calling thread's parser at run
end (also bounds its memory like the parallel workers already do);
- cbm_parallel_extract destroys the calling thread's parser BEFORE
cbm_slab_install, so no parser ever crosses the allocator switch.
Guards: prod-tier smoke invariant inv_second_index_inprocess
(sequential-then-parallel double index over stdio, RED with signal 6
on the unfixed binary — ASan builds mask the allocator seam, so the
unit tier cannot catch this class) + suite-tier fork-isolated
index_second_inprocess_run_survives_issue773 pinning the in-process
double-index contract. Repro 5/5 clean after the fix (was 5/5 abort).
Closes#773
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The new stale-sidecar guard exposed a pre-existing Windows gap in the
same code path: rename() fails with EEXIST when the destination
exists, so cbm_store_dump_to_file could never install over a previous
DB generation on Windows. New cbm_rename_replace uses
MoveFileExW(MOVEFILE_REPLACE_EXISTING) with wide paths (raw
MoveFileExA would re-mangle non-ASCII cache paths); POSIX keeps plain
atomic rename.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The FAST export path (the watcher/incremental auto-update) read the
raw main-file bytes of a live WAL-mode store: committed rows still in
the -wal were missing entirely and mid-checkpoint reads produced torn
snapshots (field artifact: four short indexes + btreeInitPage error 11
across ~12 nodes-table pages). Import then installed the torn file
byte-for-byte — the integrity check only sanity-checked the projects
table.
- Export: both quality levels now snapshot via VACUUM INTO (the
consistent copy); only the index-stripping remains BEST-only, FAST
keeps zstd-3. The fast path pays a full-copy VACUUM INTO now —
correctness over speed; a torn team-shared artifact costs far more.
- Import: new cbm_store_check_integrity_deep (shallow check + PRAGMA
quick_check) — used only on this rare path, hot-path callers keep
the cheap shallow check.
Reproduce-first: artifact_fast_export_snapshots_live_wal_store
exports FAST while the writer connection is open — RED on the old
code with 0 of 60 nodes surviving the round-trip (all rows lived in
the WAL); GREEN now. store_deep_integrity_detects_page_corruption
pins the import guard both directions (healthy passes, zero-banded
pages refused).
Closes#895
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Every row-scan loop discarded the terminal sqlite3_step code, so
SQLITE_CORRUPT mid-scan was indistinguishable from a clean end of
results: against a page-corrupted DB, counts answered from covering
indexes stayed correct while row fetches died at the first corrupt
table page — every query surface returned plausible truncated/empty
answers with no error (search total=379 with results=[], Cypher counts
diverging from schema counts, BM25 total=0, empty schema sections).
All 31 scan loops now capture the terminal code and fail loudly with
CBM_STORE_ERR + a store error message when a scan aborts, with
per-site cleanup (partial results freed or handed to the caller's
normal free path). Two deliberate soft spots, both enrichment-only and
documented inline: include_connected neighbor names (no store handle)
and the cluster adjacency scan (partial adjacency; primary surfaces
already fail loudly). Best-effort schema property-name discovery and
two capped helper scans keep their by-design early-exit semantics.
Reproduce-first: corrupt_page_scan_returns_error_not_truncation
zeroes a 40-page mid-file band per the issue's dd recipe and asserts
scans return CBM_STORE_ERR. RED on the old code with the exact
reported symptom (OK with 264 of 2000 rows silently truncated); GREEN
now. Fixture-health guards make a healthy-scan pass impossible to
mistake for a fix.
Closes#896
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three code paths install a brand-new DB file where a previous
generation lived without removing the destination's -wal/-shm:
the pipeline page writer (cbm_writer_open), artifact import (which
cleaned the TMP file's sidecars but not the destination's), and
cbm_store_dump_to_file (whose comment wrongly asserted stale sidecars
'become stale and get recreated'). SQLite validates a WAL against the
sidecar's own header/checksums — not the main file — so a crashed
session's leftover WAL is replayed ON TOP of the freshly installed
file at the next open, splicing old-generation pages into it.
New shared helper cbm_remove_db_sidecars (compat_fs, wide-path safe on
Windows) called at all three install points.
Reproduce-first: dump_install_ignores_stale_wal_sidecar follows the
issue's recipe (hot-copy a live WAL, clean close, restore the copy,
install generation 2, reopen). RED on the unguarded code — the replay
clobbered the fresh generation entirely (fresh row unqueryable);
GREEN with the cleanup. Non-vacuous: asserts the hot WAL is non-empty
before the copy.
Closes#897
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Clients like Cline probe resources/list, prompts/list and
resources/templates/list on connect regardless of declared server
capabilities; the -32601 Method-not-found errors surfaced as a failed
connection (status=error) even though tools worked fine. Empty lists
are the interoperable answer for a tools-only server.
Reproduce-first: mcp_discovery_methods_return_empty_lists is RED on
the unhandled dispatch (Method not found) and GREEN with the empty
lists.
Closes#958
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
search_graph now defaults to TOON text, but ha_format_context parses
the inner payload as JSON ("results"), so the Grep/Glob/Read hook
augmenter silently produced no hookSpecificOutput — caught by the
test_hook_augment.py Windows guard going red. Both internal call sites
route through ha_build_args; request the JSON encoding there.
Verified end-to-end: indexed fixture + PreToolUse payload via stdin
emits the symbol context again.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Completes the compact-output pass across the remaining query tools
(consumers are LLM agents; every response byte is context tokens):
- trace_path: callees[N]{qn,hop}/callers[N]{qn,hop} tables (risk/test/
args columns per flags); the per-hop JSON key envelope was 84% of the
payload. 3,589B -> 1,634B on a representative trace.
- query_graph: rows[N]{cols}: table (columns double as the header);
format:"json" restores columns/rows arrays.
- search_code compact mode: results/raw/dirs tables; drops the
field (duplicate of the qn tail). 2,488B -> 1,135B for 17 hits.
- search_code full mode: per-hit source capped at a 60-line window
anchored on the first match, with source_start/source_truncated
markers — uncapped whole-symbol dumps ran to 142KB per response; the
complete symbol stays one get_code_snippet call away.
- get_architecture: default (no aspects) is now a summary (languages,
packages, entry_points + always-on totals/label/type counts) with an
aspects_hint — the old default rendered EVERYTHING including the full
file_tree (94KB -> 4.5KB -> 2.8KB with TOON tables); all sections
emit as TOON tables, aspects:[...] and ["all"] keep full access
(52KB vs 94KB even for the explicit full dump).
- get_code_snippet: drops the property-blob enrichment (41% of the
response; signature/docstring are literally in the source; fp/sp/bt
never belonged there). Metrics remain reachable via search_graph
fields=[...].
- list_projects: per-project 12-field git block (mostly null) replaced
by the branch name; name/root/nodes/edges/size kept.
- search_graph description: in/out documented as TOTAL all-edge degree,
NOT caller/callee counts (agent evals mis-read it as callers).
- format:"json" escape hatch on every converted tool.
Guards: tool_output_byte_budgets pins absolute byte ceilings on default
search/trace outputs (a property blob sneaking back into row emission
blows them immediately); snippet_enriched_properties inverted to pin
the no-spill contract. Suite 5,984 green; smoke updated to the TOON
contract end-to-end.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cbm_cli_build_args_json typed unknown flags as strings and shipped them
as JSON args the server ignores: 'trace_path --max-depth 1' succeeded
but traced at the default depth 3 — silently-wrong output that inflated
an agent evaluation by thousands of tokens of unwanted hop-2/3 rows.
Unknown flags for a known tool now fail loudly with a Levenshtein
nearest-flag suggestion:
unknown flag --max-depth for this tool (did you mean --depth?) — run
'cli trace_path --help' for the supported flags
Unknown-tool (no schema), raw-JSON, --args-file, stdin, and supervisor
worker argv paths are unaffected — none of them route through the flag
converter.
Reproduce-first: cli_build_args_json_unknown_flag_rejected is RED on
the permissive code (JSON produced) and GREEN with the rejection.
Closes#997
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The #979 direct-hit early return in resolve_import_map fires before
suffix handling, so any imported_symbol.method() call whose base symbol
is itself an indexed node resolved to the base node instead of
base.method — the exact mis-resolution the neighboring comment warns
about. On django this degraded the graph by ~11K edges (CALLS
74,685->72,175, TESTS 38,032->29,236); auth.login => Signal.send became
an edge onto the user_logged_in Variable node.
Take the direct hit only for suffix-less callees — precisely the
aliased direct-symbol import case (#875) the early return was added
for. Verified on django: CALLS 74,708 (baseline + the 23 genuine #979
wins), TESTS 38,035; the #979 bare-alias test stays green.
Reproduce-first: resolve_import_map_alias_with_suffix_hits_method is
RED on the unguarded code (returns the bare symbol QN) and GREEN with
the guard.
Closes#1000
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Tool responses land in an LLM agent's context window, where the legacy
per-node JSON objects were dominated by data no agent can use: the
similarity/semantic pipeline intermediates fp (~450B minhash hex), bt
(body-token bag) and sp (structural profile) plus 14 complexity metrics
were grafted onto every result by enrich_node_properties — ~64% of every
node emission, ~370 tokens per search hit.
search_graph now defaults to TOON (Token-Oriented Object Notation,
toonformat.dev): scalars as 'key: value' lines and results as a
'results[N]{qn,label,file,lines,in,out}:' header + one row per hit.
Published benchmarks show equal-or-better LLM retrieval accuracy at
40-60% fewer tokens than JSON; rows now also carry line ranges, which
the legacy output lacked.
- fields:[...] opts into per-node property columns (complexity,
signature, docstring, ...); fp/sp/bt are blocklisted even there
- format:"json" restores the legacy verbose objects byte-identically;
include_connected forces it (nested neighbor lists)
- BM25 and semantic modes emit the same table shape (rank/score column)
- semantic-only calls no longer run the unfiltered regex search that
silently prepended up to 200 enriched nodes (102.8KB -> 4.2KB)
- default limit 200 -> 50 (BM25 100 -> 50): cheap first page, page via
has_more/offset as before
- new emitter module src/mcp/compact_out.{c,h} (string builder + TOON
scalar/table emission with spec quoting rules)
Measured on a real index (14-hit regex search): 20,793B -> 1,327B
(-94%); BM25 19,667B -> 5,081B; semantic 102,837B -> 4,181B. A typical
discovery session drops ~50.9K -> ~4.7K tokens combined with the
follow-up slices.
Guards: tool_search_graph_toon_never_leaks_internal_fields (sentinel
fp/sp/bt never emitted, even when requested), rewritten
tool_search_graph_includes_node_properties pins default-lean/fields-
opt-in/json-escape-hatch; suite 5983 passed, smoke green.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- Add method_call_expression to perl_call_types in lang_specs.c so the
structural tier emits a method-call edge for the LSP bridge to refine
(parity with PHP member_call_expression). callee_name is the bare method
via the field-based extractor's `method` branch.
- Normalize the textual callee in cbm_pipeline_find_lsp_resolution to its
last "::"-separated segment so qualified static Pkg::sub() calls match the
resolved sub's dotted short-name (parity with PHP scoped_call_expression).
- Zero-edge guarantee preserved: untyped receivers still emit no edge.
Resolves DEVN-04 from plan 22-03. Verified on a Base/Derived/main fixture:
run_typed->{greet,describe}, run_static->helper, run_classcall->greet,
describe->greet (inherited), run_untyped->none.
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Follow-up to #787/#828, disclosed during #828's review: the same clobber
class also affects two call sites #828 deliberately left out of scope.
create_env_configures_for_file (pass_definitions.c) and resolve_file_throws
(pass_parallel.c) both resolved their edge source via a direct
cbm_gbuf_find_by_qn(enclosing_func_qn) lookup with no guard against the
lookup landing on the shared Folder/Project node for directory-module
languages (Java/Go) -- the exact #787 root cause, just unguarded at two
more sites. A class-level construct (no enclosing function) has its
enclosing_func_qn fall back to the MODULE QN, which for a directory-based
module collides with the package's Folder node, conflating every
same-package file's CONFIGURES/THROWS edges onto one arbitrary source.
- create_env_configures_for_file: added the same
cbm_pipeline_node_is_dir_container guard already used by the three
#828-fixed finders, falling back to the per-file File node on a
directory-container hit.
- resolve_file_throws: was missing the `rel` parameter its sibling
resolve_file_calls/usages/rw already carry, so it inlined a raw lookup
instead of calling the shared, already-guarded find_source_node. Added
`rel` and switched to find_source_node, matching its siblings exactly.
Regression guard: tests/repro/repro_issue842.c, reproduce-first per the
issue's request for "an #828-style multi-order red fixture":
- CONFIGURES reproduces on a small 5-file sequential fixture (Go, since
Java's System.getenv detection is separately broken -- extract_env_key_from_call
reads a "function" field Java's method_invocation node doesn't have; noted
in the test, out of scope here).
- THROWS only reproduces past MIN_FILES_FOR_PARALLEL=50 (the worker-merge
path) -- confirmed empirically that the small sequential fixture already
attributes correctly even on unfixed HEAD, mirroring #787's own two-faced
(sequential/parallel) bug. Padded with 60 filler classes, following
repro_issue787.c's build_parallel_fixture precedent.
RED on unfixed HEAD (both tests): 0 passed, 2 failed. GREEN with this fix:
2 passed, 0 failed. Full scripts/test.sh (5958 cases) passes clean with
this change; scripts/lint.sh's two pre-existing failures (pass_lsp_cross.h
parameter-name mismatch, extract_defs.c cppcheck AST parse) are unrelated
and present identically on unfixed main.
Fixes#842
Signed-off-by: Dhruvy0804 <dhruvgarg553@gmail.com>
Completes the #963 coverage taxonomy: alongside the best-effort FAILURE
signals (parse_partial, skipped), the report now lists what was
deliberately NOT indexed — a deterministic, by-design class, never mixed
into the failure surfaces.
- discover: file skips are classified by reason (gitignore / cbmignore /
skip-list / ignored-suffix / fast-pattern / size-cap) and collected via
the new cbm_discover_ex2 (per-file entries capped at 2000 with the
uncapped total reported — truncation is explicit, never silent; whole
excluded subtrees stay exhaustive as before). Unsupported-extension
files are deliberately NOT recorded: no grammar for an extension is
not an ignore decision, and listing every README/asset would drown
the signal
- pipeline: ignored files captured per run (full + incremental) and
persisted into the index_coverage table as not_indexed_file rows,
excluded dirs as not_indexed_dir; incremental runs rewrite the
by-design set fresh from discovery instead of carrying old rows
- store: the deleted-file prune exempts not_indexed_* (deliberately
unindexed paths never have hash rows); the missed shadow graph keeps
showing FAILURES only — the UI's report-an-edge-case callout can
never fire for gitignored paths, and a project whose only coverage
rows are by-design gets no miss graph at all
- mcp: index_repository response gains not_indexed_files {files, count,
truncated, note} (dirs stay under excluded); index_status's coverage
report gains a not_indexed section (dirs + files + note) and no longer
lumps by-design rows into skipped; tool descriptions updated — the
by-design class is presented as deliberate and deterministic (change
the ignore rules and re-index), with no best-effort hedging
- tests: e2e guard (gitignored file + dir reported with reasons on both
surfaces, failure surfaces stay clean, missed graph excludes them,
rows survive an incremental re-index, ignored constructs genuinely
absent from the graph); store unit test covers the prune exemption
and shadow-graph exclusion
Refs #963
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The UI security audit (security-ui.sh, layer A1) forbids hardcoded
external URLs in graph-ui source — the callout's GitHub issues link
tripped it on all pr-smoke legs. Follow the established pattern for
external targets (/api/repo-info deep-links): /api/ui-config now
carries upstream_issues_url and the callout consumes it, rendering the
issue button only when the backend provides an https URL. The protocol
check uses a regex literal on purpose: a bare protocol string in source
also aborts the audit's URL extraction.
Verified: security-ui.sh passes locally; /api/ui-config serves the URL;
the callout opens with the prefilled link in the browser; frontend
build + tests green.
Refs #963
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Per review: no dedicated coverage tool.
- index_status now carries the coverage report (parse_partial + skipped
lists from the separate index_coverage table, plus the best-effort
coverage_note); get_index_coverage is removed. Tool description
updated so agents learn the coverage semantics from index_status;
index_repository and the response note now point there
- the read hook resolves coverage through index_status (same envelope
fields, unchanged note wording); verified end-to-end against a real
index (flagged file -> note with exact ranges, clean file -> silence)
- get_code_snippet responses from a parse_partial file now carry a
correlated coverage_note ("line range(s) X could not be parsed ...
the source above is ground truth") — the result names its file, so
the warning is precisely anchored. Entirely-skipped files cannot
appear here (no nodes), so no uncorrelated noise is possible; clean
files carry no note (asserted both ways in the e2e test)
Refs #963
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Render the miss graph side by side with the code graph instead of
swapping layouts:
- /api/layout (code graph) now also attaches "missed_graph": {nodes,
edges, offset} — the shadow-project layout placed below the primary
cluster (same satellite pattern as linked_projects; -Y slot so
cross-repo satellites collide last). graph=missed stays the isolated
view for API users
- the UI paints the skeleton white and ghostly beside the galaxy,
auto-frames BOTH clusters on load, and navigates naturally: clicking
the skeleton flies the camera into it (file labels + detail panel),
clicking a code node flies back to the code side, and clicking empty
space while the skeleton has focus returns to the overview (the
galaxy can be entirely off-screen at that point)
- the sidebar toggle now shows/hides the skeleton (with a missed-file
count) rather than swapping the layout
Verified in the browser: overview composition, skeleton focus with
split.c labeled, and the empty-space return flight; ui/httpd suites and
frontend build + tests green.
Refs #963
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>