mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-27 06:53:11 +08:00
Rebased onto current main and reworked in response to review. Three of the four
Windows reds were fixed upstream since the branch was cut at b075f05, so they are
now green regression guards; the fourth stays a genuine known-red.
- test_non_ascii_path.py (#636/#357): green guard - fixed by #700 (per-pass
readers now route through cbm_fopen -> _wfopen). Re-verified green on main.
- test_hook_augment.py (#618): green guard - fixed by #619 (cbm_is_walkable_abs_path
accepts drive-letter X:/ cwd). Re-verified green on main.
- test_ui_drive_listing.py (#548): rewritten. The fix exposes drives via a new
roots[] field, not the dirs[] array the old test asserted (which would stay red
against fixed code). Now asserts every fixed drive is in roots and browsable.
Re-verified green on main (drives C:/D:/E:).
- test_cli_non_ascii_arg.py (#423/#20): unchanged - main() is still narrow-argv
with no wide command line, so this remains genuinely red (the keeper).
- scripts/test-windows.ps1: split green guards (gate CI) from opt-in known-reds;
add -GuardsOnly; run indexing in-process (CBM_INDEX_SUPERVISOR=0) so a guard
reflects the path/hook/drive fix under test, not the index-worker spawn path.
- .github/workflows/_test.yml: new test-windows-guards job builds the product+UI
binary (scripts/build.sh --with-ui) and runs the guards with -GuardsOnly so
#700/#619/#548 stay enforced on Windows CI.
- RED_TEST_ANALYSIS.md: refreshed to record the landed fixes and current status.
Signed-off-by: Flipper <jacobphilipp@ymail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
4.6 KiB
Python
110 lines
4.6 KiB
Python
r"""GREEN regression guard — the PreToolUse hook augmenter fires on Windows.
|
|
|
|
Guards the fix for issue #618 (landed on main via #619) at the product surface.
|
|
|
|
`codebase-memory-mcp hook-augment` is the non-blocking Claude Code PreToolUse
|
|
Grep/Glob augmenter: given a hook payload it should emit a `hookSpecificOutput`
|
|
with `additionalContext` listing graph symbols that match the searched token.
|
|
|
|
Before #619 it emitted nothing for every payload on Windows: `src/cli/hook_augment.c`
|
|
gated on POSIX-style absolute paths (`cwd[0] == '/'` and a walk-up loop over
|
|
`dir[0] == '/'`). A Windows `cwd` is a drive-letter path (`C:\...` / `C:/...`),
|
|
so `cwd[0]` was never `'/'` and the augmenter bailed before querying the graph.
|
|
#619 added `cbm_is_walkable_abs_path` (accepts `X:/` drive-letter roots), so the
|
|
augmenter now fires for a drive-letter cwd.
|
|
|
|
This test indexes a repo with a known symbol, confirms `search_graph` finds it
|
|
(control — proves the index and project name are fine), then invokes
|
|
`hook-augment` exactly as the installed PreToolUse hook does and asserts a
|
|
`hookSpecificOutput` payload is produced. It fails (red) if that fix regresses.
|
|
Also passes on Linux/macOS (`cwd` starts with `/`).
|
|
|
|
Exit code: 0 == augmenter fired (green), 1 == no-op (regression), 2 == setup error.
|
|
|
|
Usage:
|
|
python test_hook_augment.py <path-to-codebase-memory-mcp[.exe]>
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
SYMBOL = "someIndexedSymbol"
|
|
SRC = "export function %s(a: number): number { return a + 1; }\n" % SYMBOL
|
|
|
|
|
|
def run_cli(binary, cache, args, stdin=None, timeout=120):
|
|
env = dict(os.environ)
|
|
env["CBM_CACHE_DIR"] = cache
|
|
return subprocess.run([binary] + args, capture_output=True, timeout=timeout,
|
|
env=env, input=stdin)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("usage: python test_hook_augment.py <binary>")
|
|
return 2
|
|
binary = os.path.abspath(sys.argv[1])
|
|
if not os.path.exists(binary):
|
|
print("FAIL: binary not found: %s" % binary)
|
|
return 2
|
|
|
|
work = tempfile.mkdtemp(prefix="cbm_win_hook_")
|
|
try:
|
|
repo = os.path.join(work, "repo")
|
|
os.makedirs(os.path.join(repo, "src"), exist_ok=True)
|
|
with open(os.path.join(repo, "src", "m.ts"), "wb") as f:
|
|
f.write(SRC.encode("utf-8"))
|
|
cache = os.path.join(work, "cache")
|
|
os.makedirs(cache, exist_ok=True)
|
|
|
|
# repo_path / cwd in the forward-slash drive form Claude Code passes.
|
|
repo_fwd = repo.replace("\\", "/")
|
|
idx = run_cli(binary, cache, ["cli", "index_repository",
|
|
json.dumps({"repo_path": repo_fwd})])
|
|
idx_out = (idx.stdout or b"").decode("utf-8", "replace")
|
|
if '"nodes"' not in idx_out:
|
|
print("SETUP FAIL: index did not run:\n%s" % idx_out[:300])
|
|
return 2
|
|
|
|
# Control: prove the symbol is indexed and queryable.
|
|
lp = run_cli(binary, cache, ["cli", "list_projects", "{}"])
|
|
projects = json.loads((lp.stdout or b"").decode("utf-8", "replace"))["projects"]
|
|
name = projects[0]["name"]
|
|
sg = run_cli(binary, cache, ["cli", "search_graph",
|
|
json.dumps({"label": "Function",
|
|
"name_pattern": ".*%s.*" % SYMBOL,
|
|
"project": name})])
|
|
if SYMBOL not in (sg.stdout or b"").decode("utf-8", "replace"):
|
|
print("SETUP FAIL: control search_graph did not find %s" % SYMBOL)
|
|
return 2
|
|
print("control: search_graph finds %s in project %s" % (SYMBOL, name))
|
|
|
|
# Invoke hook-augment exactly as the installed PreToolUse hook does.
|
|
payload = json.dumps({
|
|
"hook_event_name": "PreToolUse",
|
|
"tool_name": "Grep",
|
|
"cwd": repo_fwd,
|
|
"tool_input": {"pattern": SYMBOL},
|
|
}).encode("utf-8")
|
|
ha = run_cli(binary, cache, ["hook-augment"], stdin=payload, timeout=60)
|
|
out = (ha.stdout or b"").decode("utf-8", "replace").strip()
|
|
print("hook-augment rc=%d stdout=%r" % (ha.returncode, out[:200]))
|
|
|
|
fired = ("hookSpecificOutput" in out) and ("additionalContext" in out)
|
|
if fired:
|
|
print("\nGREEN: PreToolUse augmenter emitted additionalContext.")
|
|
return 0
|
|
print("\nREGRESSION (red): hook-augment produced no hookSpecificOutput on "
|
|
"Windows (drive-letter cwd rejected — has the #619 "
|
|
"cbm_is_walkable_abs_path handling in hook_augment.c regressed?).")
|
|
return 1
|
|
finally:
|
|
shutil.rmtree(work, ignore_errors=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|