Reconcile the complete Atlas and Lens stream with current main while retaining both histories. Preserve architecture functionality, current process and watcher safeguards, test infrastructure, and dependency security updates. Add coverage for Git-environment isolation combined with child-local overrides.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolves the index_status tool description: main's watch state (#2167) and this branch's doc_links both stay.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A reference written in a doc comment is extracted with the definition it
documents (internal/cbm/doclink.c; the C# scanner doclink_cs.c) and
resolved per file in the pipeline (src/pipeline/doc_links.c,
doc_links_cs.c, doc_links_msbuild.c). A reference that names a definition
of the repository with certainty becomes a MENTIONS edge from the
documented definition (the File node for a file-level comment) with the
properties via, syntax, tier, line and count. Any other reference becomes
a row of the new doc_link_unresolved table with its reason: missing,
ambiguous, external, test_only_target, graph_gap, unparseable or
below_bar_tier. Nothing is guessed. index_status reports the layer in a
doc_links block.
C# binds a cref by the compiler's lookup rule: the first scope with the
name decides, a cref never binds an inherited member, System is outside
the repository. Assemblies come from project files, and MSBuild global
usings (<Using>, ImplicitUsings, Directory.Build.props/.targets, imports,
conditions) are evaluated from the project files' content as data the
indexer has already read; the layer opens no file. Two declarations of a
name in different assemblies make the reference ambiguous.
Each file's scope is stored line-free with its LSP surface ("dl"), so an
incremental run re-resolves the files a change can reach and falls back
to a full run for a change that can re-route references anywhere (a
project file, a global using, a deleted file).
The scanner and the resolver treat the repository as untrusted input:
bounded nesting, no input-driven recursion, work that grows linearly
(held by test seams, never by a clock), no silent limits, and a damaged
or foreign stored scope fails closed.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Since #1948 a non-git root can be polled by a file-tree scan when
watch_non_git is on. index_status still reported such a root as
strategy "none", which the enum documents as "not a git project: never
polled", so a root that is actively polled looked unwatched.
publish_status now maps a tree-polled root to the new
CBM_WATCHER_STRATEGY_TREE, rendered as "tree" (the value the
watcher.baseline log already uses). "none" keeps its meaning: a
registered non-git root that is never polled. CONFIGURATION.md lists
the four values.
Test: daemon_application_index_status_reports_tree_strategy_issue2167
fails without the production change and passes with it; suites
daemon_application, watcher and mcp: 507 passed, 4 platform skips.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The background watcher only watches the project an open MCP session is
rooted in, and drops the watch when the last session for that project
closes. A repository indexed with `cli index_repository` (or from a
session rooted elsewhere) is never watched, but README promised
"Auto-sync keeps it fresh after that" for every index_repository target
and the MCP instructions said "Indexes auto-refresh". Nothing in the
status output said which projects were actually watched, so a stale
CLI-indexed repo looked like a watcher bug.
What gets watched is unchanged. Instead:
- index_status gains a `watch` object: `watched`, plus `strategy`,
`poll_interval_ms` and `last_scan_at` when watched, or a `reason`
(not_session_project, cli_session, auto_watch_off, watcher_disabled,
not_registered, no_watcher) and a re-index hint when not. It does not
touch the #1561 `freshness` key.
- The watcher publishes per-project strategy, cadence and last completed
scan time through atomics and exposes them via
cbm_watcher_project_info(); the daemon answers index_status through a
session-scoped provider.
- README, docs/CONFIGURATION.md, docs/index.html, the index_status tool
description and the MCP instructions now state the real scope.
`watcher_enabled` already shows in `config list` since v0.11.0 (680bc72e).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Summary of the 28 hand-test findings, how they were checked on the final tree, and the known limits.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Review follow-up for K27:
- The opened Overview area or file reaches only Overview, and the hotspot
area only Hotspots. Endpoints, Entry points and Hotspots no longer draw
or report an area opened in Overview, so the chat context names nothing
that is not on screen.
- Plan or 3D in System structure and in Behavior is part of the entry and a
step, as on the shared map. The Behavior path to a destination is a step;
the operation on the call chain and the page of direct calls stay with the
entry without being steps, so Back and Forward return to them.
- The in-view "Back" buttons of System structure and Behavior are absorbed
into the one Back beside the subtabs.
- Back, Forward or a Recent jump while typing in the Routes filter first
records the typed text as a step instead of dropping it.
- replaceNavigation also merges with the step after it, so no two equal
steps end up next to each other.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K27: Architecture had scattered ways back with different
meanings (the Overview trail, a "Back" in System structure that walked the
focus, a "Back" in Behavior that walked the starts) and no way back from
"Show these N routes". Nothing went forward.
Architecture now keeps one place (subtab; opened area or file and Plan or
3D; route perspective and filter; System structure focus and expanded
groups; Behavior start, destination and where followed calls began) and
records it in the bounded model Galaxy uses (graph/navigation-history.ts),
with the same rules: at most 25 steps, consecutive duplicates merge, a new
navigation after Back drops the forward branch, no pushState, a Recent list
of 8 places, Alt+Left and Alt+Right only in the active workspace and not
while typing or while another surface takes the keys, a fresh history per
project.
Back, Forward and Recent sit beside the subtabs; their tooltips name the
target ("Back to Overview · django"). The old "Back" buttons in System
structure and Behavior stay in place and take the same step. "Whole system"
and the Overview trail stay as ordinary steps.
Architecture adds three refinements: typing in the route filter becomes one
step once it pauses; a choice the page makes itself (the suggested Behavior
start, a reset after reindexing) replaces the current step
(replaceNavigation), so Back never bounces on it; and the current step
keeps the newest details of its place (refreshNavigation), so a step whose
suggested start was rejected for a stale snapshot is still named after the
start the journey shows.
The views read their part of the place from the workspace and report
changes back; rendered on their own, as in their unit tests, they keep it
as their own state and offer no Back. A new tab row class avoids the
existing atlas-arch-bar meter. The concept doc now covers both workspaces.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Review of the hand-test fixes, K2 and K8. Restoring a history entry with
the same root (Back to another depth, or the minus button while a layer
loads) cleared the selection, so Selection details disappeared and the
chat lost its context, although commit 96fbec67 said it stays. Only the
"All graph" entry clears the selection now; another root still selects
itself once loaded.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand test K2: the concept now names the late project, cancel as Back and the empty-canvas click as history rules, as implemented.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand test K2 and K9. A click on a node made it the new root with no way
back, and a click just beside a node threw away scope, layers and path.
A bounded history (25 entries, consecutive duplicates merged, forward
branch dropped on a new navigation, no pushState) now records root,
depth, direction, edge types, open path and view. Back and Forward sit
in the scoped toolbar with tooltips naming the target, Alt+Left and
Alt+Right work outside fields, and a Recent list jumps to the last 8
roots. All graph is an entry of its own.
An empty-canvas click inside a scope now only clears the path
highlight. The scope is left only through All graph, Escape or Back.
The concept is in docs/development/pr-2068-galaxy-history.md; the
model in graph/navigation-history.ts is meant for K27 as well.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Add the measured browser acceptance (19 of 19 checks), the test runs on
the final tree, what was not run and why, and the remaining limits, with
ten downscaled screenshots of the fixed views beside the reproduction.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Reproduce every point from the 2 October review call in a real browser
against a Django 5.2.7 index and this repository as control, and record
the plan with measured causes, priorities and open decisions.
The capture script splits each interaction into numbered screenshots
with a visible cursor ring and records canvas identity, camera and the
prompt sent to the browser model, so resets, zoom direction and chat
evidence are measurable rather than described. Only 17 downscaled
images are committed; the full series stays local.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
A cache root whose resolved path runs through a directory owned by another
non-root account (typical on shared/HPC filesystems where ~/.cache is a
symlink into a shared tree) is refused by the private-directory walk. The
refusal itself is correct: the owner of an ancestor can rename or replace
everything below it after the walk, so it could redirect the cache. But
posix_directory_transition_secure() set no validation detail, so the caller
printed "ancestry component validation failed (errno 17)" -- a stale EEXIST
from mkdirat() that named neither the directory nor the reason.
- The refusal now names the directory, its owner uid and account name, the
caller's uid, and the remedy (a location with a self/root-owned ancestry,
e.g. via CBM_CACHE_DIR). The path is shortened from the left so the
remedy always fits the 384-byte detail buffer.
- On Linux the account name is read from /etc/passwd, not getpwuid(). The
release binary is statically linked against glibc, and getpwuid() there
loads the host's NSS modules at runtime, which can crash on a glibc
version mismatch (a real risk on sssd/LDAP cluster hosts). Accounts that
exist only in a directory service are shown by uid.
- New advice-only check: on Linux, a cache root on NFS, CIFS, SMB2, Lustre
or GPFS (statfs f_type) logs one daemon.cache_root_network_fs warning per
process. SQLite WAL is unreliable on network filesystems. Nothing is
refused.
- docs/CONFIGURATION.md gains a "Shared / HPC filesystems" section.
The ownership policy is unchanged, and there is still no override (same
stance as #1830).
Test seams: one seam reports a directory (matched by dev/ino) as
foreign-owned, and another supplies the statfs magic. The refusal, the
message and the once-per-process warning are therefore testable without
root and on every platform.
Refs #1717
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
README.md:132, README.md:633, README.md:761, and docs/CONFIGURATION.md:203
said one-shot `cli` commands "never start or connect to the coordination
daemon" / read "their own environment without starting the daemon". That
was already contradicted a few lines above it (README.md:126 lists
one-shot CLI commands among the processes that "share a crash-safe OS
admission barrier" with the daemon) and by the code:
main_local_cli_daemon_execute() (src/main.c) bootstraps a client
connection to the shared per-user coordination daemon via
main_client_bootstrap_with_upgrade(), spawning one (bootstrap.daemon_spawned)
when none is running, before dispatching the tool call.
Verified live: `cli --verbose list_projects` against an isolated
HOME/CBM_CACHE_DIR/CBM_RUNTIME_DIR printed "hint: this command started a
temporary CBM daemon", and its cbm-daemon.log showed
daemon.start -> daemon.runtime_stopping (reason=last_committed_client_disconnected)
-> daemon.lifetime_end, i.e. the daemon it spawned exited again once the
CLI command's own connection closed. bootstrap_production_spawn() passes
the calling process's `environ` straight to posix_spawn(), so a CLI
invocation that starts the daemon also seeds that daemon's captured
daemon-owned environment (CBM_DIAGNOSTICS, CBM_LOG_LEVEL, etc.) - it does
not read "its own environment" independently as the docs claimed.
Corrected all four passages to state the real behavior: CLI commands
connect to the shared daemon (starting one if none is running) for the
same admission barrier and per-project locks, hold a `cli_session` that
is never registered with the background watcher, and - if their own
connection is what started the daemon - that daemon exits again once the
command's connection closes and no other session is attached. No opt-out
env var or flag for an in-process/no-daemon CLI mode exists (grepped the
whole tree), so none is documented.
This is a pure documentation fix; no production behavior changed. Kept
clear of PR #2348 (fix/issue-2167), which separately rewrites the
watcher-scope paragraph at README.md:152/237/672 and
docs/CONFIGURATION.md:88 for issue #2167's auto-sync-scope correction -
none of the lines here overlap with that diff.
Refs #2183, #2167
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A project indexed from a directory that is not a git repository was never
refreshed after its first index: init_baseline classified it is_git=false
and poll_project/check_changes returned early for it forever, so the graph
silently went stale. The comment in init_baseline even claimed such folders
were "polled as a plain directory", which was never true.
Add an opt-in config key, watch_non_git (default false, read once at daemon
startup like watcher_enabled). When on, a non-git root is polled on the
existing adaptive cadence (cbm_watcher_poll_interval_ms) by a tree
signature: the indexer's own cbm_discover walk (same skip lists, .gitignore
stack and .cbmignore), sorted by path and folded over (relative path, size,
mtime). A changed signature takes the existing reindex path with the same
commit-after-success discipline as the #937 dirty signature. The baseline
leaves the signature "unknown", so the first poll reindexes once
(at-least-once, covering edits made while the daemon was down).
Runaway guard: only files discovery would index enter the signature, so
cbm's own .codebase-memory output (built-in skip) and the cache directory
(pruned by absolute path) can never make a finished reindex look like a
new change; a scratch folder under an unrelated dirty repository is polled
on its own files and never inherits the ancestor's dirty state.
cbm_file_info_t gains mtime_ns, filled from the stat discovery already
performs, so the signature needs no second stat per file and stays
UTF-8-correct on Windows (discovery's wide stat).
Docs: README and docs/CONFIGURATION.md now say non-git roots are not
watched by default and document watch_non_git. The staleness signal in
index_status is left to #1561's freshness object. Refs #1948.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
storage/framework/views/ holds Laravel's compiled Blade templates:
generated PHP with no source value. It was excluded only through
Laravel's stock .gitignore in that folder; when that file is missing,
every compiled view was indexed and reported as parse_partial by the
php grammar, burying real coverage gaps under generated noise.
Every built-in skip rule matched a directory BASENAME, and "views" is
far too common to skip everywhere. Add ALWAYS_SKIP_DIR_SUFFIXES, a
built-in rule list matched against the relative path suffix on a
'/'-component boundary, so storage/framework/views (at any depth, e.g.
backend/storage/framework/views) is skipped while resources/views,
app/views, app/framework/views, mystorage/framework/views and
storage/framework/views_old stay indexed. The rule is applied in
should_skip_directory next to the basename skip list, so it shares its
semantics: the subtree is reported as an excluded dir (#411) in the
index result and as not_indexed_dir "excluded subtree" in
check_index_coverage, and a .cbmignore negation
(!storage/framework/views/) can un-skip it like any non-safety-core
built-in skip (#500).
Test: discover::discover_skips_laravel_compiled_views_issue1735 (RED
before, green after, RED on revert) and
discover::discover_cbmignore_negates_laravel_compiled_views_issue1735.
Docs: docs/cbmignore.md precedence layer 1.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
On WSL2 the Windows drives under /mnt/<drive> are DrvFs (9p) mounts. With
the default automount options they report 0777 for every directory and
ignore chmod, so a CBM_CACHE_DIR there is refused by the private-directory
walk: the ancestor rule (not world-writable) fails first, and the leaf
0700 rule would fail right after. The refusal is correct -- the 0777 is
real -- but it gave WSL users no way forward.
The security gate is unchanged. When the refusing directory is on a 9p
filesystem under a WSL kernel (fstatfs magic 0x01021997 plus
/proc/sys/kernel/osrelease), the ancestor refusal and the leaf
"mode survived chmod" refusal now name the remedy: set
[automount] options = "metadata,umask=22,fmask=11" in /etc/wsl.conf and
run `wsl --shutdown`, or keep the cache on the Linux filesystem. The
ancestor message uses shorter rule text on DrvFs so the remedy fits
the 384-byte detail buffer. Detection compiles only on Linux; other
platforms and non-WSL 9p mounts keep the generic message.
A CBM_ENABLE_TEST_SEAMS switch forces the DrvFs verdict so the daemon_ipc
suite checks the remedy text (and its absence off DrvFs) deterministically.
docs/CONFIGURATION.md documents the WSL requirement.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The card headline already reads "17 MCP tools" but the body named
eight tools and then said "and 7 more" — 8 + 7 = 15, the old count.
TOOL_ANNOTATIONS[] in src/mcp/mcp.c has 17 entries, so the tail is 9.
Assisted-by: Claude Code / claude-opus-5
Machine: MacBook-Anton
Account: a@gmail
Operator: robot:pr-reply-daily
Signed-off-by: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com>
The TOOLS[] registry in src/mcp/mcp.c and the v0.11.0 --help output
list 17 tools, but README, the npm README, CONTRIBUTING and the Pages
site still say 15 (or 14), and the README tool table has no row for
get_file_outline, compare_graphs or check_index_coverage.
Refs #1297
Assisted-by: Claude Code / claude-opus-5
Machine: MacBook-Anton
Account: a@gmail
Operator: robot:git-s3-docs-fix-lane
Signed-off-by: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com>
Record the failed first pass, integrated repairs, actual Playwright flows,
source and research decisions, same-port daemon behavior and maintainer entry
points. Include before/after screenshots and the final map recheck after login.
Archive original test logs losslessly with SHA-256 records; retain failed runs
separately instead of presenting them as successful checks.
Disclose unresolved global C lint, parser callsite coordinates, heuristic
architecture roles, manual diff narrowing and outstanding platform validation.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Indexing accepts whatever a repository contains. A tree carrying a vendored
monorepo, a generated dump, or a runaway build directory is discovered in
full, and the first sign of trouble is a host under memory pressure with
nothing that attributes it to indexing.
Add two opt-in limits evaluated during discovery against accepted source
files only: index_max_files and index_max_source_mb. Both default to off, so
nothing changes until an operator sets one. Crossing a limit fails the whole
attempt with a structured resource_limit_exceeded result naming the resource,
the observed value and the limit; no partial graph is published, and an
existing serving index keeps answering.
Limits are read from the CLI-managed _config.db and are not MCP request
arguments. A supervised parent replaces any caller-supplied policy before
spawning its worker, and the worker rejects a missing or incomplete contract,
so the CLI, the daemon and the supervised worker all enforce the same
decision.
Both keys reach an operator through the existing config get/set/list/reset
with no new subcommand. `set` suppresses its own generic message for them
because the policy writer names the precise reason -- so that writer speaks
on every failure it can return, including a validated value whose write then
fails on a database that cannot be written. Exiting non-zero in silence is
not an acceptable answer from a CLI.
The two shell regressions that hand-roll the supervisor's worker argv carry
that contract as well. Without it the worker exits before either guard can
observe anything, and the guard would go quietly vacuous.
Signed-off-by: 刘冲 <mail@liuchong.dev>
Re-stacks the CodeAtlasWeb branch on the updated base (feat/atlas-r1 at
a44732b6, which merges main 7b0f553c). Ten conflicts, all in the seam
between the previous frontend and its replacement:
- README.md: main's surface count (45) with this branch's Atlas line.
- graph-ui/package-lock.json, graph-ui/src/App.tsx: CodeAtlasWeb's own
files stay; the previous frontend's edits to the same paths do not
apply.
- Seven previous-frontend files main had modified (GraphTab,
NodeDetailPanel and its test, StatsTab test, useProjects, rationale
and its test) stay deleted, and two tests main added for that
frontend (App.test.tsx, useProjects.test.tsx) are not brought in;
their subjects no longer exist in this tree.
Nothing under graph-ui changes relative to this branch; the C side
takes main's lean-output work and the branch's merge resolution.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The Atlas screenshot the README embeds still showed the previous
frontend. This is CodeAtlasWeb from a clean-tree embedded build: the
explorer, the Monaco reader on the sample fixture, the twin, the galaxy
and the menu row with the [p]rojects entry.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Chialisp (.clsp/.clib/.clinc) is the Chia smart-coin language. The only
public grammar, Quexington/tree-sitter-chialisp, cannot parse it: on the five
reference files from chia-blockchain@main it produced 3-34 ERROR nodes each.
Three defects account for all of them, and each desynchronised the rest of the
file rather than losing one node:
* comments were `/;.*\r\n/` -- CRLF-only, while real files are LF;
* `(include foo.clib)` was rejected because of the dot in the filename;
* `(defconstant NAME <expr>)` accepted only a primitive after the name.
So the grammar is written here instead, in tools/tree-sitter-chialisp/, and
files as first-party: its vendored LICENSE is the project's own, byte-identical
to the repository root, carrying no third-party copyright.
It is deliberately GENERIC -- source_file / list / symbol / string / number /
hex / dot / comment, modelling the clvm_tools reader rather than the Chialisp
form vocabulary. `mod`, `defun`, `defconstant` and `include` are ordinary head
symbols; which lists are definitions is decided in extract_defs.c. A dialect
that adds a form (cl-21 -> cl-23 -> cl-26 each added several) therefore needs
no regenerated parser. Acceptance: all five reference files parse with zero
ERROR and zero MISSING nodes; the three defects above are pinned as tests.
Extraction, and the four decisions in it:
* `Constant` is admitted to the cross-file name registry
(cbm_label_is_registry_symbol). Chialisp's condition codes live as
`(defconstant ...)` in a shared .clib and are referenced by name from every
puzzle; labelled but unregistered they are seeded nowhere, and roughly half
of a Chialisp graph would be present as nodes yet never the target of an
edge. All three registry-seeding sites route through that one predicate, so
they cannot diverge. No other language emits `Constant`, so the blast radius
is exactly Chialisp. Pinned by a two-file graph test that goes red when the
label is removed from the predicate.
* `export` / `namespace` are NOT definition heads. `(export foo)` names a
function already defined in the same file, so treating it as a def head
mints a second node for one symbol. They stay in the not-a-call filter.
* A parameter list is a `list` too, but it binds rather than invokes. Without
that gate `(defun check_conditions (HEIGHTLOCK conditions) ...)` mints a
phantom call to HEIGHTLOCK, and real Chialisp is mostly such definitions.
* `(include *standard-cl-26*)` is a dialect directive, not a file dependency;
recording it would invent a dependency on a file that does not exist.
Three helpers that the defs, calls and unified walks all need -- the quote-data
gate, comment-skipping named-child access, and the Chialisp def-head set -- live
in helpers.c rather than being copied per translation unit. They previously
existed as byte-identical duplicates that had already drifted: extract_defs.c
skipped comments when reading a def head while its mirror in extract_unified.c
did not, so a comment between a head and its name desynchronised defs from call
scope. Both now use the same accessor.
Ledger: 161 -> 162 grammars across the seven published surfaces, ABI-14 86 -> 87,
first-party 13 -> 14, CBM_LANG_COUNT 164 -> 165.
Distilled from PR #919 by darkverbito, whose extraction logic -- the CLVM
not-a-call vocabulary, the quoted-data gate, the filename-derived `mod` name and
the include/embed import handling -- is kept and is the substance of this change.
The grammar that PR vendored is not used.
Co-authored-by: darkverbito <8049985+darkverbito@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Seven surfaces disagreed with reality and with each other: README.md,
docs/index.html, docs/llms.txt and pkg/npm/README.md said 158, while
server.json, the chocolatey nuspec and scripts/package-release.sh said 159.
161 grammars are vendored.
21 individual claims corrected. The count is now gated by
tests/test_language_count_contract.sh, so the next language to land cannot
ship a stale figure.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Grok Build (xAI's grok CLI/TUI) already reached codebase-memory-mcp through
its Claude/Cursor compat layer, but a Grok-only user got nothing. Detect
$GROK_HOME (default ~/.grok) or a grok CLI and install:
- an owned [mcp_servers.codebase-memory-mcp] managed block in config.toml;
a same-name foreign table is left byte-identical because a second header
would make Grok reject the whole file, while the known owned shape written
by `grok mcp add` is adopted. No env forwarding: grok 1.0.5 passes the
full parent environment to stdio servers (verified), unlike Codex (#1562).
- an owned rules file under $GROK_HOME/rules/ (always scanned, all projects)
- the shared skill under $GROK_HOME/skills/
- Scout/Verify/Auditor agents with named-server mcpInheritance and the tier
allowlist spelled out as the server__tool ids Grok's search_tool/use_tool
dispatcher accepts (inheritance filters per server, never per tool)
- hooks withheld: Grok's passive events discard stdout and PreToolUse honors
only deny/rewrite decisions, so there is no context-output contract.
Verified against grok 1.0.5: inspect discovers the server, agents, skill and
rules; mcp doctor handshakes and discovers 15 tools. --clients=grok token,
README/npm/llms/landing-page matrices (43 -> 44 surfaces), and smoke coverage.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Follow-up to the PR #1105 review: the unit test proves the config predicate but
would still pass even if the daemon-side gate were removed. Add a process-level
regression that drives the real binary and proves the gate actually prevents the
watcher from being built, started or registered when disabled.
- tests/test_watcher_disabled.sh (wired into scripts/test.sh as Step 5e) drives
the binary against an isolated CBM_CACHE_DIR + tiny git fixture. With
watcher_enabled=false it asserts: watcher.disabled reason=config emitted,
watcher.start absent, no watcher.watch registration, auto_index still runs, and
manual index_repository is served. A watcher_enabled=true positive control
asserts watcher.start + watcher.watch appear, so the test fails if the gate is
removed (verified by neutering the gate in host_state_prepare and observing the
failure).
No assertion is decided by a timeout. The watcher lives in the daemon, which
logs to $CBM_CACHE_DIR/logs/cbm-daemon.log in a fixed order: watcher.disabled
(host_state_prepare) -> daemon.start -> the thread's own watcher.start -> the
join -> daemon.stop. Each run therefore retires its daemon and waits for that
CLOSED lifecycle before asserting, so absence is a fact rather than a race.
Every wait is a bounded poll on an asserted state following the soak harness's
wait_for_daemon_stop doctrine, and an exhausted budget fails loudly with the
reason instead of passing quietly; session stdin is held open through a FIFO
until the last JSON-RPC response lands, not for an interval.
Registration is asynchronous and unreachable until the project DB exists
(application_refresh_watch_locked requires application_regular_db_exists), so a
closed lifecycle alone would not carry the negative — the session could simply
have ended first. The disabled+auto_index run waits for that DB, i.e.
registration's own precondition; the enabled control waits for watcher.watch
itself, so its absence in the disabled runs is evidence rather than noise.
Honors CBM_TEST_BINARY like tests/test_worker_watchdog.sh, so the BUILD_DIR=
sanitizer and container legs test the binary they built.
- docs: CONFIGURATION.md + README.md document that watcher_enabled is read once
when the daemon starts — the daemon outlives individual MCP sessions, so
reconnecting a client is not enough; `daemon stop` retires it. This corrects
the earlier claim that auto_watch is also startup-read: auto_watch is consulted
per registration, and the two keys are now contrasted on read time as well as
on scope (session registration vs whether the subsystem starts at all).
No production code change in this commit: watcher.start and watcher.watch already
exist as production logs.
Signed-off-by: soren <nguiasoren@gmail.com>
Add a persistent `watcher_enabled` config key (default true). When false, the
daemon host skips building the background watcher entirely: the poll thread
never starts and no projects are registered. Manual index_repository is
unaffected, and the config wiring stays unconditional so auto_index /
auto_watch keep working even when the watcher is off.
This is distinct from auto_watch, which only gates per-session registration
while the watcher IS running. watcher_enabled is the master switch that stops
the subsystem from starting at all — the request in #335 (doc-heavy repos where
every save churns the graph baseline).
The key is read once, when the daemon builds its host state. Changing it takes
effect when the daemon next starts; docs/CONFIGURATION.md says so explicitly.
A disabled watcher is not a daemon-startup failure — the daemon still comes up
and still owns IPC, HTTP/UI, project locks, and manual indexing; only the
watcher and the in-memory store backing it go unbuilt.
- cli.h/cli.c: CBM_CONFIG_WATCHER_ENABLED key + cbm_config_watcher_enabled()
predicate (NULL-safe, default true), registered as a CONFIG_KEYS row next to
auto_watch. The table is no longer only a print list: since #1522/#1558 it
also backs key validation, so `config set/get/reset watcher_enabled` would be
rejected as an unknown key without that row, and the default recorded there
(true) is the one the table comment asks to keep in sync with the runtime
reader's fallback.
- daemon/host.c: gate watcher construction in host_state_prepare() and the
thread create in host_background_start(); log watcher.disabled when skipped.
Teardown needed no change — stop NULL-checks, join is gated on
watcher_started, and cbm_watcher_free is NULL-safe.
- test_cli.c: cli_config_watcher_enabled_default_and_persist pins the config
layer — default on, disable/re-enable spellings, persistence across reopen,
and a NULL-config default (a config-store failure must never silently
disable the watcher).
- docs: document watcher_enabled, and add the previously-undocumented auto_watch
row to CONFIGURATION.md so the watcher knobs are described together; README
example.
Closes#335.
Signed-off-by: soren <nguiasoren@gmail.com>
Add omp (Oh My Pi) as a STABLE, detected client surface with full
four-layer integration: MCP server registration, durable instructions,
skill, and three direct-MCP tiered subagent profiles (Scout/Verify/
Auditor). This matches Claude Code's integration depth — omp subagents
have direct MCP access (confirmed via ablation testing), unlike
parent-handoff-only clients (Pochi, Cursor, Rovo, Augment).
Schema (agent_clients.h):
New CBM_AGENT_CLIENT_OMP enum value, positioned before COUNT.
Profile (agent_clients.c):
- stability: CBM_AGENT_STABLE (auto-detected, auto-installed)
- capabilities: MCP | INSTRUCTIONS | SKILL | AGENT
No HOOK — omp's context event is fire-once advisory only; the
recommended upstream path is skill + agents + MCP, not TS hooks.
- detection_command: "omp" (specific enough per existing clients)
- marker: ~/.omp/agent (omp-managed config root)
- resolve_path: ~/.omp/agent/mcp.json
- json_client: true (uses standard mcpServers JSON edit path)
- agent_json_canonical: added to type:"stdio" branch alongside
GitLab Duo and Visual Studio (omp's schema expects this field)
Dialect (agent_profiles.h/.c):
New CBM_GRAPH_DIALECT_OMP, direct-capable (not in handoff-only list).
- tool prefix: "mcp__codebase_memory_mcp_" (single underscore,
hyphens→underscores — verified at runtime via omp device inventory)
- render: YAML frontmatter with tools (read/grep/glob + tier-specific
MCP tool allowlist), read-summarize: false (exact source verification
for evidence tiers — omp's read returns structural summaries by
default), autoloadSkills: [codebase-memory] (subagents start blank)
Install/uninstall (cli.c):
- install_omp_durable_context: writes AGENTS.md + skill +
3 tiered agent profiles to ~/.omp/agent/
- uninstall_omp_durable_context: removes managed block + skill +
tiered profiles
- legacy_omp_verify_agent_content: migration source for prior installs
- Wired into install_agent_client_registry and
uninstall_agent_client_registry loops
Design decisions (documented for reviewers):
1. No HOOK capability: omp's tool_call event can only block/pass (no
augmentation channel like Claude's additionalContext). A context-
event nudge was tested but is advisory-only and excluded from the
upstream path to keep the PR minimal.
2. read-summarize: false: omp's read tool returns structural summaries
by default; Tier 2/3 require exact snippets for material claims.
3. autoloadSkills: omp subagents start with no conversation history;
without autoload the skill decision matrix is never loaded.
4. Direct-capable (not handoff): ablation testing confirmed omp
subagents can call mcp__* tools directly. write→xd:// dispatch
does NOT work in subagents ("xd:// is not mounted in this session").
Test coverage:
- test_agent_clients.c: omp in stable_ids (19 entries), capabilities
array, json_schemas (type:stdio assertion), foreign_entries
- test_agent_profiles.c: omp in direct_dialects array, direct-capable
assertion, dedicated test verifying direct profile has
mcp__codebase_memory_mcp_ prefix + read-summarize + autoloadSkills,
and handoff profile excludes MCP tools
- test_cli.c: required_agents updated to 44, all 43→44 / 37→38 string
contracts updated across README/npm/index.html/llms.txt/main.c
Build: scripts/build.sh passes (exit 0).
Tests: scripts/test.sh passes (exit 0, all suites green).
Lint: scripts/lint.sh has one pre-existing error in pass_lsp_cross.h
(parameter name mismatch imp_keys/imp_vals vs imp_names/imp_qns),
unrelated to this change.
Signed-off-by: DavidHLP <lysf15520112973@163.com>
The daemon/CLI rendezvous directory is created under %LOCALAPPDATA% (Windows) or
/tmp -- /private/tmp on macOS -- and every ancestor of it must pass the
private-directory walk. That ancestry is not always acceptable, and when it is
not, EVERY invocation fails, `config list` included, so the settings surface
cannot be reached either:
codebase-memory-mcp: secure daemon endpoint could not be created
#1623 narrowed the Windows side of this by admitting AppContainer package and
capability SIDs on ancestors, and named the remainder explicitly: a live local
group, Authenticated Users inherited from a secondary volume root, and orphaned
unresolvable SIDs still refuse, and "those need CBM_RUNTIME_DIR or a separate
change". #1621 is the POSIX shape of the same dead end -- /private/tmp/cbm-daemon-<uid>
refused with no way to move it.
There was no way to move it in a shipped build. The only relocation hook,
CBM_TEST_DAEMON_RUNTIME_PARENT, is compiled out unless CBM_ENABLE_TEST_SEAMS is
defined, so a test build started while the shipped build did not; CBM_CACHE_DIR
is no help either, because it moves the cache and never the rendezvous.
CBM_RUNTIME_DIR names the parent directory the rendezvous is created under. It
does NOT relax the check: the directory it names goes through exactly the same
validation as the default -- ancestors owned by you or root, not world-writable,
no allow-ACL; the rendezvous directory itself still forced to owner-only -- and a
value that fails is refused rather than silently replaced by the default. The
operator only chooses an ancestry that passes. cbm_safe_getenv never truncates,
so no half of an over-long value can become a runtime parent.
The override is resolved in cbm_daemon_bootstrap_endpoint_new(), the one function
every product endpoint goes through: the daemon, the MCP client, the local CLI,
the index worker, and the install/update/uninstall activation path in cli.c. No
call site can silently keep the default, and the detached daemon inherits the
value with the rest of its environment. An explicit parent still wins, so the
compile-time test seam and the lifecycle guards' isolated namespace behave
exactly as before.
Approach and variable name from #1576 by Leonardo trindade miranda, resolved one
layer lower so the activation path is covered too.
Refs #1574
Refs #1621
Co-Authored-By: Leonardo trindade miranda <tmonestudio@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Release evidence from run 31744302624 shows the tolerated Microsoft `!ml`
verdict is close to a coin flip per byte image rather than a property of the
code. Across the eight targets the stripped and unstripped candidates of the
SAME linker output disagreed on four, and in both directions:
linux-amd64 stripped microsoft-ml unstripped clean
darwin-arm64 stripped clean unstripped microsoft-ml
linux-arm64 stripped clean unstripped microsoft-ml
If the classifier were keying on something intrinsic to our code the siblings
would agree; they do not. So each variant is close to an independent draw, and
5 of 16 candidates drew the flag.
Two draws is not always enough. On that run linux-amd64-portable came back
microsoft-ml on BOTH candidates, leaving no clean binary to ship for that
target. A third independent draw at a ~31% observed per-candidate hit rate takes
the both/all-flagged case from roughly 1-in-10 per target to roughly 1-in-30.
The third candidate is `--strip-debug` (Apple: `-S`): debug information removed,
symbol table kept. Behaviourally identical to the other two — same linker
output, only metadata differs — but a distinct byte image, which is all
VirusTotal needs to scan it as its own file. Verified on the real v0.10.4
candidates: linux-amd64 gives three distinct hashes (294,634,656 /
294,623,208 / 293,746,096 bytes) and darwin-arm64 likewise, with the ad-hoc
signature verifying after strip.
Selection is unchanged in spirit and now ordered: smallest artifact first
(stripped, debug-stripped, unstripped), take the first CLEAN one, and only if
every candidate drew the tolerated verdict ship the smallest flagged one. A
hard verdict on any candidate still blocks the release before selection.
Cost is 24 objects per release instead of 16.
Derivation enforces that all three hashes differ — identical candidates would be
one draw wearing three hats, and the selector would believe it had alternatives
it does not have. Public claims in README, SECURITY.md and docs/index.html
updated from "both stripped and unstripped" to the three candidates.
All three release contract tests pass, including the native derivation test
which exercises the real strip and codesign path on this host.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>