Commit Graph
139 Commits
Author SHA1 Message Date
Jake Abendroth 23dfea58b1 fix(hook-augment): register Bash search matcher
Wire Bash into the installed Claude PreToolUse matcher, add end-to-end reachability coverage, migrate the previous Grep|Glob matcher, and remove the unused rtk_grep parameter.

Signed-off-by: Jake Abendroth <contact@jakea.net>
2026-08-21 04:21:09 -07:00
Jake Abendroth 5882be29f7 fix(hook-augment): bail on rtk grep -l per maintainer requirement
rtk grep adds its own -l <N> that shadows grep's valueless -l, making
the pattern position ambiguous. Fail closed instead of guessing.

Removes l from the rtk_grep val_flags string and adds an explicit
return false when rtk && bin == HA_BIN_GREP && flag == 'l'.
Test flipped from ASSERT_TRUE to ASSERT_FALSE.

Signed-off-by: Jake Abendroth <contact@jakea.net>
2026-07-31 15:43:56 -07:00
Jake Abendroth eb12d6dd49 style: apply clang-format
Signed-off-by: Jake Abendroth <contact@jakea.net>
2026-07-29 16:16:38 -07:00
Jake Abendroth 8289897fd9 feat(hook-augment): augment Bash tool searches via graph
Bash-based searches (rg, grep, ag, ack, ugrep, git grep) bypass
hook-augment because the PreToolUse matcher only covers Grep/Glob.
Detection lives in hook_augment itself, reusing the existing
search_graph path without duplicating the resolver.

- ha_tokenize: minimal shell tokenizer (quote/backslash handling,
  no evaluation)
- ha_parse_bash_search_pattern: strips VAR=val prefixes and
  wrappers (env, nice, time, command, rtk, tokf run), identifies
  the binary, walks flags, bails on -f/--file and multiple -e,
  handles rtk grep -l <N> as value-taking
- ha_tool_event_supported: Bash added alongside Grep/Glob for
  PreToolUse in HA_DIALECT_EVENT
- ha_process: Bash branch routes tool_input.command through the
  extractor before the shared ha_extract_token + search_graph path

Binaries covered: grep/egrep/fgrep, rg, ag, ack, ugrep/ug, git grep
Wrappers covered: env, nice, time, command, rtk, tokf run

Closes #1082

Signed-off-by: Jake Abendroth <contact@jakea.net>
2026-07-29 16:16:38 -07:00
Martin Vogel 958a511345 docs+cli: finish the install-script update contract
The update handoff moved out of the product on every platform, but two loose
ends were left behind.

README was actively WRONG, not merely incomplete. It still documented in-place
updates for macOS/Linux, and it promised "The MCP server also checks for updates
on startup and notifies on the first tool call" -- a claim that died with the MCP
update thread and then the daemon's curl check. The section now states the one
contract that holds everywhere (`update` validates flags and prints the install
script command), why it works that way (on Windows a running image cannot
replace itself; on POSIX a deliberate choice, because an in-process updater is
structurally a downloader), and the affirmative property that replaced it: cbm
makes no network request of its own accord and the archives carry no download
URLs at all.

`uninstall` now REPORTS the install script it finds beside the binary, printing
the path and the `rm` command, and says "would leave" under --dry-run. It does
not delete it. install.sh places itself there so `update` has something to hand
off to, but that file may equally be the user's own copy, a symlink into a
checkout, or package-manager managed -- an uninstaller must not delete a file it
cannot prove it owns, and one printed line leaves the decision where it belongs.
Windows checks install.ps1 too and tolerates either path separator.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-30 00:05:32 +02:00
Martin Vogel 1f674c805e harden: remove capability that should never have shipped
Microsoft's ML flagged the rc.1 release binaries. The decisive evidence is that
the SAME sha256 went from 0/62 clean to Microsoft-detected in about an hour with
no byte change, so the verdict lives partly in scanner-side state and no code
change can promise a clean result. What code CAN do is stop shipping things that
have no business in a release artifact, which is worth doing on its own merits
and incidentally widens the classifier margin. Every claim below is verified
against a built binary by the new gate, not by reading source.

Executable stack (the worst of the findings). vendored/nomic/code_vectors_blob.S
is the only assembly in the build and carried no .note.GNU-stack. An unannotated
object makes ld assume the worst for the whole link, so EVERY Linux release we
have ever shipped had GNU_STACK RWE. Adds the note (cause) plus ELF-only
-Wl,-z,noexecstack (outcome); the gate fails the release if it returns.

Test seams are now opt-in, never opt-out. TEST_SEAMS=1 defines
CBM_ENABLE_TEST_SEAMS; without it the crash-orphan probe -- which forks a child
that ignores SIGTERM and loops forever, then writes its pid to a caller-supplied
path -- and the lease-ownership marker compile to trivial stubs, so call sites are
untouched and the binary holds no fork, no signal handler and no env-var string.
Opt-IN is the point: forgetting the flag yields a clean binary rather than a leaky
one. scripts/test.sh requests it in the leg that consumes it, and
tests/test_worker_watchdog.sh now asserts the capability up front instead of
dying later with an opaque "Killed: 9".

The daemon's background version check is gone. It spawned curl against
api.github.com/repos/.../releases/latest on the first eligible session of every
run to say "a newer version exists" -- a release URL and an outbound request in
every shipped binary, for something the install scripts already report. The
INJECTABLE SEAM survives: update_ops is still honoured, the fakes in
tests/test_daemon_application.c still cover notice/ownership/cancellation/replay,
and with no provider application_update_subscribe_locked returns early so no
generation ever starts. "No network request by default" is now structural.

Dead capability out of release builds. The tar.gz/zip extraction block
(gzip_decompress through cbm_extract_binary_from_zip, plus its cli.h
declarations) moves under CBM_CLI_ENABLE_TEST_API -- verified self-contained, zero
uses of any helper outside it, only callers the excluded updater and
tests/test_cli.c. Downloading an archive, decompressing it, picking an executable
out of it and marking it executable is the canonical dropper composite; it is now
absent rather than merely unreachable. SQLite is built with
-DSQLITE_OMIT_LOAD_EXTENSION (no caller of load_extension anywhere in src/ or
internal/), removing that API surface and part of the dlopen/dlsym surface.

Temp files and environment scanning (S2/S3). Predictable paths in mcp.c,
artifact.c and diagnostics.c are created privately and exclusively and written
through the returned descriptor; pass_envscan.c no longer descends symlinked
directories out of the project root, and its fixed 512-byte path buffers no
longer truncate into pointer arithmetic that could land outside the buffer.

Build-time entropy. mimalloc's version banner baked __DATE__/__TIME__ into every
binary, so two builds of identical source seconds apart could never share a hash
and no release could inherit a false-positive determination made about its
predecessor. Local patch removes it (marked to survive refreshes), -Wdate-time
makes any future use a build error, and -Wl,--no-insert-timestamp stops the PE
header carrying the link clock.

scripts/ci/check-binary-composition.sh is the proof that each removal stays
removed, wired into package-release.sh after strip so the local artifact-flow
smoke enforces exactly what CI does. It asserts absences plus a CANARY string, so
handing it a compressed, stubbed or empty file fails instead of passing
vacuously, and a missing tool is a hard error -- a skipped assertion must never
look like a satisfied one.

Two build-system traps found by that gate, both of which had silently defeated a
fix: the product binary is compiled in one shot from sources, so a flag flip did
not rebuild it (now tracked by a .build-config stamp that also removes the
binary, making it independent of mtime granularity); and prod_sqlite3.o /
prod_mimalloc.o depended on a single named source, so SQLITE_OMIT_LOAD_EXTENSION
and the mimalloc patch BOTH compiled to nothing on the first incremental build.
Source review would have called them done.

Deliberately NOT changed. Three seams stay in release artifacts because
scripts/smoke-test.sh runs against the real artifact and needs them:
CBM_TEST_CRASH_ON and CBM_TEST_HANG_ON inject the faults that prove supervisor
recovery, and CBM_TEST_WINDOWS_USER_PATH_RUN_ID is what keeps the PATH smoke from
writing the tester's actual PATH. The gate treats those as an allowlist, so a
NOVEL seam still fails. The true no-UI standard build is deferred rather than
rushed: src/ui/* is in PROD_SRCS and four files outside src/ui reference UI
symbols, including the daemon that serves the UI, so that assertion reports
instead of failing until the split lands -- a gate everyone knows is red teaches
people to ignore gates.

No grammar is removed. ObjectScript accounts for essentially all binary growth
since the last provably-clean release (+21.4MB rodata, +1.1MB text from two
four-line shims), which made it the obvious ablation candidate, but a dry run
performed twelve real Defender endpoint scans across standard/UI and amd64/arm64
with ObjectScript, the daemon and the expanded hooks all present and every scan
was clean. Nothing there is a deterministic trigger, so cutting a
community-contributed language would spend a real feature on unproven margin.
Lean is not a candidate either: at 99.6MB of source it is by far the largest
grammar, but it shipped in v0.9.0 which scanned 20/20 clean, so removing it would
produce a novel unscanned profile instead of restoring a known-good one.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-29 23:50:53 +02:00
Martin Vogel afd67c51b0 refactor(cli): install scripts own updating; the binary carries no URL
Completes the move started on Windows. `update` prints the install script's
command and exits 0 on every platform; the download/extract/chmod/exec sequence
is compiled out of release builds entirely, and the MCP background thread that
ran cbm_popen("curl ... api.github.com ...") on first tool call is gone.

The installers now place themselves beside the binary, which closes the last
gap: `update` referenced a raw.githubusercontent URL purely because an
install.sh-installed user had no local copy to point at. Two details make that
safe.

The source is the install script from the archive we JUST checksum-verified,
never "$0" -- which does not exist under `curl | bash`, and would pin the user
to the OLD installer forever. Since the script ships inside the verified
archive, it inherits that verification instead of needing its own.

And it is published by atomic rename, never written over the live path. Bash
reads a script incrementally by byte offset, so overwriting the file it is
executing continues reading NEW bytes at the OLD offset: silent, bizarre
corruption. cp to a temp name then mv -f swaps the directory entry while the
running shell keeps its original inode. Windows follows the same rule via
Copy-Item + Move-Item even though PowerShell parses up front.

Deliberately NOT done: spawning the new installer to delete and replace its
predecessor. Fetch remote code -> drop to disk -> spawn -> self-delete is a
textbook stager, and self-deletion is among the most heavily weighted
heuristics there is. It also gains nothing -- `"$DLBIN" install` already runs
the NEW release's install logic, because the binary owns the install.

Net: zero install or download URLs remain in the shipped binary, and the
allow-list entry is retired with it. Both scripts scan CLEAN (0/61) with the
self-placement code in them.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-29 17:04:50 +02:00
Martin Vogel 0b31a5dcdf fix(security): declare the printed install-script URL on the allow-list
The `update` handoff prints a curl one-liner when install.sh is not beside the
binary, and the security audit blocked the release on it:

  BLOCKED: src/cli/cli.c: URL not on allow-list: https://raw.githubusercontent.com/DeusData/

Two things were wrong. The URL was not declared, and it was split across string
literal continuations, so the audit extracted only the first fragment -- an
allow-list entry for the real URL could never have matched it. Hoisted to a
single CBM_INSTALL_SH_URL token so the declaration means what it says.

It is the same URL as the documented one-liner install, and it is printed for
the user to paste: the binary no longer downloads anything.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-29 10:38:18 +02:00
Martin Vogel 9ecabe0a6f refactor(cli): move self-update out of the product on every platform
Windows already handed updating to install.ps1, because a running .exe cannot
replace its own image. The remaining platforms now do the same, for a different
and better reason.

An in-process updater is structurally a downloader: it fetches a remote archive,
verifies it, extracts it, marks the result executable and runs it. That composite
lived in every shipped artifact -- along with the release URLs it fetched -- to
serve a command most users run a handful of times. The install scripts already
do all of it, are idempotent (so re-running one IS the update), and run while cbm
is not running.

`update` now prints the exact command for the running platform and exits 0:

  unix     bash "<dir>/install.sh"     (or the curl one-liner when not adjacent)
  windows  powershell -ExecutionPolicy Bypass -File "<dir>\install.ps1"

Both scripts ship inside their release archive, so the printed path resolves
next to the binary. Flags are still parsed and validated, so `update --dry-run
--ui` keeps rejecting typos rather than silently accepting them.

The updater itself is EXCLUDED from release builds rather than left to dead-code
elimination: build_update_url, download_verify_install, extract_and_install_binary,
checksum fetch, detect_os/detect_arch and the cli_download_* helpers now compile
only under CBM_CLI_ENABLE_TEST_API, which release builds do not define. The C
suite still covers the flow through the activation test seam. Verified against
the release binary: `releases/latest/download` is gone entirely.

Also removes the MCP background update check. It ran on the first tool call and
did:

  cbm_popen("curl -sf --max-time 5 -H 'Accept: application/vnd.github+json' "
            "'https://api.github.com/repos/.../releases/latest' 2>/dev/null")

An embedded shell command plus a startup network callout, in every session, to
tell users a newer version exists. It shelled out, depended on curl being
installed, and phoned home from every agent session. The install scripts report
versions; this did not need to be in the server.

Smoke follows the same collapse. Phases 6c and 14 asserted a Windows-specific
handoff and a POSIX in-process replacement; they now assert one platform-neutral
contract selected by UPDATE_SCRIPT, and 14a's byte-identity check compares the
driver against ITSELF before and after -- comparing against "$BINARY" passed on
Windows only because Windows has no signing step, while the POSIX fixture
ad-hoc re-signs its copy and so differed before `update` ever ran.

The Linux glibc guard is RELOCATED, not retired. The standard linux asset links
glibc 2.38+ and breaks Debian 11, RHEL 8 and Ubuntu 20.04, so the installer must
fetch the static "-portable" build. That constraint moved from the binary to
install.sh, and the smoke now guards it where the behaviour lives.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-29 10:22:19 +02:00
Martin Vogel 0a22fb14df fix(windows): absorb transient file locks when retiring a running image
Release run 30385988475 lost three Windows smoke jobs to one phenomenon on two
different surfaces. A Windows file that was just written or just executed can
refuse deletion and rename for a moment while a scanner reads it or while the OS
finishes reaping a child. Both conditions clear on their own within moments.

It reads as nondeterminism because it is timing: windows-2025 passed both amd64
variants while windows-latest -- the SAME image, windows-2025-vs2026 -- failed
both, and windows-11-arm failed too.

PRODUCT: uninstall abandoned a live installation.

With one binary per platform, uninstall must remove the very image it is
executing, and on Windows a running image can only be renamed, never
overwritten. That rename got a single attempt, so a transient sharing violation
surfaced to the user as

    error: failed to remove ...; completed configuration/index cleanup may remain

with the install left half-torn-down. install.ps1 already retries this exact
operation ten times for this exact reason; the C path now does the same, and
only for sharing/access/lock violations, so a file that is genuinely held still
fails closed instead of spinning.

Proven with a failure-injection seam rather than by racing a real scanner:
3 injected failures must still succeed, 64 must still fail AND leave the target
in place. Deterministic, so it does not need CI to learn whether it works.

HARNESS: the smoke killed itself silently.

Phases that install a binary then `rm -rf` the directory hit the same lock. Under
`set -euo pipefail` that returns non-zero and ends the run WITHOUT printing
anything -- the logs jump straight from "OK 13h" to job cleanup with no FAIL
line, which is what made this expensive to read.

None of those 16 removals is an assertion; they are fixture cleanup, and
_smoke.yml already states the principle ("a temp dir on an ephemeral runner must
never fail the job") that smoke-test.sh did not honour. They now go through
smoke_rmtree, which retries so the disk is actually reclaimed -- these fixtures
hold ~300 MB binaries and runners are disk-tight -- then warns and continues.

Verified: Windows VM full smoke ALL PASSED (previously died after 13h), macOS
full smoke ALL PASSED, activation_transaction + cli 259 passed on Windows, all
10 shell contracts pass, lint-ci clean.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-28 22:49:22 +02:00
Martin Vogel 2740208f40 fix(cli): report a failed install --dry-run plan check in the exit status
The Windows smoke guards that an invalid PATH seam must fail closed rather than
silently falling back to the live HKCU\Environment\Path. That guard went green
in a way that meant nothing: the seam still refused, and install still printed
"PATH configuration failed", but the process exited 0.

Windows release builds used to compile a separate managed-install path. With one
binary per platform they compile the shared cbm_cmd_install, which since 30b5f12
downgraded ANY failed dry-run plan check to CLI_OK. That commit was fixing a real
complaint -- the dry-run summary was being skipped entirely on Windows -- but it
bundled the exit status into the fix. Keeping the summary is right; reporting
success is not. --dry-run exists to answer "would this install work?", so a
caller that only sees the exit code could not tell a refused PATH probe from a
clean plan.

The summary and the triage note still print; only the status changes. A real
install already failed non-zero here, so the fail-closed behaviour itself was
never at risk -- just its observability.

The smoke also still encoded the launcher-era portable-vs-managed split, which
does not survive one-binary-per-platform:

  * 6b expected uninstall to be REFUSED. That refusal existed because a portable
    extracted bundle was a different artifact from the launcher-managed install
    it would have torn down. The extracted binary now IS the installed one.
  * 6c expected update to be REFUSED, contradicting the contract Phase 14a
    asserts against a real update: Windows hands off to install.ps1, exits 0 and
    prints the command. 6c now pins that same handoff.
  * 14a compared binaries with cmp, which is absent from the Windows MSYS shell,
    so it reported "different" for two copies of one file. Hashes instead, the
    way Phase 12 already verifies the release archive.
  * 14f seeded the MCP entry at the RETIRED binary. The old launcher-managed
    update rewrote that entry in-process; the handoff does not, so uninstall
    correctly declined to remove an entry owned by a different installation and
    the phase demanded the one thing it must never do. It now seeds the
    installed binary, which is what install.ps1 leaves a real user holding.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-28 19:42:37 +02:00
Martin Vogel a54ea95719 fix(windows): ship one binary — remove the launcher stub flagged as a dropper
Windows shipped a PAIR: a small permanent launcher (codebase-memory-mcp.exe)
plus the real product binary (codebase-memory-mcp.payload.exe). The launcher
existed for exactly one reason — a running .exe cannot replace its own image
on Windows, so an in-process self-update needs a second resident binary to do
the swap.

That stub is statically indistinguishable from a dropper: a small, unsigned,
zero-prevalence PE whose whole job is verify-and-execute another binary.
Defender's ML scored it Trojan:Win32/Wacatac.B!ml and blocked the v0.9.1-rc.1
release at the VirusTotal gate. It is not fixable in our code on x64 —
bcrypt-free, stripped, VERSIONINFO'd, minimal-resource and even
resource-FREE builds on CI's own MSYS2 CLANG64 toolchain were all flagged,
while the product binary scans clean on every platform.

So remove the stub and move self-update OUT of the process into install.ps1,
which runs while cbm is NOT running: Windows' image lock only blocks a
process from replacing ITSELF.  now prints the exact PowerShell
command (with the Unblock-File hint for Mark-of-the-Web); install.ps1 is
idempotent, so re-running it IS the update — it stops the daemon, renames the
running binary aside (the one mutation Windows permits on a running image),
publishes the new one, and sweeps retired copies.

Windows now matches Linux and macOS: ONE binary per platform.

  * packaging, install.ps1, npm and PyPI wrappers all carry a single binary
  * the launcher/payload ABI contract and ~2500 lines of stub state machinery
    are deleted
  * every daemon start, CLI call and hook fire loses a process spawn, a named
    pipe handshake and an stdio relay
  * test_windows_bundle_contract.sh is rewritten as an INVERTED contract: it
    now asserts no shipped surface can reintroduce a launcher/payload pair,
    and that install.ps1 retires the running binary before publishing

Verified: VirusTotal 0/67 on the packaged binary and 0/58 on install.ps1 (no
certificate involved); macOS and Linux full suites green; Windows guards all
green including the new update-handoff contract; npm 10/10; PyPI 3/3.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-28 17:03:01 +02:00
Martin Vogel e5c79418ca revert(install): restore the activation-transaction diagnostics to main
The refusal notes changed the error text the Windows launcher guards match
against, and test-windows-guards went red on this PR after they landed. They were
investigation instrumentation and they succeeded -- they turned 'staging
transaction open failed (status -3, os 0)' into ERROR_DISK_FULL and named the
exact site -- but a diagnostic that shifts a matched error string does not belong
in a release-blocking change.

Worth redoing deliberately on its own, with the guards' expectations updated in
the same commit.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-26 14:01:08 +02:00
Martin Vogel d2dee7b8ff diag(install): report the OS error from a failed staging copy
The copy loop's failure was reported as 'os 0' because the cleanup and close
calls after the loop clobber the thread's last error. Captures it at the failure
point, and separates a failed read from a failed write -- copying a payload that
another process still holds is a different problem from a full disk.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-26 09:45:54 +02:00
Martin Vogel 536d228631 diag(install): split the staging copy failure into named causes
The copy loop's five distinct failures -- short read/write, an empty source, a
failed fsync, a failed close, and a failed directory sync -- shared one branch
and one unnamed IO status. Exhaustive labelling identified this as the path the
Windows launcher guard's immediate reinstall takes; this names which of the five
actually fires.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-26 09:18:11 +02:00
Martin Vogel 6d95d4f8df diag(install): name every unlabelled staging IO refusal
Fourteen paths returned CBM_ACTIVATION_TRANSACTION_IO without setting a refusal
note, so the caller could only ever print 'status -3, os 0' -- an I/O error with
no cause. Identifying which one fires cost a ten-minute Windows guard run per
hypothesis, and two targeted guesses missed.

Labels every site so a single run names the path. Temporary labels; once the
guard's path is known they become descriptive.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-26 08:53:18 +02:00
Martin Vogel 15cbbc505a fix(install): name the target-directory refusal when preparing a transaction
activation_transaction_prepare refused an insecure target directory and
returned IO with no note, while destroy() on the way out clobbered
GetLastError -- so the caller printed 'staging transaction open failed (status
-3, os 0)': an I/O error with neither a cause nor an OS code.

That is the path the Windows launcher guard's immediate reinstall takes, and the
missing note is why each hypothesis about it cost a ten-minute guard run. A
target directory failing the exact-owner rule is a different bug from an
unreadable source, and now says so.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-26 08:30:05 +02:00
Martin Vogel e65e8ee42d fix(install): name which source refusal failed when staging
Three distinct refusals -- an insecure source directory, a failed snapshot, and
a missing source file -- collapsed into one boolean, and the frees on the way
out clobber GetLastError, so the caller reported 'staging transaction open
failed (status -3, os 0)': an I/O error with no cause and no refusal note.

The Windows launcher guard's immediate-reinstall step fails through exactly this
path, and with no cause reported each hypothesis cost a full ten-minute guard
run. Captures the OS error before the frees and names the predicate that
refused.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-26 07:58:55 +02:00
Martin Vogel 62821375c4 fix: harden daemon release paths and verification
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-24 16:08:42 +02:00
Martin Vogel 568e613141 test-infra: overlap the local ladder, open the serial tail, keep container I/O off virtiofs
Local runs left most of the machine idle: the serial tail ran sixteen
suites one at a time on otherwise-idle cores, the three platform legs
were launched by hand (usually sequentially), and every container-leg
object write crossed the virtiofs bind mount.

- run-tests-parallel.sh tail scheduling in two phases: the FLEX suites
  (timing-shaped but free of the shared per-account daemon runtime
  namespace) run CBM_TAIL_JOBS-wide (default 2), then the EXCL group —
  daemon-family plus the suites that drive daemon one-shots or
  supervisor rendezvous — runs strictly sequentially on a machine
  exactly as quiet as the old fully-serial tail gave it. The wave was
  already fed longest-first by the shard dealing order, so the drain-out
  no longer ends on a heavy straggler.
- ladder.sh: one maintained entry point for the full local push gate
  with the legs overlapped — lint, the Linux container suite, and the
  Windows VM suite in the background, the macOS suite in the
  foreground, one verdict per leg, logs kept per leg. A missing
  prerequisite fails its leg loudly instead of silently skipping.
- win.sh test-par now runs through vm-run-tests.sh (--par mode): the
  full parallel harness under the CI-shaped protected temp root with
  complete output. It previously ran under the MSYS-shared /tmp and
  piped through `tail -25` — the same truncated-blindness class that
  hid 40 Windows failures from the `test` command.
- docker-compose: build artifacts and the incremental fixture cache
  move to named volumes on the container VM's native filesystem. Object
  writes over the virtiofs bind mount are the container legs' largest
  avoidable I/O cost, and the fixture cache now survives across
  container runs.
- test_mem(win): the first valid full-parallel VM run proved working-set
  trimming beats the re-touch mitigation (19 MB resident of a 256 MB
  double-touch at 18 parallel suites). The RSS probe now VirtualLocks a
  64 MB span — locked pages are exempt from trimming, making the
  measurement pressure-immune — with bounded touch-and-sample retries
  when the lock is unavailable. Red-to-green under the same 18-job load.
- cli: the portable install's staging error now appends the activation
  refusal note (predicate, SID, object) like the managed path already
  does — a bare "activation transaction I/O failed" on a CI-only
  failure is undiagnosable without it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 01:33:58 +02:00
Martin Vogel f51745533c fix(win): reconcile the C suite with the managed-launcher contract
The first full Windows x64 CI run of this branch (earlier contract-step
walls had always aborted the leg) surfaced 40 failures: the managed
launcher's fail-closed install/update/uninstall gates and the suite's
POSIX-modeled activation tests were both new in this branch and had
never been reconciled, and they hid three real production bugs.

Production fixes:

- ui: config publishes with POSIX rename semantics. MoveFileExW's legacy
  replace cannot supersede a destination a reader holds open (the name
  lingers until the last handle closes, even with FILE_SHARE_DELETE), so
  a config save racing an open reader failed. The temp file is opened
  with DELETE access and renamed in-hand (bare-path target, NUL-padded
  rename record) before close, falling back to MoveFileExW on any
  rename-by-handle failure so non-NTFS config homes keep the previous
  behavior.
- subprocess(win): the child's redirected log handle now shares write
  access. POSIX places no lock on the redirected log, so a second
  producer may append while the child runs; the missing FILE_SHARE_WRITE
  mandatory-locked every other writer out, which the index-supervisor
  callback-isolation test legitimately exercises.
- cli(win): the portable uninstall path composes the managed binary name
  with its .exe suffix; it could never find (or remove) the installed
  binary before.
- activation: security-predicate refusals are now diagnosable. The
  transaction's predicates refuse without a usable OS last-error, which
  collapsed five distinct refusal classes into one blind "status -3,
  os 0" at every caller. Each refusing predicate records what refused —
  predicate, untrusted SID, and object path — first-wins so the deepest
  cause survives, and the staging error line appends it. This is what
  identified every Windows refusal in this batch, including the real
  install-failure class on drive-root-rooted payload directories
  (Authenticated Users holds inherited Modify there; profile-rooted
  directories do not).

Windows test dispatch (release binaries unchanged):

- Test builds (CBM_CLI_ENABLE_TEST_API, the existing test-object macro)
  compile the portable activation flow on Windows, reachable solely
  through the installed activation test seam and announced loudly on
  stderr; release binaries compile exclusively the managed transaction,
  so the dispatch polarity fails closed. The suite's install/update/
  uninstall calls route through a seam-if-absent wrapper, keeping the
  shared activation and agent-config semantics unit-covered on Windows;
  POSIX arms keep exercising the real activation machinery.
- Two new release-gate tests assert the fail-closed contract with the
  seam OFF: a portable payload may not self-update/self-uninstall, and
  install refuses to adopt or replace a foreign file at the canonical
  launcher path even under --force.
- Test-bug fixes the first valid Windows runs exposed: the version-
  cohort crash test reused one conflict record across the probe and the
  turnover acquire (whose entry memset erased the probe's detail before
  the assertions read it); the activation staged-path assertion now
  checks containment rather than a separator (the \\?\ namespace
  performs no forward-slash translation); the ui atomic test captures,
  cleans up, then asserts so a failure cannot poison later tests; the
  PATH-visibility guard is POSIX-only (Windows configures PATH through
  the user registry, not a shell rc).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 00:53:41 +02:00
Martin Vogel 34f9ad1b3d fix(daemon,win): CI-green fixes and a daemon-stability guard
Fixes surfaced while bringing the shared-coordination-daemon branch
green across macOS, the Linux containers, and the real Windows-ARM64 VM,
plus a new stability guard that caught one of them.

- daemon(win): the CLI teardown transition latched a failed release as
  permanent, but a Windows participant-state release must briefly
  try-hold the shared startup/legacy gates and legitimately collides
  with a concurrent one-shot's teardown; the release is retriable by
  contract (it always retains the transition), so parallel one-shot
  commands no longer report "CLI coordination cleanup failed" despite
  succeeding. Found by the new stability guard's churn section.
- cli/main(win): install/update/uninstall and the Augment hook-script
  removal derive the managed launcher's identity from its plain
  drive-form path (cli_windows_plain_utf8), not the \\?\ extended-length
  form, so agent-config ownership matches on Windows and uninstall no
  longer leaves the MCP entry or owned hook scripts behind.
- security: the `daemon start --open` browser launch is now shell-free
  (ShellExecuteW on Windows, cbm_exec_no_shell elsewhere) instead of
  system(); the CORS origin check spells out its two literal loopback
  URLs so the static URL audit sees a complete value. Both clear the
  Layer-1 allow-list audit.
- coordination: version-cohort lock retries now sleep a per-process
  jittered interval — fixed-period retries can phase-lock two
  participants so one starves. The activation-quiesce test's
  observation window now covers worst-case candidate staging (three
  tamper-defense hashes over a ~1 GB sanitizer binary exceed the old
  30 s budget on container I/O).
- smoke/soak: the agent-config smoke matches Windows config paths
  through their escaped-backslash quoted form; the soak parser skips the
  update-available banner before the JSON summary and gained a one-shot
  CLI admission-churn phase inside the RSS/FD leak window. The build-dir
  safety fixture creates a real symlink/junction (MSYS2 ln -s otherwise
  deep-copies) so its traversal-refusal contract is genuinely exercised,
  and the symlinked-agent-roots test demotes its planted links to an
  unprivileged owner when run as root so the refusal is really tested.
- tests(win): a new daemon-stability guard exercises the parameter
  surface, hook fail-open with its rate-limited notice, start-twice and
  occupied-port handling, busy-stop refusal against a live MCP session,
  kill -9 crash recovery, and sequential/parallel one-shot churn.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-22 17:48:20 +02:00
Martin Vogel 0baddf7887 daemon: permanent lifecycle, daemon-backed CLI/hooks, real-Windows hardening, long-path launcher transactions
Daemon lifecycle and Windows correctness, verified on a real Windows 11
ARM64 VM through the maintained test-infrastructure/vm drivers, plus the
macOS and Linux arm64 suites and the container lint gate.

Daemon lifecycle:

- daemon start/stop/status subcommands. `daemon start` launches a
  PERMANENT daemon (spawn shape is byte-exact argv; survives idle
  periods and session ends) and reports an already-active daemon
  instead of failing. Permanence is honored at every stop latch:
  last-committed-client disconnect, host initial-client window,
  coordinator release, and application final-session close — a
  permanent daemon also keeps admitting new sessions after its last
  one closes.
- daemon stop refuses while sessions are active and lists the blocking
  peers (pid/role) that must finish first; an idle daemon drains
  through the activation-shutdown machinery with the ACK ordered after
  connection interrupts. A second stop is idempotent. The wire ops are
  no-cohort first-frame requests with peer fingerprint authentication,
  so stop/status never conflict with an exact-build admission gate.
- One-shot CLI commands now execute through the daemon (index workers
  keep their local supervised path). A cold CLI run that had to spawn a
  temporary daemon prints a hint that `daemon start` removes the
  per-command startup tax; a warm daemon is recycled silently.
- Hooks are connect-only fail-open: with no daemon present the hook
  emits a visible, rate-limited notice (Claude-dialect systemMessage
  plus stderr for other dialects) and always exits 0 — augmentation is
  never allowed to block the caller's tool use.
- Version skew: a newer-build client automatically drains an
  older-build permanent daemon (strict semantic-version triples only;
  dev builds never auto-drain) and the build-conflict message names
  `cbm daemon stop` as the manual escape hatch.

Windows IPC/runtime (real-VM verified):

- ipc(win): persistent pending overlapped ConnectNamedPipe. The accept
  path used to destroy its listening pipe instance on every 20 ms poll
  timeout; a client attaching in the teardown window was severed or left
  on an orphaned pipe object whose HELLO no server handle could ever
  read, absorbing the connect until the client's own timeout expired.
  The pending connect now survives poll timeouts and nothing is
  destroyed while a client could be attaching.
- ipc(win): drain-before-close for final responses. Closing a named-pipe
  server handle can discard a just-sent response before the peer reads
  it (POSIX stream sockets never lose buffered data on close). A bounded
  cbm_daemon_ipc_connection_drain (read-until-peer-EOF; no-op on POSIX,
  immediate on interrupted connections) now precedes close in
  runtime_worker_finish and runtime_reject_inline, so hello-conflict,
  capacity and disconnect acknowledgements reliably reach the peer.
- runtime: CLOSE_INTENT wire frame. A Windows named-pipe client has no
  transport half-close, so close_begin now announces departure with an
  explicit frame (ordered after APPLICATION_CANCEL, before the local
  interrupt); the server releases the client's admission on receipt
  instead of waiting for the handle to close. Admission-drop timing is
  now identical to POSIX shutdown() semantics on every platform.
- runtime(win): client close cancellation. close_begin serializes with
  request publication under the send lock, best-effort sends the active
  token's APPLICATION_CANCEL frame, then interrupts local I/O; the
  server cancels MCP/subprocess work promptly. Contract tests accept
  both correct outcomes (interrupted transport or decoded CANCELLED).
- runtime: activation acknowledgement ordering. The activation ACK is
  the requester's license to act on "snapshotted and draining", so every
  connection interrupt is now initiated before the ACK is sent; a
  session could previously get one more request serviced after the
  requester observed the ACK.
- service(win): deadline-bounded private-file prepare. The conflict-log
  prepare retry loop (100 x Sleep(2), which rounds up to the ~16 ms
  timer granularity) burned ~1.6 s against permanently obstructed paths,
  stalling hello rejections past the client's timeout. The retry budget
  is now a 250 ms deadline; transient share collisions still retry.
- subprocess(win): cmd.exe /C payload encoder quotes metacharacters
  correctly (root cause of the git-on-Windows failure cluster).
- watcher: SHA-256 buffer sizing (CBM_SZ_64 -> CBM_SZ_128) and a native
  Windows stop/unwatch cancellation test with exact-image verification.
- httpd: send_all writes in bounded 64 KiB slices. A single giant
  nonblocking send() on Windows is absorbed wholesale into AFD kernel
  buffering regardless of SO_SNDBUF, so send deadlines and interrupts
  could never engage against a slow peer (and the full payload was
  pinned in nonpaged pool). Slicing restores a deterministic
  backpressure point; a test hook pins SO_SNDBUF for the deadline and
  interrupt tests.
- ui/http: shutdown lifecycle — interrupt checks, response-wide send
  deadline, explicit connection states, refusal to free a server while
  a listener-owned connection is active.

Windows long-path support:

- Central path-aware wide conversion (canonicalize via GetFullPathNameW
  and prepend the extended-length prefix for absolute paths >=240) at
  the compat chokepoints (cbm_fopen/compat_fs/mkstemp/mkdtemp), sqlite
  store opens, and the daemon build-fingerprint/log paths. Deep managed
  installs (a 64-hex generation directory routinely exceeds MAX_PATH)
  now index, stage and activate correctly.
- activation transaction: its own file APIs and the component-walking
  ancestry validators now operate in the extended-length namespace;
  the launcher path is canonicalized (and prefixed when deep) once at
  entry so every downstream exact-string comparison stays
  form-consistent.
- Executable self-resolution uses the wide APIs (GetModuleFileNameW,
  GetFileAttributesW) so non-ASCII install paths survive argv[0]
  resolution.

Windows launcher install/uninstall transaction:

- FileRenameInfoEx names are NUL-terminated in an over-allocated
  buffer. FileNameLength governs per the contract, but filter drivers
  read FileName as NUL-terminated and appended adjacent heap bytes to
  created names — a flaky, garbage-suffixed rename target. Both the CLI
  and the launcher rename helpers are fixed.
- Uninstall retires state via rename-aside (.cbm ->
  .cbm-retired-v1-<tag>-<pid>) with the retired tag shortened to 16 hex
  chars so the bare rename target stays under the FileRenameInfoEx
  NT-conversion ceiling at guard depths; 64 bits still uniquely
  identify the generation.
- When the running launcher's mapped generation backings pin .cbm
  against rename, the backings are relocated to activation-<pid>-N
  .retired tombstones beside the install (a mapped image may be renamed,
  never deleted; the launcher's liveness-guarded sweep reclaims stale
  tombstones). Every relocation is recorded, and a FAILED uninstall
  reverses the moves after restoring .cbm — via MoveFileExW with
  extended-length paths on both arguments, since the deep generation
  target is beyond the handle-based rename's bare-path reach — so a
  restored install keeps its generation backings and stays runnable.
- After a committed uninstall the retired tree's backings are relocated
  out so the tree is shallow enough for the detached cleanup's rd, and
  the cleanup's working directory strips the extended-length prefix
  (CreateProcessW lpCurrentDirectory silently ignores prefixed paths).
- Files created under Administrators-default-owner directories
  (CopyFileW destinations, CREATE_NEW tombstones, probe directories)
  are explicitly owner-stamped so the exact-owner validators hold on
  runner images; guard fixtures stamp hand-built trees the same way.

Diagnostics, tests and infra:

- diagnostics: discovery is now an always-delivered JSON control record
  (new cbm_log_control) that survives CBM_LOG_LEVEL suppression and
  paths containing spaces; placement honors $TMPDIR with /tmp fallback
  via a diagnostics-local helper; the soak parser reads the JSON record;
  documented in docs/CONFIGURATION.md. Red-first coverage for suppressed
  log levels, TMPDIR-with-spaces, and native Windows output-contract
  assertions.
- tests(win): daemon_ipc/daemon_frontend fixtures now build endpoint
  parents with production-shaped ancestry (LocalAppData on Windows, via
  th_secure_runtime_parent_new) — the runtime ancestry validation
  correctly refuses temp roots whose ancestors grant mutation rights to
  Authenticated Users (C:/msys64/tmp, GitHub-runner work dirs) — and
  drive the documented startup-owner publication flow before reading
  generation-bound endpoint addresses. This turns the 26 Windows
  failures previously visible in CI's full-test job green without
  weakening any validation.
- tests(win): the launcher guard covers the full permanent-launcher
  contract including failed-uninstall restore and immediate reinstall
  after uninstall; new daemon lifecycle and reworked hook-augment
  guards run the start/recycle/stop flow end to end.
- tests: CBM_SKIP_PERF is now actually consumed by the test runner
  (it was set by CI but never read, so perf suites ran everywhere);
  four throughput/bench suites are classified as perf, the heavy
  store_arch suite moved to the slow-timeout tier, and two
  wall-clock-sensitive assertions were rewritten as invariant checks
  with coarse hang-detector backstops.
- build/test infra: build-dir safety contract, UI dev-proxy security
  contract, soak daemon-recovery contract, path-safety helper, the
  Windows VM worktree-sync contract wired into scripts/test.sh, and
  vm/win.sh guards building its clean embedded-UI product in an
  isolated BUILD_DIR so it cannot clobber the incremental test build.
  provision-windows.sh now installs Node.js for the guards UI build.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-21 15:32:15 +02:00
Martin Vogel ba91c6c396 fix(cli): resolve msys-form profile paths and create the staging-root chain
cli_windows_staging_root builds the per-user install staging root from
LOCALAPPDATA/USERPROFILE, but msys and CI harnesses export those in POSIX
drive form ("/c/Users/..."), which the CRT cannot resolve, and the target's
AppData\Local ancestors may not exist yet on a fake-profile fixture. Rewrite
a leading "/c/" to native "C:/" before use, create the whole ancestor chain
(a single mkdir left the root missing), and fall back to the temp root only
after verifying the directory exists. Also name the failing sub-step when
private staging cannot even create its directory, so the opaque "did not
remain runnable" message is no longer the only signal.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 16:55:44 +02:00
Martin Vogel aa90491792 fix(cli): stage Windows install transactions under a per-user root
The activation transaction and the release-descriptor probe validate the
full ancestor chain of everything they stage: every path component must
have a trusted owner and grant mutation rights only to trusted identities.
That walk is deliberate TOCTOU defense and stays strict. The staging
directory, however, came from the process temp root, which routinely lives
in a tree that legitimately fails the walk through no fault of ours: under
msys2 TMP resolves inside the msys install tree (C:\msys64\tmp), and on
GitHub runners inside the runner work directory, both of which carry broad
inherited grants such as Authenticated Users modify on an upper component.
The staged pair then failed validation ("launcher descriptor candidate is
unsafe" / the smoke Phase 8 --version probe refusal) even though the staged
files themselves were private.

Add cli_windows_staging_root(): stage install and update transactions under
%LOCALAPPDATA%\codebase-memory-mcp, whose ancestor chain is user-owned end
to end, so the strict walk passes by construction instead of by environment
luck. Falls back to USERPROFILE\AppData\Local and finally the process temp
root when the environment is degraded.

Also make the staging observable where it previously failed blind:
cli_windows_stage_private_file reports which transaction step failed with
the OS error code, and cbm_mkdtemp warns once if the private security
descriptor cannot be applied and it falls back to default directory
security. A blind boolean here cost several blind CI round-trips.

win.sh: scp targets need native C:/ paths on Windows OpenSSH.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 14:32:05 +02:00
Martin Vogel dc72a5b639 style(cli): clang-format the private-staging diagnostic lines
The Phase 8 staging diagnostic added two long lines that clang-format-20
(the CI formatter) wraps differently. Reformat them so lint passes; no
behavior change.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 10:31:38 +02:00
Martin Vogel 0a8b77c3f2 fix(activation): stamp staged-file owner to the token user
activation_windows_security_init built the creation security descriptor with
a protected current-user DACL but never set an owner, so a file created with
these attributes inherited the token's default owner. Under an
Administrators-default-owner policy -- standard on Windows Server and on
GitHub's elevated Windows runners -- that default is BUILTIN\Administrators,
not the calling user. Every staged file is then validated with the strict
activation_windows_owner_is_current check, which rejected the
Administrators-owned file and made private staging fail with "launcher/payload
source pair did not remain runnable after private staging" (smoke Phase 8).

Stamp the owner explicitly to the token user in the creation descriptor, the
same fix the daemon side already applies. This keeps the strict owner check
intact -- the staged file genuinely becomes current-user-owned -- so it is not
a validation relaxation; setting the owner to our own token user needs no
extra privilege. Complements the launcher's trusted-owner acceptance, which
covers the installed binary that a non-admin user cannot re-stamp.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 09:27:39 +02:00
Martin Vogel 9ef0f3168c diag(cli): name which private-staging sub-step made the pair unrunnable
cli_windows_prepare_install_pair folded launcher staging, payload staging,
the payload fingerprint match, and the staged-launcher --version probe into
one bool, so the caller could only report "launcher/payload source pair did
not remain runnable after private staging" without saying which sub-step
failed. Windows cbm_exec_no_shell does not inherit the child's stderr, so the
launcher's own refusal reason never reaches the log either. Split the checks
so each failing sub-step prints a distinct error, turning the opaque Phase 8
smoke failure into a precise signal on the next run.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 09:24:27 +02:00
Martin Vogel 218438927e fix(cli): Windows managed install --dry-run states dry-run mode
The Windows managed-install path (cli_windows_managed_install_activate)
printed its plan as "Would install managed launcher -> ..." but never
the words "dry-run", unlike the POSIX path's "(dry-run — no files were
modified)" - so `install --dry-run` did not clearly indicate dry-run
mode (smoke Phase 6a, confirmed by echoing the captured output).
Append the same dry-run indicator to the Windows plan line.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 07:44:58 +02:00
Martin Vogel 30b5f12eef fix(cli): install --dry-run always reports dry-run, even on a plan check
A dry-run mutates nothing (every install mutation is guarded by
!dry_run), but on Windows the activation plan's PATH/agent-config probe
can return non-OK, and the caller then returned early WITHOUT the
dry-run summary - so `install --dry-run` neither indicated dry-run mode
nor exited success (smoke Phase 6a). For a dry-run, emit the
"(dry-run — no files were modified)" indicator and return CLI_OK on
that path (the plan simply noted something), and log the underlying
status to stderr for triage. POSIX is unaffected (its dry-run
activation returns OK, so the branch is never taken).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-19 06:50:47 +02:00
Martin Vogel 5d8a9421fb Merge remote-tracking branch 'origin/main' into feat/shared-coordination-daemon
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	src/mcp/mcp.c
#	tests/test_cli.c
2026-07-18 13:48:06 +02:00
Martin Vogel 89c7c98f00 fix: support standard Windows profile ACLs
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 12:26:17 +02:00
Martin Vogel cc5f71bdff fix(hook,smoke): teach the last two consumers the tree output shape
The PR CI caught two consumers the format migration missed — both parsed
tool output by its old shape and failed silently or with a wrong count:

- hook-augment built its PreToolUse additionalContext by reading the
  legacy 'results' object-array from search_graph format:json; after the
  json-tree reshape that key no longer exists, so the hook emitted nothing
  (caught end-to-end by tests/windows/test_hook_augment.py). The parser
  now walks groups -> qn_prefix/file + column-ordered rows. A new local
  test feeds the parser the LIVE server envelope, so any future drift
  between response shape and hook parser fails in the normal suite on
  every platform — not only in the Windows CI guard. Reproduced RED
  (ctx NULL, the exact CI failure) before the fix, GREEN after.

- smoke-test.sh carried six old-shape parsers (callers[N], rows[N]{,
  clusters[N], semantic[N]{, and the assert_toon_table helper); the tree
  headers never matched, so trace verification read 0 callers. All six
  updated to the tree contract; the full smoke script passes end-to-end
  locally.

cli suite 222 passed; lint clean; scripted smoke ALL PASSED.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 10:36:08 +02:00
Martin Vogel cb896a3de4 fix: harden cross-platform daemon startup
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 02:43:14 +02:00
Martin Vogel 4693b625e0 fix: stabilize cross-platform daemon smoke
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 02:22:37 +02:00
Martin Vogel 83c137d2a5 feat: complete shared daemon lifecycle
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 01:26:08 +02:00
Martin Vogel 0e00ef5702 feat: coordinate concurrent CBM sessions
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-16 19:20:46 +02:00
Martin Vogel aa2366811b fix: eliminate installer filesystem races
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-14 21:13:52 +02:00
Martin Vogel 50df0997cf fix: scope legacy shim cleanup to Windows
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-14 19:25:45 +02:00
Martin Vogel 5ae6ef7875 fix: migrate Windows Claude hook scripts safely
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-14 19:08:54 +02:00
Martin Vogel 9cda693acb fix: converge exact-owned hook updates
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 06:28:17 +02:00
Martin Vogel ecc6a5fdcd fix: migrate legacy Windows hook commands
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 05:21:36 +02:00
Martin Vogel 62d191b6d1 fix: restore cross-platform lint and builds
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:46:04 +02:00
Martin Vogel a537d3bd84 fix(cli): make Windows Claude hooks shell portable
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:18:10 +02:00
Martin Vogel 2040488cf4 fix(cli): preserve hook compatibility after rebase
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:00:44 +02:00
Martin Vogel d6c9d28bf5 style: apply current C formatting
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:00:32 +02:00
Martin Vogel f0db224140 feat: add coverage-aware agent integrations
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:50:03 +02:00
Martin Vogel a3903caa0b feat(cli): expand agent integration coverage
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:50:02 +02:00
Martin Vogel da4c00671f Merge pull request #1057 from DeusData/fix/929-windows-cmd-hooks
fix(install): Windows hook scripts get .cmd form; legacy files removed
2026-07-12 21:30:21 +02:00
Martin Vogel 5c9c554a2b Merge pull request #1055 from DeusData/fix/858-hook-deadline
fix(hook): raise + env-configure the augment deadline, breadcrumb on fire
2026-07-12 20:52:21 +02:00