mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:27:03 +01:00
Repoint MCP paths; parallelise mutation gate
The mutation hook alone took 51 s, so no commit touching the session_autopsy test could fit the 60 s cap. Mutants now run 4 wide (19 s). Two oldest evidence files pruned for the 60-file cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWYFhDShgeHByh6yT9L
This commit is contained in:
parent
1ee9035918
commit
2a788b741d
@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"intent": "Make book-guard openable from the i3 bar the same way screen-locker is: a [book_guard] block shows this month's paper-book pace, and a click opens the book-guard desktop window. Move the shared pins to gatelock 0.9.0 (the new shared morning-session reader) and today's newest stable releases.",
|
|
||||||
"scope": [
|
|
||||||
"linux_configuration/i3blocks/book_guard.sh - new block script",
|
|
||||||
"linux_configuration/i3blocks/config - [book_guard] entry next to [screen_locker]",
|
|
||||||
"linux_configuration/tests/test_i3blocks_book_guard.sh - behaviour test",
|
|
||||||
"meta/requirements.txt - gatelock 0.9.0 and newest-stable pins",
|
|
||||||
"Non-goal: the stale tracked linux_configuration/i3/config (it already lacks the workout-locker and leetcode-guard exec lines); the live ~/.config/i3/config got the book-guard exec line."
|
|
||||||
],
|
|
||||||
"changes": [
|
|
||||||
"book_guard.sh reads the state.json book-guard publishes into ~/data/cloud/Reading on every inbox pass and gate run (no server, one jq pass): green on pace, yellow behind but unlocked (escaped / free day / unlocked tag), red when locked, orange when missing or older than 45 min, red with DISARMED when book-guard.timer is inactive. A click runs book-guard-desktop via setsid, which opens another window if it is already serving.",
|
|
||||||
"meta/requirements.txt: gatelock 0.8.2 -> 0.9.0; anthropic 1.11.0, cryptography 50.0.2, flake8-bugbear 26.9.30, geopandas 1.2.0, howlongtobeatpy 1.0.24, pyflakes 4.0.1, selenium 4.50.0, mypy 2.4.0, pylint 4.1.1, ruff 0.16.10."
|
|
||||||
],
|
|
||||||
"verification": [
|
|
||||||
{
|
|
||||||
"command": "bash linux_configuration/tests/test_i3blocks_book_guard.sh",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "on pace / locked / escaped / free day / other / stale / disarmed / missing / broken / click all asserted; systemctl and the launcher stubbed on PATH."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "bash linux_configuration/tests/test_i3blocks_efficiency.sh",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "All i3blocks efficiency regression tests passed."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "~/.config/i3blocks/book_guard.sh (live state.json)",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "'📖 0/300 · 10p behind (escaped)' #F1FA8C; block live in the bar after i3-msg reload."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": ".venv/bin/python -m pytest python_pkg/code_tutor (the only anthropic/geopandas/selenium/cryptography importer)",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "217 passed on anthropic 1.11.0."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "pre-commit run --files <changed-files>",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "all hooks passed, including dependency-freshness."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"risks": [
|
|
||||||
"cryptography 50.0.2 stays outside pyopenssl/mitmproxy's declared ranges, as 50.0.1 already was.",
|
|
||||||
"A click with book-guard-desktop missing does nothing (errors are silenced so the bar never breaks)."
|
|
||||||
],
|
|
||||||
"rollback": [
|
|
||||||
"git revert this commit and remove ~/.config/i3blocks/book_guard.sh; i3-msg reload.",
|
|
||||||
"Confirm the bar still renders [screen_locker] and the other blocks."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"intent": "blender-mcp-host/serve and the session_autopsy hook test point at the relocated MCP server checkouts (~/.local/share/mcp-servers, ~/.claude/mcp-servers) instead of the removed ~/src/mcp-servers.",
|
||||||
|
"scope": [
|
||||||
|
"linux_configuration/scripts/blender-mcp-host",
|
||||||
|
"linux_configuration/scripts/blender-mcp-serve",
|
||||||
|
"linux_configuration/tests/test_session_autopsy_hooks.py",
|
||||||
|
"Non-goal: moving or re-cloning the MCP servers themselves"
|
||||||
|
],
|
||||||
|
"changes": [
|
||||||
|
"blender-mcp-host: ADDON_PATH and the embedded start_addon spec path now /home/kuhy/.local/share/mcp-servers/blender-mcp/addon.py",
|
||||||
|
"blender-mcp-serve: SERVER_DIR now /home/kuhy/.local/share/mcp-servers/blender-mcp",
|
||||||
|
"test_session_autopsy_hooks.py: expected PYTHONPATH is {home}/.claude/mcp-servers/session-autopsy; comment names kuhyx/claude-config"
|
||||||
|
],
|
||||||
|
"verification": [
|
||||||
|
{
|
||||||
|
"command": "python -m pytest linux_configuration/tests/test_session_autopsy_hooks.py -q -p no:randomly --no-cov",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "12 passed in 4.12s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "grep -c src/mcp-servers linux_configuration/scripts/blender-mcp-host linux_configuration/scripts/blender-mcp-serve linux_configuration/tests/test_session_autopsy_hooks.py",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "0 matches in each of the three files"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"risks": [
|
||||||
|
"Hardcoded absolute /home/kuhy paths still break on another account or machine",
|
||||||
|
"If the checkout under ~/.local/share/mcp-servers is missing, blender-mcp-serve fails at start"
|
||||||
|
],
|
||||||
|
"rollback": [
|
||||||
|
"git revert the commit; paths return to ~/src/mcp-servers",
|
||||||
|
"Re-run test_session_autopsy_hooks.py and start blender-mcp-serve once to confirm"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"intent": "The session_autopsy mutation gate finishes in about 19 s instead of about 51 s so a commit touching it fits the 60 s per-action cap.",
|
||||||
|
"scope": [
|
||||||
|
"meta/scripts/mutate_session_autopsy_hooks.py",
|
||||||
|
"Non-goal: changing the mutation definitions or the pass/fail rule"
|
||||||
|
],
|
||||||
|
"changes": [
|
||||||
|
"Mutants run in a ThreadPoolExecutor (4 workers) via _run_mutant; each uses its own hook copy and its own coverage file",
|
||||||
|
"Per-mutant report lines are still printed in MUTATIONS order after all finish"
|
||||||
|
],
|
||||||
|
"verification": [
|
||||||
|
{
|
||||||
|
"command": "python3 meta/scripts/mutate_session_autopsy_hooks.py (before)",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "PASS: all 12 tests killed, 51.4 s wall"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "python3 meta/scripts/mutate_session_autopsy_hooks.py (after)",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "PASS: all 12 tests killed, same per-mutant kill lists, 19.1 s wall"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"risks": [
|
||||||
|
"Four concurrent pytest runs use more CPU and RAM for a few seconds",
|
||||||
|
"A test that shares global state across processes could become flaky under concurrency"
|
||||||
|
],
|
||||||
|
"rollback": [
|
||||||
|
"git revert the commit; mutants run sequentially again",
|
||||||
|
"Re-run the script and expect PASS in about 51 s"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"intent": "Remove python_pkg/wsg_grabber from the monorepo now that it lives in its own private repo, kuhyx/wsg-grabber, with its full history; and turn main's CI green again (hypothesis released 6.168.3).",
|
|
||||||
"scope": [
|
|
||||||
"python_pkg/wsg_grabber/ deleted (package, tests, README, install.sh)",
|
|
||||||
"AGENTS.md (CLAUDE.md symlink): wsg-grabber added to the extracted-repos list",
|
|
||||||
"meta/requirements.txt: hypothesis 6.168.2 -> 6.168.3, the only item the Pre-commit checks and dependency freshness jobs reported behind on 33f0fee3",
|
|
||||||
"Non-goal: the historical mentions in docs/DOCS-python-split-recipes.md and docs/DOCS-restructure-live-state.md stay; they record what happened here"
|
|
||||||
],
|
|
||||||
"changes": [
|
|
||||||
"git filter-repo kept the 14 commits that touched python_pkg/wsg_grabber; the standalone setup is kuhyx/wsg-grabber 6b6d4b3",
|
|
||||||
"/usr/local/bin/wsg-grabber regenerated by ~/src/wsg-grabber/install.sh, so the launcher no longer depends on this repo"
|
|
||||||
],
|
|
||||||
"verification": [
|
|
||||||
{
|
|
||||||
"command": "~/src/wsg-grabber: .venv/bin/python -m pytest -p no:cacheprovider -q",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "303 passed, TOTAL 1645 stmts / 342 branches, 100.00% coverage; pre-commit run --all-files clean there"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "vm run wsg 'sudo bash ~/install.sh' then on the host: sudo bash ~/src/wsg-grabber/install.sh && wsg-grabber stats",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "vmbox: packages installed, launcher written with PYTHONPATH=/home/arch, --uninstall removed it. Host: launcher PYTHONPATH=/home/kuhy/src/wsg-grabber; stats reads the live index (26898 known, 706 kept, 200 passed, 25992 purged)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "gh run view 36399404250 --log-failed",
|
|
||||||
"result": "pass",
|
|
||||||
"evidence": "the only freshness finding was pypi:hypothesis 6.168.2 -> 6.168.3 at meta/requirements.txt:42, bumped here"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"risks": [
|
|
||||||
"Anything outside git that pointed at ~/src/testsAndMisc/python_pkg/wsg_grabber would break; the only such artefact found was /usr/local/bin/wsg-grabber, already repointed."
|
|
||||||
],
|
|
||||||
"rollback": [
|
|
||||||
"git revert this commit to restore the package, then rerun its install.sh to point the launcher back here",
|
|
||||||
"Validate with `wsg-grabber stats`"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -20,7 +20,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_NAME="$(basename "$0")"
|
SCRIPT_NAME="$(basename "$0")"
|
||||||
readonly SCRIPT_NAME
|
readonly SCRIPT_NAME
|
||||||
readonly ADDON_PATH="/home/kuhy/src/mcp-servers/servers/blender-mcp/addon.py"
|
readonly ADDON_PATH="/home/kuhy/.local/share/mcp-servers/blender-mcp/addon.py"
|
||||||
readonly START_SCRIPT="/home/kuhy/.local/share/blender-mcp-host/start_addon.py"
|
readonly START_SCRIPT="/home/kuhy/.local/share/blender-mcp-host/start_addon.py"
|
||||||
readonly LOG_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/blender-mcp-host.log"
|
readonly LOG_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/blender-mcp-host.log"
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ import importlib.util
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
_SPEC = importlib.util.spec_from_file_location(
|
_SPEC = importlib.util.spec_from_file_location(
|
||||||
"blendermcp_addon", "/home/kuhy/src/mcp-servers/servers/blender-mcp/addon.py")
|
"blendermcp_addon", "/home/kuhy/.local/share/mcp-servers/blender-mcp/addon.py")
|
||||||
_MOD = importlib.util.module_from_spec(_SPEC)
|
_MOD = importlib.util.module_from_spec(_SPEC)
|
||||||
sys.modules["blendermcp_addon"] = _MOD
|
sys.modules["blendermcp_addon"] = _MOD
|
||||||
_SPEC.loader.exec_module(_MOD)
|
_SPEC.loader.exec_module(_MOD)
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
readonly HOST_SCRIPT="/home/kuhy/.local/bin/blender-mcp-host"
|
readonly HOST_SCRIPT="/home/kuhy/.local/bin/blender-mcp-host"
|
||||||
readonly SERVER_DIR="/home/kuhy/src/mcp-servers/servers/blender-mcp"
|
readonly SERVER_DIR="/home/kuhy/.local/share/mcp-servers/blender-mcp"
|
||||||
readonly UV="/home/kuhy/.local/bin/uv"
|
readonly UV="/home/kuhy/.local/bin/uv"
|
||||||
|
|
||||||
# stdout is the MCP JSON-RPC channel -- any stray byte written there corrupts
|
# stdout is the MCP JSON-RPC channel -- any stray byte written there corrupts
|
||||||
|
|||||||
@ -144,16 +144,16 @@ class TestSessionEndHookLaunchesIngest:
|
|||||||
json.dumps({"transcript_path": str(transcript)}),
|
json.dumps({"transcript_path": str(transcript)}),
|
||||||
)
|
)
|
||||||
argv = _await_launch(record)
|
argv = _await_launch(record)
|
||||||
# session_autopsy was extracted from this monorepo to kuhyx/mcp-servers;
|
# session_autopsy was extracted from this monorepo to kuhyx/claude-config
|
||||||
# the hook must launch it from there, not from a python_pkg that no longer
|
# (~/.claude/mcp-servers); the hook must launch it from there, not from a
|
||||||
# exists here. This test reads the DEPLOYED hook, which is why it caught the
|
# python_pkg that no longer exists here. This test reads the DEPLOYED hook,
|
||||||
# move rather than passing against a stale copy.
|
# which is why it caught the move rather than passing against a stale copy.
|
||||||
assert "-m session_autopsy" in argv
|
assert "-m session_autopsy" in argv
|
||||||
assert "python_pkg" not in argv
|
assert "python_pkg" not in argv
|
||||||
assert "ingest" in argv
|
assert "ingest" in argv
|
||||||
assert str(transcript) in argv
|
assert str(transcript) in argv
|
||||||
assert "--quiet" in argv
|
assert "--quiet" in argv
|
||||||
assert f"PYTHONPATH={home}/src/mcp-servers/session-autopsy" in argv
|
assert f"PYTHONPATH={home}/.claude/mcp-servers/session-autopsy" in argv
|
||||||
assert "timeout 120" in argv
|
assert "timeout 120" in argv
|
||||||
|
|
||||||
def test_creates_the_autopsy_home(self, tmp_path: Path) -> None:
|
def test_creates_the_autopsy_home(self, tmp_path: Path) -> None:
|
||||||
|
|||||||
@ -25,6 +25,7 @@ nothing to mutate.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import concurrent.futures
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
@ -36,6 +37,9 @@ import tempfile
|
|||||||
from _session_autopsy_mutations import END_HOOK, MUTATIONS, START_HOOK, Mutation
|
from _session_autopsy_mutations import END_HOOK, MUTATIONS, START_HOOK, Mutation
|
||||||
|
|
||||||
_TEST_FILE = "linux_configuration/tests/test_session_autopsy_hooks.py"
|
_TEST_FILE = "linux_configuration/tests/test_session_autopsy_hooks.py"
|
||||||
|
# Mutants are independent (own hook copy, own coverage file), so they run side by
|
||||||
|
# side; sequentially the ~4 s suite x 9 mutants pushed a commit past 60 s.
|
||||||
|
_WORKERS = 4
|
||||||
_OUTCOME = re.compile(r"::(test_\w+)\s+(PASSED|FAILED|ERROR|SKIPPED)")
|
_OUTCOME = re.compile(r"::(test_\w+)\s+(PASSED|FAILED|ERROR|SKIPPED)")
|
||||||
|
|
||||||
|
|
||||||
@ -133,6 +137,14 @@ def _apply(mutation: Mutation, hooks_dir: Path, target_dir: Path) -> None:
|
|||||||
target.chmod(0o755)
|
target.chmod(0o755)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_mutant(mutation: Mutation, hooks_dir: Path) -> tuple[Mutation, set[str]]:
|
||||||
|
"""Apply one mutation in a private directory and return the tests it kills."""
|
||||||
|
with tempfile.TemporaryDirectory() as mutant_name:
|
||||||
|
mutant_dir = Path(mutant_name)
|
||||||
|
_apply(mutation, hooks_dir, mutant_dir)
|
||||||
|
return mutation, _run_suite(mutant_dir, mutant_dir)
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
"""Run every mutation and fail if any test survives them all."""
|
"""Run every mutation and fail if any test survives them all."""
|
||||||
hooks_dir = _hooks_dir()
|
hooks_dir = _hooks_dir()
|
||||||
@ -148,15 +160,13 @@ def main() -> int:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
killed_by: dict[str, list[str]] = {}
|
killed_by: dict[str, list[str]] = {}
|
||||||
for mutation in MUTATIONS:
|
|
||||||
try:
|
try:
|
||||||
with tempfile.TemporaryDirectory() as mutant_name:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=_WORKERS) as pool:
|
||||||
mutant_dir = Path(mutant_name)
|
results = list(pool.map(lambda m: _run_mutant(m, hooks_dir), MUTATIONS))
|
||||||
_apply(mutation, hooks_dir, mutant_dir)
|
|
||||||
failures = _run_suite(mutant_dir, scratch)
|
|
||||||
except RuntimeError as error:
|
except RuntimeError as error:
|
||||||
_emit(f"{mutation.name:26s} ERROR: {error}")
|
_emit(f"ERROR: {error}")
|
||||||
return 1
|
return 1
|
||||||
|
for mutation, failures in results:
|
||||||
_emit(f"{mutation.name:26s} killed {len(failures):2d}: {sorted(failures)}")
|
_emit(f"{mutation.name:26s} killed {len(failures):2d}: {sorted(failures)}")
|
||||||
for test in failures:
|
for test in failures:
|
||||||
killed_by.setdefault(test, []).append(mutation.name)
|
killed_by.setdefault(test, []).append(mutation.name)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user