Commit Graph
113 Commits
Author SHA1 Message Date
Martin Vogel 5d10cb7af7 fix: make Lens settings and impact history portable
Normalize Windows cache paths when reporting runtime settings and resolve
Git executables before spawning history queries. Keep executable lookup
results per thread and cover the ownership contract deterministically.

Support Git 2.43 while denying history-query network transports, and close
the activity database on daemon startup failure so Windows can clean up.

Validation: 463 native macOS tests, 421 Windows tests, 44 MSan UI tests,
and focused canonical lint passed. Production-only revert checks confirm
the repaired failures.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 23:14:24 +02:00
Martin Vogel a19d7a45fb Use memory core for Lens response ownership
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 20:16:34 +02:00
Martin Vogel cf41bdc6d6 Secure Lens history reads and verify shipped browser dependencies
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 18:12:30 +02:00
Martin Vogel 08e0474a36 Harden CBM Lens integration against current main
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 17:35:04 +02:00
Martin Vogel 0392068d49 Merge main into CBM Lens Beta
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>
2026-10-11 16:57:48 +02:00
Martin Vogel 1b581f1ba3 Merge pull request #2346 from DeusData/feat/cross-repo-status
feat(cross-repo): report run status and skipped legacy stores (#2133)
2026-10-08 01:31:14 +02:00
Martin Vogel 7077695f29 fix(ui): size the index-status body for the roots the table holds
GET /api/index-status rendered every job into a fixed 2 KB stack buffer
with a clamp. Now that a job carries its resolved root in a 4 KB field,
one long root alone exceeded that buffer and the listing came back as a
cut JSON body with status 200.

Build the body in a heap buffer sized from the table: per slot the entry
framing plus the root and the error message at cbm_json_escape's widest
expansion. The two strings are escaped straight into the body, the
append helpers keep their clamp as a second guard, and the buffer goes
through the memory core like the other variable-size responses.

The 202 reply of POST /api/index printed the stored root unescaped, so a
quote or backslash in the path ended its JSON string early. It is now
escaped the way the listing escapes it.

Tests: with every slot holding a root as long as the platform resolves
(just under the 4 KB field on Linux, under PATH_MAX on macOS), the
listing parses as JSON and carries each root whole, first with one job
and then with the table full; a root containing a quote comes back
parseable from the 202 reply, naming the resolved path.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-02 02:23:12 +02:00
Martin Vogel 46a8f57f18 fix(ui): index job carries the resolved root, not the request spelling
POST /api/index resolves the root_path of the request into a 4 KB buffer,
but the index job kept the original request string in a 1 KB field. The
job, the worker log lines, the 202 reply and /api/index-status therefore
reported the request spelling instead of the resolved root, and a path
past 1023 bytes reached the daemon cut off.

Store the resolved path in the job, give the field the capacity of the
buffer it is copied from, and answer 400 for a path that would not fit
instead of storing a shortened one. The status listing's escape buffer
follows the new field size.

The job's downstream, application_background_index, re-resolves what it
receives, so a resolved input is a fixed point there and the daemon keys
the job by the same string the route worked with.

Tests drive the live route: a root outside a configured allowed root is
answered 403 and no job starts; a root spelled through a ".." segment is
stored and forwarded in resolved form; a root past 1 KB is either refused
or forwarded whole. The last case reaches the accepted branch on Linux
(PATH_MAX 4096); on macOS the path cannot resolve and the refusal branch
is asserted.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-02 00:42:05 +02:00
Martin Vogel 50a6a67b89 Streamline local agent controls and preserve proactive chat
Separate agent configuration from chat visibility, keep chat compact, and
resume selection explanations after manual questions. Restore project
conversations locally for 24 hours with bounded storage and clear history.

Move daemon configuration into System and report persisted index metadata
and live watcher registration alongside its configuration control.

Validated with 303 focused frontend tests, TypeScript/Vite production build,
160 native watcher/HTTP tests, regression checks, and local browser checks.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 20:24:06 +02:00
Martin Vogel e4f9735dea feat(cross-repo): report run status and skipped legacy stores (#2133)
Two follow-ups to the #2133 wildcard fix, both about what cross-repo
intelligence tells the caller.

1. "Never run" vs "ran, no links". An empty CROSS_* set used to mean
   either one, and no output surface could tell them apart. Every run now
   records a marker in the SOURCE store's store_meta
   (key cross_repo_last_run:<project>, same per-project key pattern as
   coverage_shadow_fp:<project>) once the previous CROSS_* generation has
   been cleaned: last_run_at, outcome (complete|cancelled|failed),
   targets, projects_scanned, total_cross_edges, skipped_projects.
   index_status and the UI layout (next to linked_projects) now carry a
   `cross_repo` object: {"status":"never_run"} or {"status":"ran", ...}.
   The marker lives beside the edges it describes, so a reindex that
   rebuilds the store drops both together. No new database files and no
   new freshness key.

   New store API cbm_store_meta_get/put. put seeds a missing store_meta
   through the generation path, because creating the table without
   db_uid/mutation_gen would make cbm_store_generation() report malformed
   metadata.

2. ["*"] skips a pre-#768 store (since #2343) but only logged it. The
   result now names each one, and the MCP response lists them:
   skipped_projects: [{name, reason:"pre_768_schema",
   hint:"reindex this project"}] (only emitted when non-empty). The
   cross_repo.project_unusable log line is unchanged.

cr_project_exists() is split into cr_project_state() (usable / missing /
legacy) so that the enumerator can tell a legacy store apart from a
foreign .db file. New allocations go through mem_core.

Refs #2133
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 22:40:38 +02:00
Martin Vogel cb251ed472 Merge pull request #2357 from DeusData/fix/issue-2144
feat(mcp): opt-in async index_repository with status polling (#2144)
2026-09-30 22:20:46 +02:00
Martin Vogel ea931e7647 feat(ui): refine graph exploration and add local agent configuration
Add focused galaxy tracing, edge filters, organic cluster layouts and cached
graph exploration with compact architecture and impact summaries.

Make the browser-local agent explain the current selection proactively,
and add a Config panel with persisted daemon overrides, shared browser
preferences and a reference for external configuration inputs.

Validation: 182 focused frontend tests and TypeScript passed. Reused the
recorded production build and 485 passing native checks on unchanged
source. The pre-existing capped-layout loading-note test failure remains
documented separately; the full local CI ladder was waived.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 18:39:48 +02:00
Martin Vogel 3e67838b05 Merge branch 'main' into fix/windows-unicode-repo-paths
Resolve the RUN_TEST list conflict in tests/test_daemon_application.c by keeping both sides' new tests.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-25 23:15:24 +02:00
Martin Vogel 28b41c35b7 feat(mcp): opt-in async index_repository with status polling (#2144)
index_repository blocks until the whole index completes. MCP clients with
a per-call deadline (Copilot for IntelliJ) give up on a large repository;
their cancel drops the daemon job's last subscriber, the daemon cancels
the worker, and every retry starts over, so the index never finishes.

Add an opt-in async mode and a status query to the same tool; the default
synchronous behaviour is unchanged:

- async: true starts the project's index job in the daemon, or joins the
  one already running for it, and returns at once with its state. The
  application holds one subscriber reference for an async job until its
  terminal publish, so the request returning, the client cancelling or
  the starting session closing no longer cancels it. Only daemon shutdown
  (or the final session of a non-permanent generation) ends it. Async
  requests never queue behind the physical job limit; a full daemon
  answers busy. async/status are stripped from the worker args, so an
  async request coalesces with an identical synchronous one.
- status: true reports the running or last job for the project
  (queued/running/cancelling/succeeded/failed/cancelled, started_at,
  finished_at, error summary) from a small per-project record kept in the
  daemon's job registry after the job itself is reaped. An unknown
  project is an error; a project indexed before this daemon generation
  reports idle. No freshness key is added: index_status keeps the #1561
  freshness object.
- async + status together, non-boolean values, and async with
  cross-repo-intelligence are refused. In-process servers (index worker,
  embedders) refuse both modes: nothing there outlives the call.
- On a temporary (non-permanent) daemon, async is refused when the
  request arrives on the one-shot `cli` tool channel and that session is
  the daemon's only live session: the daemon would stop and cancel the
  job the moment the command exits. The error points at `daemon start`
  (or an open MCP session). A long-lived MCP session stays a valid host
  even when it is the only one, which is the IDE case this exists for;
  status stays allowed everywhere. The daemon lifecycle is unchanged.
- New allocations go through the memory core (cbm_alloc/cbm_calloc/
  cbm_mem_strdup/cbm_free); blocks returned by non-core APIs are released
  with safe_free, so no file's raw allocator count grows.
- A cut-short synchronous call now offers the async alternative: in the
  cancellation text the daemon or frontend delivers (including the
  -32800 reply to a cancelled index_repository), and, because a timed-out
  client never reads that reply, as a notice on the next index_repository
  or status call for the project.
- The tool description and schema, the CLI help and the README explain
  the flags, the deadline problem and the polling pattern.

No server-side timeout or budget is introduced.

Tests (deterministic, held fake worker, bounded observation waits):
async returns before completion and status observes running then
succeeded; an async job survives its session closing while a sync job in
the same shape is still cancelled; validation errors; the cancellation
reply and the next-call notice carry the advice; a lone one-shot client
of a temporary daemon is refused async while status and an MCP session
on the same daemon are allowed; in-process refusal; schema; notice
helper.

Refs #2031

Fixes #2144

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-25 23:03:56 +02:00
Bernhard Jackiewicz 4372d9fada feat(ui): integrate repository evidence and change analysis on one daemon
Add source-backed repository navigation, selection context and a shared change
analysis workspace for symbols, working-tree changes and Git comparisons.
Persist agent events and project logs through the existing daemon with SQLite
schema 4, bounded polling, retention and restart-safe deduplication.

Repair compact RPC decoding, path-search races, source pagination and identity,
large map responses, live error propagation and explicit local diagnosis flows.
Include the real-repository browser runners and focused regression coverage.

Validation: 2473 frontend tests, 203 acceptance checks, production build,
style/promise gates, 5 reference-boundary checks and 7 local hook tests pass.
The complete repository-map browser flow also passes after daemon restart.
Global C lint and cross-platform release validation remain open and are
recorded in the accompanying handoff report.

Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
2026-09-10 00:08:19 +02:00
Martin Vogel cc6743e9f3 Merge pull request #2060 from TheStranjer/fix/follow-own-symlinked-agent-roots
fix(cli): follow agent-root symlinks owned by the invoking user
2026-09-09 23:37:45 +02:00
Martin Vogel 80f4f41f9b Merge CodeAtlas logging with the exploration frontend
Retain the existing frontend diagnostics and unreadable-file reporting
alongside the selected-code inspector, graph navigation, and local chat.
Keep both process telemetry and log-tail models, and preserve the HTTP
logging tests alongside the browser model download policy checks.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-09 19:59:56 +02:00
Martin Vogel a47e0dadb3 feat(ui): graph exploration workspaces with local browser chat
Add Explore, Galaxy, Architecture, Agents, and System workspaces with a
persistent, resizable local chat dock. Attach exact source selections and
graph context, render Markdown, and offer pinned opt-in browser models.

Keep the galaxy visible below chat and focused on the active file or code
selection. Restore hierarchy root selection, resolve projection identities
by symbol, and retain loaded search matches after an empty index reply.

Expose process measurement units, embed browser runtime MJS/WASM assets,
and constrain binary-audit exceptions to reviewed dependency strings.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-09 00:37:21 +02:00
TheStranjer 8622d6a0ed fix(cli): make following user-owned symlinks an opt-in per call site
Review of the previous head found the global rule unsound for one caller:
cbm_mkdir_p also creates <repo_path>/.codebase-memory inside the checkout
being indexed, and git creates symlinks owned by whoever cloned, so a
repository shipping `.codebase-memory -> ~/.ssh` would have been followed
by the artifact writer. "User-owned" says nothing about "user-intended"
once the path root is a checkout.

cbm_mkdir_p keeps the strict walk (root-owned links only). The new
cbm_mkdir_p_ex(path, mode, policy) with CBM_MKDIR_FOLLOW_OWNED also
follows a symlink owned by the invoking user, and only callers whose
paths are rooted in the user's own configuration opt in: the installer's
agent roots, skills, instruction files and hook directories, the
JSON-like config editor's parent creation, the cache root, <cache>/logs,
the UI config directory and the DB parent. The artifact directory inside
the checkout and the ~/.local/bin creations stay strict; the activation
transaction re-walks the latter with its own no-follow policy anyway.

What a follow lands on is now checked too: the opened target must be a
directory owned by root or the invoking user and not world-writable
unless sticky, so a link swapped between the fstatat and the openat
cannot steer the walk into a directory an attacker controls or one where
anyone can pre-plant entries. This applies to root-owned links as well;
macOS /private/tmp is root-owned and sticky and still passes. Refusal
restores the first openat's errno.

Tests: the plain walk refuses an own link that the opted-in walk
follows; targets that are world-writable without the sticky bit are
refused; as root, a foreign-owned target and a user-owned link are both
refused, each followed by restoring ownership and walking again; the
identical `.codebase-memory -> <user-owned dir>` link is refused by the
plain walk and followed once opted in; cbm_artifact_export refuses a
checked-in artifact-dir link and leaves its target empty; the installer
test gains the euid-0 leg with home, link and target owned by a non-root
uid. Under euid 0 the tests demote their links to a non-root owner, since
root-owned links are trusted by both walks.

Refs #1722

Signed-off-by: TheStranjer <791672+TheStranjer@users.noreply.github.com>
2026-09-07 23:17:50 -06:00
Martin Vogel 9dc882a82f feat(ui): frontend log file, written by the page and served by the server
The browser console was the only place Atlas wrote to, and it dies with
the tab: a reader who saw "get_code_snippet returned no source" had
nothing to hand over, and an agent looking into it had nothing to read.
The page now keeps a second copy of everything it logs, on the server,
next to the daemon's own records.

Server (src/ui/http_server.c):

- POST /api/ui-log takes {page, session, entries[]} and appends one JSON
  line per entry to <cache_dir>/logs/ui.log (received, page, session,
  seq, ts, level, source, message, detail, stack, url, line, col).
  Bounds: 64 KiB per post, 100 entries per post, 4096 characters per
  text field cut on a UTF-8 boundary and marked, the level normalised to
  a known word, every string escaped again on the way out. The file
  rotates once to ui.log.1 at CBM_UI_LOG_ROTATE_BYTES (default 5 MiB).
  Warnings and errors are mirrored into the ring GET /api/logs serves,
  so both sides of one failure sit in one list; the chatter stays in
  the file. The reply names the file and says whether it could be
  written.
- GET /api/ui-log?lines=N tails the file (default 200, at most 1000,
  from the last 256 KiB) and names the file and its rotated
  predecessor; `partial` says when the lines do not start at the top.
- cbm_ui_log_file_path() is the one place the path is built.

Frontend (graph-ui):

- app/ui-log.ts: a bounded buffer (200 entries, oldest dropped first
  and the loss named at the head of the next batch), batched posts
  (25 entries or 1.5 s), backoff on a refused post, never writes to the
  console itself. app/ui-log-transport.ts posts with fetch, and with a
  beacon when the page is going away.
- app/ui-log-install.ts wraps the five console methods (they still
  print; the copy is taken after), listens to window error and
  unhandledrejection, and flushes on pagehide. Installed first in
  main.tsx, so nothing can fail before the copy exists.
- provider/error-observer.ts is the seam for failures the panels catch
  and show as a sentence: the /rpc transport and the /api client
  announce every failed call there before throwing, with a level that
  tells a tool the server does not offer (info) from a tool that said
  no (warn) from a broken or unreachable server (error). The reader
  announces a file the index does not know.
- The projects panel shows the tail under "This server" as sentences,
  names the file to attach to a report, and reloads it with the rest.
- README (both) and INVENTAR.md name the file and the routes.

Tests: three httpd cases (JSON lines and tail, refused bodies and
dropped entries, rotation); vitest for the buffer, the wiring in jsdom,
the transport, the seam through both clients, the API method, the model
and the panel. Verified live against a real project: the reader's
failure appears in the file and in the panel within two seconds.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-08 01:26:33 +02:00
Martin Vogel 7cd5c93136 Merge feat/atlas-r1 (current with main) into feat/codeatlas-web
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>
2026-09-06 03:23:48 +02:00
Martin Vogel da7235ab39 Merge origin/main into feat/atlas-r1
Brings the Atlas branch up to current main (7b0f553c). Eight files
conflicted; how each was settled:

- src/mcp/mcp.c: the real ingest_traces description stays, in main's tool
  row shape (main dropped the title column); index_repository keeps the
  truthful abort branches (aborted_previous_preserved, persist_failed)
  and gains main's format_migration flag in the success branch.
- src/ui/http_server.c and tests/test_httpd.c: the branch's twelve-tool
  read allowlist for /rpc (#1663) over main's three, with main's %zu
  and initialised counter.
- README.md: main's surface count (45), the branch's Atlas line.
- graph-ui (the previous frontend, replaced by the stacked #2068 but
  kept building and green here): the branch's node-selection wiring
  (onSelectNode) is kept consistently, so main's duplicate
  handleSelectPath and its 3-argument call go; main's version chip is
  added to the branch's header; useProjects keeps the branch's
  render-first flow on top of main's paginated JSON helpers
  (fetchAllProjects, fetchFullSchema), which the lean output contract
  requires, and a project whose size the server did not send now gets
  its schema too. main's GraphTab.selection.test.tsx, written against
  main's GraphTab, is dropped in favour of the branch's own #1197
  coverage in GraphTab.regions.test.tsx ("reports selected nodes to
  the route for deep links"); main's useProjects pagination test waits
  for the lazy schema enrichment instead of asserting before it runs.

Verified: mcp, httpd and ui suites 414 passed; the old frontend builds
and its 149 tests pass.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-06 03:03:19 +02:00
Martin Vogel 62af5b87ee feat(ui): wire CodeAtlasWeb into the build, the dev-proxy contract and the CSP
Maintainer glue for the imported frontend; no product behaviour of the
UI itself changes.

- Makefile.cbm: `npm ci` runs with PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1.
  The UI's dev dependencies include playwright for its local browser
  proofs, and a plain install would download browsers that no build or
  gate launches.
- graph-ui/vite.config.ts: the dev server listens on 5173 with the two
  literal loopback origins, which is what
  tests/test_ui_dev_proxy_security.sh pins.
- graph-ui/index.html: lang="en"; the catalog is English.
- CSP: the directive string moves to http_server.h as CBM_UI_CSP_VALUE
  so a test can pin it, and connect-src now admits the two loopback
  services the UI may talk to when the reader starts them, the
  local-model sidecar on 127.0.0.1:4141 and the agent-event bridge on
  127.0.0.1:4142. Under connect-src 'self' both fetches were silently
  blocked in the embedded build. Every other host stays forbidden; a
  new httpd test asserts that the only hosts in the policy are those
  two loopback ports.
- scripts/security-ui.sh: two false positives that bit on the imported
  source. The analytics pattern matched the English word "plausible"
  (now plausible.io), and `\s` inside an ERE bracket is a literal
  backslash and 's', which cut every localhost URL at its first 's' and
  then failed the allowlist (now [:space:]).

Verified: httpd suite 64 passed; dev-proxy contract and UI security
audit pass; graph-ui builds and its 2024 vitest tests pass.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 20:15:13 +02:00
Martin Vogel 3909e5a0e1 feat(mcp): make tool output lean and lossless
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Daichi Matsumoto 2ea73059f8 fix(windows): support Unicode repository paths
Signed-off-by: Daichi Matsumoto <222130627+z2250389@users.noreply.github.com>
2026-09-04 20:36:38 +09:00
Martin Vogel 0e9ed75991 Merge pull request #2016 from rarepops/feat/ui-server-version
feat(ui): show serving binary version (#1820)
2026-09-03 08:13:57 +02:00
Rares Popa e0785b353a feat(ui): show serving binary version
Expose the compiled server version through the existing UI config endpoint and render it beside the product name. Keep older or unavailable config responses compatible by hiding the label, with focused frontend and native endpoint coverage.

Fixes #1820

Signed-off-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
2026-09-02 20:50:31 +02:00
Martin Vogel 7fedaba331 Merge branch 'main' into feature/freebsd-pkg-managed-detection 2026-09-02 11:45:45 +02:00
Martin Vogel 2cc80f20bb Merge pull request #1204 from DavidMusk93/deleted-self-exec-fix
fix(mcp): survive atomic binary replacement
2026-08-28 12:46:43 +02:00
Martin Vogelandaoright 7fe2f87ba7 chore(headers): sync stale decl names, name genuine buffer sizes
Distilled from #1298 by @aoright — the subset that survived review and
main's drift, per the 2026-08-17 ruling.

- pass_lsp_cross.h: the header still documented and declared
  all_defs/imp_keys/imp_vals while the implementation renamed them to
  defs/imp_names/imp_qns — doc-only drift, now synced; plus the braced
  guard in cbm_pxc_registry_for_lang.
- runtime.h: semantic_version[12] gains a named size
  (CBM_DAEMON_SEMVER_SIZE), the "defensible: a real size" case from the
  review.
- httpd.h: request-field buffers take the CBM_SZ_* names for their actual
  sizes; the constants.h include sits in the top include block rather than
  mid-file.

Deliberately NOT carried, per the same ruling: the store.c calloc hunk
(the claimed uninitialized read does not exist), the generic-constant
renames (semantic laundering), the watcher.c split (its target function
gained a semantic fix on 2026-08-20 and the refactor must be re-derived),
and the lsp_resolve.h extractions (their target loop was rebuilt on main).

Co-authored-by: aoright <102943475+aoright@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-28 10:29:24 +02:00
Martin Vogelandsunmingqiang 5b96b06754 fix(mcp): resolve deleted self-image per platform ruling
Maintainer follow-up on top of the contributor's rebased commit,
implementing the Half-B strategy decision from the 2026-08-21 review:

- Linux: when the self-resolved path fails the executable check after an
  atomic rename-over, hand back the /proc/self/exe magic link. The link
  still executes the in-memory OLD build, which is the only spawn the
  worker's build-fingerprint gate accepts. Deliberately NOT the saved
  launch path: after a replacement that path holds a DIFFERENT build, so
  preferring it swaps an ENOENT for a fingerprint refusal.
- macOS: no magic link exists; fail closed. The supervisor logs
  index.supervisor.no_self_path and indexing resumes on daemon restart
  instead of spawning a missing binary. A rename-over that leaves a VALID
  binary still resolves (and the fingerprint gate refuses downstream, with
  its own coverage).
- The prefer-saved-launch-path branch from the original head is dropped
  per the same ruling.

Also fixes both Half-A review items:
- Aliasing in cbm_http_server_set_binary_path: `path` may alias
  g_binary_path on a repeated call. The overlapping snprintf cannot
  actually fire today — resolve's leading out[0]='\0' zeroes the shared
  buffer first — but that same reset means the call silently DISCARDS the
  stored path it was asked to re-resolve, and the in-place write is one
  reset-reorder away from real UB. Resolve into a local first: correct
  input, no aliasing trap.
- Coverage asymmetry: the probe and driver ran only on Linux while the
  production edit covered all of POSIX. Both now run on macOS too, and the
  Linux probe first proves the "(deleted)" state via readlink so its
  assertions cannot pass vacuously on an intact image.

Revert-check: with main's http_server.c restored, the driver fails at
WEXITSTATUS 44 (resolver returned a dead path) on macOS; with the guard it
passes 63/63.

Co-authored-by: sunmingqiang <sunmingqiang@bytedance.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-28 02:39:21 +02:00
sunmingqiang 11bbf0d847 fix(mcp): survive atomic binary replacement
Prefer the saved executable launch path when it still points to an
executable, and reject a deleted /proc/self/exe path before spawning
index workers.

Add a Linux integration regression that forks, atomically replaces the
running test binary on disk, and verifies that subprocess resolution
uses the saved launch path instead of the now-deleted self path.

Signed-off-by: David Musk <davidmusk93@users.noreply.github.com>
Signed-off-by: sunmingqiang <sunmingqiang@bytedance.com>
2026-08-28 02:15:19 +02:00
Martin Vogel c6105c5d06 feat(ui): who can help — authorship as evidence, never a scoreboard
/api/who answers the most-sought information type in the developer-
questions literature for one file, from the churn scan the dashboard
already pays for: people with recorded history here, each with their
evidence — commits in this file, repo-wide breadth, and last-touched
where the retained newest commits prove it (absent otherwise, stated
honestly). Deliberate constraints hold: no cross-person performance
framing, no bare percentages, capped list with the distinct-author
denominator.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-28 00:39:57 +02:00
Martin Vogel baf0df8e91 feat(mcp): ingest_traces stores observed runtime calls for real
The stub counted the traces array, answered 'accepted', and discarded
everything. Now: traces are call paths of qualified names (adjacent =
caller→callee; bare {caller, callee} pairs still accepted), stored in
observed_calls/observed_paths — SEPARATE from the graph tables and
keyed by qualified name, because node ids are reassigned on every
reindex and id-keyed observation would silently rot. Counts accumulate
per run label; whole runs age out together past the 200k-pair cap
(oldest first, deterministic under the one-second timestamp resolution
via rowid tiebreaks — eviction is never a timing lottery). The response
is honest about resolution: only pairs whose both endpoints are indexed
get stored, and up to ten unresolved names come back so producers can
fix their naming instead of silently losing data.

Atlas joins observation at read time: trace and flow-detail hops that
actually ran gain observed {count, label, last_seen}; absence adds
nothing — an unobserved hop is merely possible, never 'dead'.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 21:31:49 +02:00
Martin Vogel 3ce8338276 style: clang-format the impact and symbol-history additions
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 20:11:03 +02:00
Martin Vogel b808eec514 fix(ui): dispatch /api/symbol-history before the /api/symbol* wildcard
The wildcard swallowed the longer path, answering symbol-history
requests with the symbol bundle's 'missing id or qn' error. Caught
live; route order now states the constraint.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 18:58:55 +02:00
Martin Vogel 11bdc08147 feat(ui): why is this here — per-symbol git history with linked evidence
The most-deferred developer question in the literature gets its evidence
surface: /api/symbol-history runs a bounded git log -L over the symbol's
line range (follows renames, 12 commits, on demand — one subprocess,
never automatic), and file history now carries the forge base URL
(git@/ssh remotes normalized to https). The symbol page grows a 'Why is
this here?' card: trace button, commit list with hashes and #123 refs
linked to the forge, honest empty/capped states, and a footnote that
titles are not fetched. The existing recent-commits list linkifies
through the same component. F7's gap narrows accordingly.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 18:51:39 +02:00
Martin Vogel 617a4b1495 feat(ui): change impact — who can reach this symbol, and what to retest
/api/impact answers the strongest-demand developer question in the
information-needs literature: if I change this, what breaks and what
must I retest. A reverse CALLS adjacency joins the flow cache (built in
the same edge pass); a distance-labelled BFS from any symbol reports
reachers per hop, a region rollup, and — separately — the test
functions that reach the symbol, nearest first: the tests to run.
Honest caps (depth 10, 50k visits, both flagged) and a stated basis:
static CALLS only, so every count is a floor.

The symbol page grows an 'If you change this' card: headline sentence,
cumulative hop tiers, region chips, run-these-first test list with an
explicit warning when no test reaches the symbol, and the floor
footnote. Question family F3 flips to answered (7/8/3); the
blast-radius wiki entry now names its caps.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 14:29:51 +02:00
Martin Vogel e14624d681 feat(ui): semantic zoom, flow-hop confidence, honest caps, tri-state filter
- Semantic zoom on the region scene: zooming INTO a region blob
  opens it, guarded by the anti-annoyance set from the research
  round - user wheel input within 600 ms only (programmatic fly-tos
  never trigger), approach direction only, 1.5 s grace so backing
  out doesn't bounce back in. The HUD hint says so.
- Flow steps carry the resolver confidence of their parent edge;
  hops under 75% show a small warm approx marker with the number -
  a misresolved hop now looks different from a certain one.
- Dashboard lists declare their caps honestly: 'top N of M' where
  the payload names the population (callables, churned files),
  plain 'top N' where it doesn't - never an invented M.
- FilterPanel All|None buttons became one tri-state master checkbox
  (checked / unchecked / mixed dash, aria-checked, keyboard-native).

vitest 71/71, tsc clean, C suites 96/0, lint green.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-26 23:01:11 +02:00
Martin Vogel f13f7f845e feat(ui): Why-view depth — dispatch chip, guard table, flow-step guards
- /api/why entries carry candidates from the CALLS edge; the tree
  shows a '1 of N targets' diamond chip on dynamic dispatch -
  genuine resolver uncertainty made visible.
- Decision-table pivot on the trigger tree (>=3 guarded callers):
  rows = callers, columns = distinct conditions, cells = when /
  unless / none. The experimentally-winning format for
  condition-combination lookup (Huysmans 2011).
- /api/flow&guards=1: flow-detail steps gain per-step guard chips,
  computed from each step's parent call site - journeys read as
  'then X, when ok && *cursor' instead of bare arrows.
- Number formatting unified to en-US (mixed browser locales
  rendered 1.053 next to 1,053).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-26 21:50:58 +02:00
Martin Vogel 2df92078f9 feat(atlas): the Why view — trigger trees with syntactic guard chains
The condition→action abstraction (Pennington 1987: the mental model
with the highest error rates, and the one code text serves worst),
served on demand. Design per the research synthesis: thin caller-only
spine (thin slicing), closed question menu, guard text ON the edge
in words (information scent), glyphs always paired with text
(Reacher's 22% missed-icon failures), complexity-gated (Scanlan),
declared elision, both directions (Green), honest footer.

- /api/why?id|qn&dir=up|down: callers (or callees) with the
  SYNTACTIC guard chain around each call site — enclosing
  if/else/switch-case/loop/ternary/catch conditions, outermost
  first, else-arm negation detected — extracted by parsing the few
  caller files live with the embedded tree-sitter grammars from the
  call-site line the CALLS edges already carry. Zero indexing cost;
  works on every existing index. Per-request parse cache; 2 MB file
  cap; unparseable sources reported as guards_unavailable, never
  guessed.
- /api/trace&guards=1: A-to-B paths gain per-hop guard chips -
  'reachable in 5 hops' becomes 'reachable when ...' (the exact gap
  Reacher's users hit: they hand-derived conditionals).
- Symbol page: 'When does this run?' tree - guard chips (amber
  unless / blue when), loop marker, unguarded honestly labeled,
  per-caller upward expansion, direction toggle, single-line answer
  when every call site is unguarded, footer stating guards are
  syntactic and dispatch/events are invisible.
- Trace results in Flows render the same chips per hop.

Tests: nested-if chain outermost-first + else-arm negation + loop
flag + downward direction + callsite helper on a real temp-dir C
file (C); /api/why route wiring; formatGuard/chain + the
complexity gate (vitest). Trap fixed en route: the extension table
keys WITH the dot ('.c'), not 'c'.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-26 21:05:56 +02:00
Martin Vogel e44ef0a917 feat(atlas): the handout — a shareable codebase document from the graph
GET /api/handout renders a self-contained, print-friendly HTML
document that explains the project to a newcomer: the region map
(cohesion as words, hubs), boundary spanners with a why-you-care
line, the main entry-to-effect journeys, and the churn x complexity
table. Linked from the Overview header ('Handout').

Honesty properties, tested: every graph-derived string is
HTML-escaped; no host paths leak into the artifact; caps are
declared inline ('20 of 33 regions shown', '10 of 60 journeys');
a project with no readable git history says so instead of skipping
silently; every fact is deterministic - the header states that no
model wrote any of it.

Exposing the same document as an MCP resource is recorded as a
follow-up (touches the MCP protocol surface; wants its own review).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 16:45:20 +02:00
Martin Vogel d8f95946c2 feat(atlas): working-set blast radius on the Changes tab
/api/blast buckets a file list by region (names included, 200-file
cap declared as truncated, every file accounted for: bucketed +
unmapped == files). The Changes tab feeds it the impacted symbols'
files (falling back to raw changed files) and renders the review
signal git cannot compute: 'your change reaches N files across K
regions' with per-region chips - disambiguated with the same
hub-suffix rule the galaxy uses, so two 'tests' communities don't
render as twins - and a seam warning at K>=3 suggesting a split or
citing the region names in the prompt.

Tests: blast bucketing + accounting + empty-input refusal (C),
/api/blast route wiring.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 16:31:56 +02:00
Martin Vogel a306569596 feat(atlas): honest lists — overflow shape, seam dots, boundary spanners
The D6 trust sweep: every capped or scoped surface says what it hid
and what it means.

- Symbol connections: when the page cuts the list, the hidden
  remainder is grouped BY FILE ('src/auth/session.c x 12') next to
  the Show-more button - a bare '+N more' hides the answer to 'who
  calls this' on exactly the nodes where it matters.
- Connection rows carry the neighbor's region; a row whose edge
  crosses an architectural seam gets a quiet accent dot.
- The Data-flows panel no longer renders on projects with no
  DATA_FLOWS edges at all: a section that can only ever say 'none'
  is noise, not honesty.
- /api/bridges: boundary spanners ranked by DISTINCT foreign regions
  a node calls into (cross-call count as tiebreak), test files
  excluded and inbound direction ignored - orchestrators like main
  and cbm_daemon_host_run surface; utility sinks and test harnesses
  (which the naive both-directions metric ranked first) do not.
  Overview shows the top 5 with a why-you-care line.
- Staleness banner was built and then REMOVED: no server signal
  means 'files changed since indexing' today, and detect_changes
  (a git-base diff) would have shown the wrong number. Recorded as
  a follow-up instead of shipped with wrong semantics.

Tests: bridges need >=2 foreign regions (a dense gamma community
mirrors the beta fixture pattern), overflow tail accounts for
exactly what the page cut, per-row regions + data-flow presence
flag, /api/bridges route wiring.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 16:13:43 +02:00
Martin Vogel 03995b7461 feat(atlas): galaxy orientation — search scent, minimap, region cards
The D4 orientation package, mechanics adapted from the two reference
UIs studied in the screened snapshots:

- Search scent propagates up the hierarchy: a 'find a symbol' input on
  the region sidebar lights matching regions in the 3D scene, pins hit
  counts on the region rows and the minimap, and reports the total
  with an outside-regions remainder. Served by /api/scent - a
  case-insensitive name match bucketed per region via the cached
  file->region map (File/Folder nodes excluded so a file never
  double-counts its own symbols; sub-2-char queries refused).
- 2D minimap panel (bottom right, collapsible): every region as a dot
  on a fixed top-down projection, sized by sqrt(members), colored by
  region, open region ringed, scent counts above the dots, click to
  open, and a Home button that fits the current scene.
- Region hover card: members/files, cohesion as a word (tightly /
  moderately / loosely connected) with the score, and the hub.
- Node tooltip on graphite tokens (last pre-D1 hardcoded surface) and
  in/out call degree - layout nodes now carry out_calls, which the
  degree pass already computed and discarded.
- Honest scope note in the region detail panel: '+N callers outside
  this region' whenever the full-graph in-degree exceeds the loaded
  window.

Tests: scent bucketing + LIKE-escape refusal (C), /api/scent route
wiring, minimap projection + cohesion words (vitest), region-list
queries disambiguated from minimap titles.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 14:27:19 +02:00
Martin Vogel b54eb3d0c1 feat(atlas): answer history, ownership, and A-to-B reachability
The two questions the research says developers ask most and tooling
answers worst: why is this code the way it is (rationale via change
history and ownership), and does A reach B (LaToza reachability,
asked ~9x/day). Plus the user-requested data-flow lens.

- /api/trace: shortest A-to-B path over CALLS or DATA_FLOWS (BFS,
  depth-capped at 12), endpoints by id or qualified name; the flow
  cache gains a DATA_FLOWS CSR beside the CALLS one.
- Symbol bundle: file_history (recent commits, commits/year, top
  author share from a bounded git log at metrics time, cached and
  honest - available:false when no history was parsed) and
  data_in/data_out with the stored edge detail.
- Flows tab: trace bar with symbol pickers and a control/data
  toggle; the result path is clickable hop by hop.
- Symbol tab: 'Recent changes to this file' and 'Data flows'
  panels with honest empty states.
- Tests: trace reachability both modes + endpoint errors, symbol
  bundle data-flow/history shape, /api/trace route wiring, and a
  fetchTrace client test.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 10:32:50 +02:00
Martin Vogel 46cf438dce feat(ui): findings feed Overview + hero Dashboard (Field Guide D2)
The metrics screens now answer questions instead of displaying data
(dashboard canon: Few, Grafana, CodeScene — the Field Guide carries the
citations):

Overview
- Four opinionated slots on top: Needs attention (knee-point count over
  churn×complexity), Direction (deltas vs the previous index), Riskiest
  area (named, with its why), and an untoned Trust strip (index stamp,
  missed files, unmapped symbols).
- Surprising couplings become finding sentences with evidence, an
  'inspect' next click, and an 'intended' dismissal whose key buckets by
  order of magnitude — a dismissed finding resurfaces only when it grows
  10x (regression re-alert, CodeScene's goals idea in one line of code).
- Hubs, entry points and boundaries demote to a collapsed Reference
  disclosure; inventory facts move to an untoned footer.

Dashboard
- Hero: the churn×complexity list with the knee-point head highlighted, a
  cost sentence ('N files — X% of the codebase, Y% of all commits this
  year'), raw commits shown per row, mute-as-intended dispositions, and
  rows opening the file's folder in Modules.
- Quality signals (resolution, dead, tested, documented) keep tones and
  trends; the complexity histogram now states its takeaway and names the
  tail; averages and equal-weight inventory cards are gone — facts live
  in the footer.
- Metrics payload gains raw commits per risky entry, churn totals and the
  distinct-file count to make the cost sentence computable.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 09:59:42 +02:00
Martin Vogel 3bc9d973e6 feat(ui): the Atlas graphite design system + trust repairs (Field Guide D1)
Visual language, rebuilt on the researched rules (Primer/Carbon/Material/
Radix/Linear; details in the Atlas Field Guide):

- Graphite surfaces with fixed step jobs (#0D0F12 canvas → #293038
  selected) replace the saturated navy; elevation is a lighter surface
  step, not a shadow; hover/selection are surface washes.
- One accent (sky #4FA8E0) reserved for interactivity; semantic
  good/warn/crit separate; the type floor is 12px (labels), body 13-14px,
  system font stacks (sans UI, mono identifiers only — zero vendored
  bytes), tabular numerals, radius 6px.
- Region colors move to the Okabe-Ito color-blind-safe palette, assigned
  server-side to the ten largest regions with a neutral tail — the code
  graph is the only colorful thing on screen.

Trust repairs, because every wrong-looking item costs belief in every
right one:
- builtins filtered out of hub lists (a 'len' hub reads as a bug);
- a product-code lens hides test regions by default everywhere regions
  appear, one honest toggle away ('18 test regions hidden — show');
- flow labels carry file context when the bare name is ambiguous
  ('datastructures.__init__ → …' instead of '__init__ → …').

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 09:50:25 +02:00
Martin Vogel 16b90077db feat(ui): the Atlas Dashboard — graph-derived metrics with drill-down
The numbers view of a project, with the code behind every number one
click away and no composite health score — the cards are the score:

- /api/metrics (cached per project+indexed_at): complexity and function-
  length histograms and top lists straight from pass_complexity's stored
  cyclomatic/cognitive/lines numbers, edge certainty (CALLS vs
  CALL_REFERENCE vs USAGE), dead-code candidates (same exclusions as the
  galaxy classifier), TESTS linkage, SIMILAR_TO duplication, missed
  files, documented-exports coverage.
- Per-file churn from one bounded git log pass (1 year, 10k commits, the
  pass_githistory bounds) computed at metrics time — ZERO cost added to
  indexing — plus churn × complexity, the classic where-the-bugs-live
  list.
- A per-index history sidecar (JSONL in the cache dir, one snapshot per
  indexed_at) feeds trend sparklines across reindexes.
- Dashboard tab: stat cards with warn tones, histograms, top lists
  linking to symbol pages; churn honestly marked unavailable when the
  project has no readable git history.

The wired-routes test covers /api/metrics; the ui suite pins totals,
certainty, histogram completeness and history presence on a fixture.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 01:09:25 +02:00
Martin Vogel 8bcb308e28 feat(ui): the CBM Atlas screens — Overview, Modules, Symbol, Flows, Changes, search, prompt composer
The human side of the Atlas program:

- Overview: the first read of a project — header numbers, region cards
  with provenance and cohesion, hubs by fan-in, entry points, surprising
  couplings with enumerated reasons, suggested questions with a why, and
  the cross-package boundary table. Assembled client-side from the
  read-only /rpc surface (get_architecture format:json) + the regions
  payload; no new server code.
- Modules: a squarified treemap (in-house, no new dependency) of any
  folder via /api/tree — sized by symbols, colored by dominant region,
  hatched where the indexer missed files, breadcrumbed drill-down, file
  panel listing symbols via search_graph.
- Symbol: one page per node via /api/symbol — docstring, flags, region,
  editor/GitHub links, code on demand, callers/callees with true totals
  and per-type confidence badges (resolved / reference / unproven),
  tests, co-change partners, near-clones.
- Flows: the /api/flows journeys grouped across/within regions, each a
  clickable indented call tree with truncation stated, mermaid export.
- Changes: detect_changes on the working tree grouped by risk, blast
  radius citable into the prompt in one click.
- ⌘K: symbols (search_graph) + code text (search_code) in one box.
- Prompt composer: a basket of cited symbols/regions/flows/questions that
  renders a prompt naming exact qualified names, file:line ranges and the
  MCP calls that reproduce the context — the bridge back to the agent.

Routing carries every screen in the URL (tab, project, path, sym, flow,
node, region) so all of it deep-links. Fixed en route: the /api/flow
route pattern '/api/flow?*' never matched (req->path has no query
string) — now exact, pinned by a wired-routes test that distinguishes a
matched route's 400 from the static handler's 404.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-24 00:53:47 +02:00