Files
brain/user-level-files/hooks/economy-mode-test.py
T
Дмитрий ed9bade863 feat: extract brain artifacts from Liderra + ~/.claude/
project-files/:
- CLAUDE.md.template (266 lines)
- docs/Pravila_raboty_Claude.template.md (720 lines)
- docs/Plugin_stack_rules.template.md (916 lines)
- docs/Tooling.template.md (613 lines)
- docs/CHANGELOG_claude_md.template.md
- docs/visualizations/hooks-skills-plugins-map.html (3122 lines)
- .mcp.json.template (universal: playwright/github/semgrep; laravel-boost dropped)

user-level-files/:
- hooks/ (10 Python files: skill-marker, skill-check, economy-* x8)
- settings-fragment.json (enabledPlugins + permissions + hooks only)
- marketplaces.json (3 sources)
- plugins-manifest.json (4 plugins pinned with gitCommitSha)
- mcp-user.template.json (magic with <<MAGIC_API_KEY>> placeholder)

Gitleaks scan: 0 findings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 00:46:51 +03:00

158 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Permanent test suite for economy-mode hook.
Tests via subprocess to verify end-to-end behavior including stdin
encoding, regex parsing, discussion-context filtering, and multi-match
handling. Run with: python ~/.claude/hooks/economy-mode-test.py
Exit code 0 = all green, 1 = any failure."""
import json
import os
import re
import subprocess
import sys
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
SCRIPT = os.path.expanduser("~/.claude/hooks/economy-mode.py")
def parse_level(prompt):
"""Run hook with given prompt. Return:
- int 0-100 if explicit activation
- None if default (no keyword matched, or matched in discussion context)
"""
payload = json.dumps({"prompt": prompt}, ensure_ascii=False).encode("utf-8")
r = subprocess.run(
["python", SCRIPT],
input=payload,
capture_output=True,
timeout=10,
)
if not r.stdout:
return None
try:
d = json.loads(r.stdout.decode("utf-8"))
ctx = d["hookSpecificOutput"]["additionalContext"]
except Exception:
return None
# "(default" or "не указал уровень" both indicate non-explicit
if "не указал уровень" in ctx or "(default" in ctx:
return None
m = re.search(r"ECONOMY MODE: (\d+)%", ctx)
return int(m.group(1)) if m else None
# (prompt, expected_level_or_None, description)
TESTS = [
# --- Russian inflection: ALL forms must activate ---
("экономия 75%", 75, "Nominative"),
("экономии 75%", 75, "Genitive"),
("экономию 75%", 75, "Accusative"),
("экономией 75%", 75, "Instrumental"),
("экономиями 75%", 75, "Plural instrumental"),
("Экономия 75%", 75, "Capitalized"),
("ЭКОНОМИЯ 75%", 75, "All caps"),
# --- Separators: must accept space, colon, dash, em-dash, equals, comma, parens ---
("экономия 75%", 75, "Space sep"),
("экономия: 75%", 75, "Colon sep"),
("экономия - 75%", 75, "Hyphen sep"),
("экономия — 75%", 75, "Em-dash sep"),
("экономия = 75%", 75, "Equals sep"),
("экономия,75%", 75, "Comma sep"),
("экономия75%", 75, "No sep (digit right after)"),
("экономия (75%)", 75, "Parens"),
# --- Numbers: integer, decimal, with/without space before % ---
("экономия 0%", 0, "Zero"),
("экономия 100%", 100, "Hundred"),
("экономия 75 %", 75, "Space before %"),
("экономия 75.5%", 75, "Decimal point"),
("экономия 75,5%", 75, "Decimal comma"),
("экономия 75.0%", 75, "Trailing .0"),
("экономия 0.0%", 0, "0.0"),
("экономия 200%", 100, "Out of range — clamp 100"),
# --- Word boundary: must NOT match when preceded by word char ---
("1экономия 75%", None, "Preceded by digit"),
("пэкономия 75%", None, "Preceded by Cyrillic letter"),
# --- Discussion contexts: must NOT activate ---
("как работает экономия 75%?", None, "Question with ?"),
("что даст экономия 75%", None, "'что даст' prefix"),
("что покрывает экономия 0%", None, "'что покрывает' prefix"),
("что такое экономия 75%", None, "'что такое' prefix"),
("не активируй экономия 75%", None, "Negation 'не'"),
("забудь про экономия 75%", None, "'забудь' prefix"),
("отбой экономия 75%", None, "'отбой' prefix"),
("пример: экономия 75%", None, "'пример' prefix"),
# --- Multi-match: last non-discussion match wins ---
("экономия 75%, потом экономия 0%", 0, "Last match wins"),
("не экономия 75%, а экономия 0%", 0, "Skip negated first, take last"),
("экономия 75% (передумал) экономия 0%", 0, "Mid-prompt change"),
# --- User's actual command from this turn ---
(
"тестирую все и снести изменения в хук, что он должен делать "
"при команде экономия 0% все для максимального результата и с "
"максимальным свеобъемливающим качеством. экономия 0%",
0,
"User's real command (this turn)",
),
# --- Empty / edge cases ---
("", None, "Empty"),
(" ", None, "Whitespace only"),
("просто задача без ключа", None, "No keyword"),
("экономия %", None, "Missing number"),
("75%", None, "Missing keyword"),
# === END-OF-PROMPT contract (NEW in v3) ===
("задача X. экономия 75%", 75, "Trailer style at end"),
("задача X. экономия 75%.", 75, "End with trailing period"),
("задача X. экономия 75%!", 75, "End with exclamation"),
("задача X. экономия 75% ", 75, "End with trailing whitespace"),
("делай X.\nэкономия 75%", 75, "Trailer on separate last line"),
("экономия 75% делай задачу X", None, "Pattern in middle, content after"),
("экономия 75% (срочно) делай X", None, "Pattern in middle with parens"),
("при команде экономия 75% что-то делать", None, "Pattern in middle of description"),
("экономия 75% потом экономия 0%", 0, "Last is at end"),
("экономия 0% (передумал) экономия 75% работать", None, "Last not at end"),
# === Subset of v2 tests revisited ===
("экономия 75%, потом экономия 0%", 0, "Last wins (still applies)"),
("не экономия 75%, а экономия 0%", 0, "Last is at end after negation"),
]
def main() -> int:
passed, failed, failures = 0, 0, []
for prompt, expected, desc in TESTS:
actual = parse_level(prompt)
ok = actual == expected
status = "PASS" if ok else "FAIL"
# Ascii-safe printing for prompt (truncate)
short = (prompt[:60] + "...") if len(prompt) > 60 else prompt
print(f" [{status}] {desc:40s} | exp={expected!s:5s} got={actual!s:5s} | {short!r}")
if ok:
passed += 1
else:
failed += 1
failures.append((desc, prompt, expected, actual))
print(f"\n=== {passed}/{passed+failed} PASSED, {failed} FAILED ===")
if failures:
print("\nFailures detail:")
for desc, prompt, exp, got in failures:
print(f" {desc}: expected={exp}, got={got}")
print(f" prompt={prompt!r}")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())