Brings the branch up to main 9c19b5dd.
Two same-spot insertion conflicts, each resolved as the main version
of the file plus the own diff of this branch:
- src/daemon/application.c: the job struct gained watch_owners on main
and request_started_ms plus the request facts here; both kept.
- tests/test_daemon_application.c: main added the #1818
autoindex-skipped helpers where this branch added the worker resource
failure test; both kept, the test placed after the doc_candidates
test.
Everything else merged cleanly. src/main.c changed on both sides: main
(#2838) lowers the worker CPU priority inside the worker role block,
and this branch strips the publication fence flag before that block
and installs the fence after the worker role options; the hunks are
disjoint and both are kept. tests/test_pipeline.c gained the Next.js
route tests (#2747) on main and the publication gate test here, also
disjoint. No call site needed adapting: no new main code calls a
function this branch re-signed, and main re-signed none that this
branch calls.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A worker resource limit is enforced by the supervisor, but only the worker
replaces the serving database, and it does so before the persistence
artifact export. A limit observed in that window stopped a worker that had
already published, and the response still said serving_index_preserved=true
while the previous index had been replaced.
Publication and resource termination now coordinate through a per-attempt
publication fence: a never-created path next to a reserved unique file in
the private worker directory, which both sides claim by creating it
exclusively (cbm_fopen "wbx": O_EXCL on POSIX, CREATE_NEW on Windows), so
exactly one of them can win.
- The worker claims it through a new pipeline publication gate that
cbm_pipeline_run calls once, after the last pre-publication check and
before the serving database is touched. Losing means the supervisor
already stopped the attempt for a limit: the run aborts before
publication and the previous generation stays in place.
- The supervisor claims it before it terminates the worker for a limit or
a failed RSS probe. Losing means the worker already committed to
publishing: the limit is no longer enforced for that attempt, which runs
to completion, and index.supervisor.resource_limit_after_publication is
logged instead. A claim that can be neither won nor lost is retried on
the next poll rather than risk stopping a committed worker.
- At terminal the supervisor settles the fence into
result.publication_committed. An unquiesced tree keeps the fence, so a
surviving member can never publish after the report.
serving_index_preserved now describes the request, not only its last
attempt: a servable index existed when the request began, no attempt of
the request committed to publishing (an earlier attempt can publish and
then crash into recovery), and that index is still servable. The direct
path and the daemon job both fold every attempt into these request facts;
cbm_mcp_index_worker_resource_response takes them as a third argument, and
NULL claims nothing.
The fence path travels as an optional last pair of the exact worker grammar
(--index-worker-publish-fence), accepted by the worker argv parser and by
the daemon bootstrap role check, stripped by the CLI and installed by both
worker entries.
Limits stay opt-in: without index_max_rss_mb or index_max_duration_seconds
there is no fence, no extra argv, no claim and no extra database read.
Graph content is unchanged either way.
RED (c78c077a plus only the new end-to-end test): a FIFO at the artifact
temp path holds the real worker after the rename, and the duration clock
crosses its limit only once the serving database has been replaced. The
supervisor stops the worker there and answers resource_limit_exceeded with
serving_index_preserved=true over a replaced database. GREEN: the worker
claimed first, the supervisor logs the limit as not enforced, and the
released worker finishes and reports the published index.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to main a5b248f47. Eight files conflicted, each a
same-spot insertion where main and this branch added lines next to the
same anchor. Each resolves to the main version plus the change of this
branch:
- src/cli/cli.h, src/cli/cli.c: CBM_CONFIG_IGNORE_WORKTREES and its
CONFIG_KEYS row next to the new doc_candidates key.
- src/mcp/mcp.c: the index_worktree schema property next to the new
doc_candidates property.
- README.md, docs/CONFIGURATION.md: the ignore_worktrees line and table
row next to the rewritten auto_watch entries and the new
doc_candidates and watch_non_git rows.
- src/git/git_context.c: the env-isolation comment after the new debug
stage marker, directly above cbm_popen_git.
- src/cli/hook_augment.c: the new SubagentStart cli_hint ahead of the
parent-graph check of this branch.
- tests/test_mcp.c: both RUN_TEST blocks; main already includes
str_util.h.
Per file, the lines changed against main equal the changes of this
branch against the merge-base.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The review of 2026-10-01 asked for this to be a stated decision rather
than a side effect. Detection walks up from the given path to the
nearest .git entry, so a subdirectory of a linked worktree is treated
as that worktree: index_repository on it is refused, and a session root
or hook working directory inside it is handled the same way. The walk
stops at the first .git; outside any checkout it runs to the filesystem
root one directory at a time. None of it runs while the key is off.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cbm_subprocess_tree_rss_bytes skipped any non-root process it could not
measure and still returned OK with a smaller total, so a descendant the
platform refused to measure silently dropped out of the RSS watchdog.
Now a member is skipped only when the platform says it no longer exists:
ESRCH from proc_pid_rusage on macOS, ENOENT or ESRCH for /proc/<pid>/stat on
Linux, ERROR_INVALID_PARAMETER from OpenProcess (or an exit code from a
handle whose memory query failed) on Windows. Any other failure fails the
probe, which the supervisor already turns into resource_probe_failed after
three consecutive errors. The root stays strict as before.
On macOS the members now come from proc_listpids(PROC_PGRP_ONLY): the old
all-process scan read every process with proc_pidinfo, which answers EPERM
for every process of another user (178 of 6438 on this machine), so it could
not tell a foreign process from an unreadable member and fail-closed
semantics would have failed every probe there. Linux keeps its /proc scan;
stat files are world-readable on a default mount, and a hidepid=1 mount now
fails closed, which the doc states.
A test seam makes every non-root measurement fail with one platform error.
RED (old skip-any-failure decision): the live-member test returns OK where
ERROR is required. GREEN with the fix; the exited-member test passes in both,
so verified exit races are still ignored.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings #2198 (benchmark harness runtime isolation, #1696) up to date
with main a5b248f4.
No conflicts. Of the files this branch touches, main changed only
scripts/test.sh since the merge base 8530c8cd: it adds Step 5-alloc
(production allocator contract), far from this branch's Step 0i3,
which is unchanged. scripts/test-runtime.sh, which the harnesses and
their contract test source, is unchanged on main. No caller of either
harness outside docs/EVALUATION_PLAN.md exists on main.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Section 7 still began with rm -f ~/.cache/codebase-memory-mcp/*.db.
Every index in the loop now lands in a run-private cache (#1696), so
that line no longer made any run colder; it only deleted the indexes in
the live store of the operator. Dropped, and the comment above it now
names only what the setup still does.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
With CBM_BENCH_KEEP_RUNTIME=1, bench_finish ran `daemon stop` with its
output and exit status discarded and wrote runtime-root.txt regardless.
`daemon stop` refuses while a committed client still uses the daemon and
fails when the daemon does not answer; the handoff then promised a
stopped daemon that was still running.
A refused stop now prints the explanation of the product, writes no
handoff, keeps the root without cleaning it up (the daemon may still be
using it) and exits non-zero. A stop with nothing to stop still exits 0,
so a clean run is unaffected.
Section 7 step 8 of the evaluation plan follows the same rule: the kept
root is removed only if its daemon stopped, and is kept with a message
otherwise.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A daemon started with `daemon start` now keeps a project watched after its
last owning MCP session closes, until the daemon stops, the project is
deleted, its directory disappears, or auto_watch is set to false. README
(the watcher paragraph and the index_repository row), the watcher_enabled
row of docs/CONFIGURATION.md and the comment above the index_status watch
provider still said every watch ends with its last session.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- Non-ASCII paths: drop the NTFS junction example; the workaround is
ASCII-only paths for the two variables, or an upgrade.
- Update entry: name the real command (update, not upgrade) and mark
#1168 as closed, fixed in v0.10.0.
- Index worker: from v0.12.0 a worker that exits without returning a
result is no longer counted as a success, and its log is kept; ask
for logs when indexing still fails on v0.11.0 or newer.
- Antigravity: state the affected range (all releases through v0.12.1
when neither detection signal is present) and which signal each
version checks; scope the duplicate binary to v0.9.x and earlier.
- OpenCode: list the detection signals per version (PATH, config, and
from v0.12.0 the data folder), the 4 KB PATH and npm/mise/scoop shim
gaps fixed after v0.12.1 (#2381), and a workaround per version.
The page structure and the original entries were first drafted by Tamer
in #2262.
Refs #1170
Co-authored-by: Tamer <tamer.elbakkali@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
§7 step 8 removed the kept runtime root while the graph session of
steps 4-7 may still have a daemon running under it; stop that daemon
under the sourced CBM_RUNTIME_DIR first. §13 Reproducibility still
cleared ~/.cache/codebase-memory-mcp/*.db and called the harness
unflagged, which no longer describes what the harness does: it indexes
into a run-private root, and only CBM_BENCH_KEEP_RUNTIME=1 hands that
root, via runtime-root.txt, to the graph phase.
Signed-off-by: Anton Standrik <astandrik@yandex-team.ru>
The evaluation plan (docs/EVALUATION_PLAN.md §7) indexes a language with
benchmark-index.sh and then answers graph questions against that index
from its own MCP session. With a run-private runtime (#1696) the index
was gone before that session could start, so the handoff is now explicit
and opt-in.
With CBM_BENCH_KEEP_RUNTIME set, a SUCCESSFUL run stops its daemon,
leaves the private root in place and records the paths that reach it in
<results>/<lang>/runtime-root.txt (sourceable: CBM_BENCH_RUNTIME_ROOT,
CBM_RUNTIME_DIR, CBM_CACHE_DIR). Ownership of that root, including its
removal, passes to the caller. A failed run cleans up regardless: there
is no index worth keeping and nothing may leak. Nothing changes in the
default path or in the three timing files.
§7's skeleton sets the flag, sources the file before the graph session
and removes the root in step 8 instead of the live-store *.db files.
The contract test drives a kept run and requires the root to survive,
the handoff file to be sourceable, and its paths to be the ones the
product processes actually used; the unflagged run still asserts the
root is gone. The kept-run assertions fail against the previous head.
Signed-off-by: Anton Standrik <astandrik@yandex-team.ru>
Match canonical Windows separators across watch ownership and admission,
drain all session watches when auto_watch is disabled, and retain the
resolved doc_candidates choice for later refreshes. Add deterministic
regressions for the five observed failures and an absent-option guard.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Replace the PATH_ALLOW_BROAD environment variable with a recorded grant,
the shape agreed in review: `codebase-memory-mcp allow-root
--approve-broad <path>` records an explicit human approval for one root
refused as too broad (CBM_WS_DENY_TOO_SHALLOW), mirroring
--approve-sensitive end to end.
- cbm_workspace_grant_add() takes approve_broad next to
approve_sensitive. Each flag lifts only its own verdict, absolute
denials stay unliftable, and a refusal names the flag that would
lift it.
- The approval is stored with a '^' marker, listed by allow-root --list
as "(approved broad)", and honoured by cbm_workspace_root_allowed()
only for CBM_WS_DENY_TOO_SHALLOW and only on an exact path match,
keeping the exact-match rule of the original change.
- cbm_workspace_root_allowed() returns to its previous signature. Every
caller (MCP index_repository and index-job start, auto-index, daemon
sessions, the HTTP UI) reads the same grant store, so stdio and
daemon sessions decide identically and nothing depends on the
environment a daemon was started with.
- The too-broad refusal names `allow-root --approve-broad <path>`, a
command a person runs.
- cbm_workspace_classify_root() and cbm_workspace_verdict_is_overridable()
are unchanged.
The regression test keeps its name and its exact-match, sibling,
absolute and sensitive checks, now against the grant store, and adds
that a broad marker never lifts a sensitive verdict.
Co-authored-by: Mark Murawski <github@kobaz.net>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Centralize recurring Windows-specific troubleshooting entries in a dedicated document linked from README.md. Each entry includes confirmed symptoms, affected version ranges, current workarounds, and canonical tracking issue references, updated to reflect v0.10.x and v0.11.0 status.
Refs #1170
Signed-off-by: Oscarbol09 <omaderabolano@correo.unicordoba.edu.co>
Watchdogs now sample the OS-charged footprint instead of resident size on
Darwin, and index_max_duration_seconds covers the whole request so a
crash/hang respawn cannot reset the clock.
Signed-off-by: 刘冲 <mail@liuchong.dev>
Discovery limits bound what indexing accepts, not what it then costs. A
repository well inside those bounds can still exhaust the host through parser
memory, or simply never finish, and a supervised worker that hangs leaves the
parent waiting with nothing to report.
Add index_max_rss_mb and index_max_duration_seconds, enforced by the parent
against the worker process tree rather than the worker process alone, so a
runaway child cannot hide behind a small parent. Resident memory is sampled
through the platform interface on macOS, Linux and Windows. Crossing a limit
terminates the tree and yields one trusted, structured terminal result that
attributes the failure to the resource that caused it.
Both limits default to off. A measurement that cannot be taken fails the
attempt instead of passing it: a watchdog that quietly stops watching is
worse than no watchdog at all.
The shell fixture that stands in for the supervisor names the two new keys.
The worker accepts only a policy that spells out every key it knows, which is
what keeps a stale supervisor from starting a worker it cannot bound.
Signed-off-by: 刘冲 <mail@liuchong.dev>
The first commit of this branch is the Smart CI change as of the rebase
onto 9f85f104. A review of it found places where the engine could run
fewer tests than a change needs; each now runs everything or selects the
right tests, with a test that fails when its fix is reverted.
Engine:
- A `#define NAME "literal"` line counted as naming a role but selected
nobody, so the tests that start the blocking-git probe through their
marker macros would not have run. The scan now follows NAME, as a whole
word, to the code that uses it, through chains of up to 4 macros; a
define it cannot follow runs everything.
- main dispatches through the role table, so no edge leads from main to a
handler. A table entry the engine cannot read, or whose handler is not
exactly one function, was skipped; it now runs everything.
- In a runner file, only a test case names a role's literal. The runner's
own helper that reads the marker no longer counts as a spawner, so a
role only the runner names runs everything again.
- A handler reached first by a parse gap or by the spawn lift was never
lifted. Role lifts and parse gaps now run to a fixpoint, before the
runner check and again after the spawn lift.
- A reached sink that main calls (the blocking-git probe) counted as a way
into main and hid the real one: #2644 now names the start-up path main
-> cbm_index_supervisor_capture_build_fingerprint.
- No pointer past the end of a role line; the RUNNER_REACHED note clamps
its prefix.
CI:
- pkg/npm/README.md, which test_cli reads, runs the test legs instead of
the docs tier.
- The build rule lists the harness scripts the test legs run:
run-test-wave.py, path-safety.sh, clean.sh.
- The revert guard rejects the ancestor's coverage when a diff fails.
- The vetoed selection is uploaded (test-selection-vetoed).
Docs: SMART_CI.md states the new fail-safe cases, the version that lacks
the new config keys, the selection base, and that the narrowed leg builds
with gcc while the coverage comes from clang.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
v0.12.0 could not be installed through npm, pnpm, pip or the Go wrapper on
any platform (#2657): each wrapper refused an archive member above 256 MiB,
and every v0.12.0 binary is 331-335 MB. The v0.11.0 binaries were already
about 300 MB, so the Go wrapper, which has had the cap since v0.10.0, was
broken before; npm and PyPI gained it in v0.12.0. The archive is verified
against the release's checksums.txt before it is unpacked, so a byte cap
only ever refused what we ship.
- npm, PyPI, Go: delete every byte-size cap (compressed archive, per member,
aggregate, checksum manifest, download size). The checksum, the exact
member set, path and link refusal, the member count, HTTPS and the
--version check stay.
- All three honour CBM_DOWNLOAD_URL like install.sh, for testing: https, or
plain http to an exact loopback authority with no proxy and no redirect.
- Smoke: scripts/smoke-wrappers.sh installs the three wrappers from the
checkout against the release fixture and runs the binary each one
installed. smoke-test.sh Phase 13w runs it; SMOKE_REQUIRE_WRAPPERS=1 makes
it required on the PR smoke lanes and in the release smoke, which gates
publishing.
Other install paths found broken while checking every channel:
- Homebrew: the formula was still at 0.10.3 and installed the dynamic Linux
build, which needs glibc 2.38 (Ubuntu 22.04, Debian 12 and RHEL 9 lack it).
It now installs 0.12.0 with the static -portable Linux assets and tracks
the newest release in the version contract like the other surfaces.
- Go: `go install .../pkg/go/cmd/codebase-memory-mcp@vX.Y.Z` never worked
(no pkg/go/v* tags, so Go fetched the whole repository, over its 500 MB
module limit), and @latest built main's wrapper, whose version constant
lags each release. The release now also tags pkg/go/vX.Y.Z, and the
wrapper installs its own module version (the constant only for local and
untagged builds).
- npm: a stable release also moves the `next` dist-tag to itself; `next`
still pointed to 0.9.1-rc.1.
- Docs: winget and Chocolatey were listed as install channels but are not
published; they are removed until they are. Windows arm64 ships and is
now listed.
Fixes#2657.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolves the four README conflicts against main 49424722: the README keeps
this branch's wording and style, and takes the content main added since
4d9592b5 in that style (list items "X: ..." instead of "X — ...", mid-
sentence dashes as semicolons):
- feature list and indexing pipeline: main's infrastructure-as-code
bullets (container images, compose, Helm, Terraform, Service -> code);
- edge types: main's SPAWNS detail plus INFRA_MAPS, USAGE ref:image,
DEPLOYS, DEPENDS_ON and CONFIGURES;
- trace_path: this branch's text (depth 1 to 15, tests excluded) plus
main's topology / deploy / run modes.
Checked: the supported-agent README contract (45 surfaces, every agent),
the language-count contract (162 grammars), all 22 local links and images.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Doc-section -> code candidates (doc_link_candidates, #2574/#2610) are the
documentation-in-the-graph feature of this release. They now run by
default for full indexes and not for moderate or fast ones; an explicit
`doc_candidates` argument, or a `doc_candidates` setting the user set,
overrides the default either way in every mode.
- pipeline: the switch is unset/on/off; unset means on only when the
effective mode is full.
- MCP: forwards only an explicit choice (argument, then a user-set config
value) to daemon jobs and workers; daemon auto-index and watcher/UI jobs
now carry a user-set value too (their workers have no config, so
`doc_candidates false` was ignored there).
- fast + doc_candidates on runs the semantic pass's corpus phases and the
doc phase only: candidates, and still no SIMILAR_TO or
SEMANTICALLY_RELATED edges.
- config: `doc_candidates` joins the config key table (default `auto`);
`config set doc_candidates ...` was refused as an unknown key before.
README, docs/CONFIGURATION.md and the MCP schema describe the default.
Proof (macOS arm64, django fb11376):
full 11,304 rows · moderate 0 · fast 0 · full+off 0 · moderate+on 1 ·
fast+on 1 (moderate/fast discovery skips docs/) -- nodes and edges are
identical between on and off in every mode; fast+on adds 0 semantic
edges.
- New tests (RED on the old code): pipeline
pipeline_doc_candidates_default_follows_mode,
pipeline_doc_candidates_explicit_wins_over_mode,
pipeline_doc_candidates_fast_on_legacy_incremental; mcp
tool_index_repository_doc_candidates_follow_mode,
tool_index_repository_doc_candidates_forwarded_only_when_explicit;
daemon_application
daemon_application_programmatic_index_forwards_set_doc_candidates.
- mcp 374 passed (4 Windows-only skips), daemon_application 73 passed,
pipeline/store_arch/cli/runtime_settings/index_policy/pipeline_frozen
green on the release tree.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The doc<->code candidate scoring (#2574, doc_section_worker in
semantic_edges) scores every documentation section against the functions
that share a key term with it, key terms being those held by at most
funcs/20 functions. Its cost therefore grows with sections x functions.
Measured on 11 corpora (10-09 main vs 4d9592b5, semantic_edges): under a
second up to ~100k functions, +12.6 s on java (325k), +14.6 s on csharp
(451k), +174 s on the Linux kernel (784k functions, 28k sections), almost
all of the kernel's regression against v0.11.0.
A new boolean setting, doc_candidates (default true), and an optional
index_repository argument of the same name switch the scoring off: no
candidate rows are stored and nodes and edges are unchanged. The argument
wins over the setting. The index handler resolves the value once and
passes it to a daemon job or supervised worker as an argument, the same way
as the resource policy, so every route sees one decision; the session
auto-index reads the setting. With the switch off, a delta-route reindex
replaces the candidate rows it would otherwise keep, so turning it off
leaves no stale candidates. The default output is unchanged.
Tests (each fails on its assertion when the switch has no effect):
- pipeline_doc_candidates_off_stores_none_and_keeps_graph: no rows, same
node and edge counts as the default run, which has rows;
- pipeline_doc_candidates_off_clears_rows_on_delta: a closure-repair
reindex with the switch off removes the earlier rows;
- tool_index_repository_doc_candidates_switch: argument false -> none;
setting default -> rows; setting false -> none; argument true over
setting false -> rows.
Proof, release-built binaries on macOS arm64 (M5 Pro 18c/48 GB):
- django, main 4d9592b5 vs this change with defaults: nodes (63,441),
edges (307,584) and doc_link_candidates rows (11,304) hash-identical;
- django with doc_candidates false: nodes and edges hash-identical, no
candidate rows;
- Linux kernel with doc_candidates false: semantic_edges 17.9 s (191.9 s on
main, 17.6 s before #2574), whole index 210.9 s (v0.11.0: ~234 s), no
candidate rows.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The UI section now describes the Lens that is on main: the six
workspaces, the Architecture views, Galaxy and the local chat. Three GIFs
and six stills of this repository at 4d9592b5 replace the old screenshot
and the large WebP recordings. Statements that no longer match the UI
are corrected or removed, and the test counts come from main.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
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>