@theglitchking/babel-fish 2.4.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env python3
2
+ """Regression tests for context-check.py — run: python3 .claude/project-map/test_context_check.py
3
+
4
+ Stdlib assert + __main__, same shape as the sibling suites. Every check is proven
5
+ to DETECT something (a seeded bad input) as well as to pass a good one: a check
6
+ that only ever prints PASS is indistinguishable from one that checks nothing.
7
+
8
+ Every fixture is a temp project passed via --project-root, so nothing here reads
9
+ or writes the real repo (#18).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import inspect
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import tempfile
18
+ from pathlib import Path
19
+
20
+ HERE = Path(__file__).parent
21
+ SCRIPT = HERE / "context-check.py"
22
+ REPO = HERE.parent.parent
23
+
24
+ GOOD_RULES = "# Rules\n\n- NEVER do the bad thing — it breaks. → `.documentation/a.md`\n"
25
+
26
+
27
+ def make_project(root: Path, claude_md: str = "# Project\n", rules: dict[str, str] | None = None) -> Path:
28
+ (root / ".documentation").mkdir(parents=True, exist_ok=True)
29
+ (root / ".documentation/a.md").write_text("# A\n")
30
+ (root / "CLAUDE.md").write_text(claude_md)
31
+ (root / ".claude/rules").mkdir(parents=True, exist_ok=True)
32
+ for name, text in (rules or {}).items():
33
+ (root / ".claude/rules" / name).write_text(text)
34
+ return root
35
+
36
+
37
+ def check(root: Path, *flags: str) -> subprocess.CompletedProcess:
38
+ return subprocess.run([sys.executable, str(SCRIPT), "--project-root", str(root), *flags],
39
+ capture_output=True, text=True)
40
+
41
+
42
+ # ── budget ───────────────────────────────────────────────────────────────────
43
+
44
+ def test_under_budget_passes(tmp):
45
+ r = check(make_project(tmp, rules={"r.md": GOOD_RULES}))
46
+ assert r.returncode == 0, r.stdout
47
+
48
+
49
+ def test_over_budget_fails_and_names_the_file(tmp):
50
+ make_project(tmp, rules={"huge.md": "x" * 2_000})
51
+ r = check(tmp, "--budget", "1000")
52
+ assert r.returncode == 1, r.stdout
53
+ assert "FAIL auto-loaded context is under 1,000" in r.stdout
54
+ assert ".claude/rules/huge.md" in r.stdout
55
+
56
+
57
+ def test_budget_counts_nested_rules_and_dot_claude_claude_md(tmp):
58
+ make_project(tmp)
59
+ (tmp / ".claude/rules/sub").mkdir()
60
+ (tmp / ".claude/rules/sub/deep.md").write_text("y" * 600)
61
+ (tmp / ".claude/CLAUDE.md").write_text("z" * 600)
62
+ r = check(tmp, "--budget", "1000")
63
+ assert r.returncode == 1, r.stdout
64
+ assert ".claude/rules/sub/deep.md" in r.stdout and ".claude/CLAUDE.md" in r.stdout
65
+
66
+
67
+ def test_warn_only_exits_zero(tmp):
68
+ r = check(make_project(tmp, rules={"huge.md": "x" * 2_000}), "--budget", "1000", "--warn-only")
69
+ assert r.returncode == 0 and "FAIL" in r.stdout, r.stdout
70
+
71
+
72
+ # ── pointers ─────────────────────────────────────────────────────────────────
73
+
74
+ def test_dangling_arrow_pointer_fails(tmp):
75
+ r = check(make_project(tmp, rules={"r.md": "- NEVER x — y. → `.documentation/gone.md`\n"}))
76
+ assert r.returncode == 1 and ".documentation/gone.md" in r.stdout, r.stdout
77
+
78
+
79
+ def test_dangling_backticked_doc_path_fails(tmp):
80
+ r = check(make_project(tmp, claude_md="See `.documentation/missing/doc.md` for more.\n"))
81
+ assert r.returncode == 1 and "missing/doc.md" in r.stdout, r.stdout
82
+
83
+
84
+ def test_dangling_markdown_link_fails_relative_to_the_file(tmp):
85
+ # This repo's runbook links docs this way — the fork's regexes never saw them.
86
+ make_project(tmp, rules={"r.md": "[doc](../../.documentation/a.md) and [bad](../../.documentation/nope.md)\n"})
87
+ r = check(tmp)
88
+ assert r.returncode == 1, r.stdout
89
+ assert "nope.md" in r.stdout and "a.md" not in r.stdout.split("--")[-1], r.stdout
90
+
91
+
92
+ def test_root_relative_link_in_dot_claude_is_accepted(tmp):
93
+ # The installer writes exactly this into .claude/CLAUDE.md. Strictly file-relative,
94
+ # it blocked the first commit of every fresh install.
95
+ make_project(tmp)
96
+ (tmp / ".claude/project-map").mkdir()
97
+ (tmp / ".claude/project-map/PROJECT_MAP.md").write_text("# Map\n")
98
+ (tmp / ".claude/CLAUDE.md").write_text("[map](.claude/project-map/PROJECT_MAP.md)\n")
99
+ r = check(tmp)
100
+ assert r.returncode == 0, r.stdout
101
+
102
+
103
+ def test_anchor_url_and_code_fence_are_not_false_alarms(tmp):
104
+ make_project(tmp, claude_md=(
105
+ "→ `.documentation/a.md#section`\n"
106
+ "[site](https://example.com/x.md) [mail](mailto:a@b.c) [here](#top)\n"
107
+ "```md\n→ `.documentation/example-only.md`\n[x](nowhere.md)\n```\n"
108
+ ))
109
+ r = check(tmp)
110
+ assert r.returncode == 0, r.stdout
111
+
112
+
113
+ # ── rule lines (--map-style) ─────────────────────────────────────────────────
114
+
115
+ def test_rule_lines_ignored_without_map_style(tmp):
116
+ r = check(make_project(tmp, rules={"r.md": "- just some context\n"}))
117
+ assert r.returncode == 0 and "ALWAYS/NEVER" not in r.stdout, r.stdout
118
+
119
+
120
+ def test_context_line_in_rules_file_fails(tmp):
121
+ r = check(make_project(tmp, rules={"r.md": "- run the gates first → `.documentation/a.md`\n"}), "--map-style")
122
+ assert r.returncode == 1 and "run the gates first" in r.stdout, r.stdout
123
+
124
+
125
+ def test_rule_without_pointer_fails(tmp):
126
+ r = check(make_project(tmp, rules={"r.md": "- NEVER do it — because.\n"}), "--map-style")
127
+ assert r.returncode == 1, r.stdout
128
+
129
+
130
+ def test_good_rule_line_passes(tmp):
131
+ r = check(make_project(tmp, rules={"r.md": GOOD_RULES}), "--map-style")
132
+ assert r.returncode == 0, r.stdout
133
+
134
+
135
+ def test_descriptive_files_are_exempt(tmp):
136
+ make_project(tmp, rules={"operational-runbook.md": "- a trap, described\n"})
137
+ assert check(tmp, "--map-style").returncode == 0
138
+ r = check(tmp, "--map-style", "--descriptive", "other.md")
139
+ assert r.returncode == 1, r.stdout
140
+
141
+
142
+ def test_claude_md_only_pointed_bullets_must_be_rules(tmp):
143
+ make_project(tmp, claude_md="- `run it` with docker\n")
144
+ assert check(tmp, "--map-style").returncode == 0
145
+ (tmp / "CLAUDE.md").write_text("- a tool line → `.documentation/a.md`\n")
146
+ assert check(tmp, "--map-style").returncode == 1
147
+
148
+
149
+ # ── wiring ───────────────────────────────────────────────────────────────────
150
+
151
+ def test_this_repo_passes():
152
+ r = subprocess.run([sys.executable, str(SCRIPT)], capture_output=True, text=True, cwd=REPO)
153
+ assert r.returncode == 0, r.stdout
154
+
155
+
156
+ def _block(text: str) -> str:
157
+ start = text.index("# ── Context Check")
158
+ end = text.index("# ── End Context Check")
159
+ return text[start:end]
160
+
161
+
162
+ def test_installer_writes_the_same_hook_block():
163
+ assert _block((REPO / ".githooks/pre-commit").read_text()) == _block((REPO / ".claude/install.sh").read_text())
164
+
165
+
166
+ def test_hooks_are_committed_executable():
167
+ # Git silently skips a non-executable hook. Until #20 both were committed 100644,
168
+ # so no clone of this repo ever ran its pre-commit hook.
169
+ out = subprocess.run(["git", "ls-files", "-s", ".githooks/"], capture_output=True, text=True,
170
+ cwd=REPO, check=True).stdout
171
+ modes = {line.split("\t")[1]: line.split()[0] for line in out.splitlines()}
172
+ assert modes and all(m == "100755" for m in modes.values()), modes
173
+
174
+
175
+ def test_hook_blocks_a_bad_rules_commit(tmp):
176
+ git = ["git", "-c", "user.name=t", "-c", "user.email=t@t", "-c", "core.hooksPath=.githooks"]
177
+ subprocess.run(["git", "init", "-q", str(tmp)], check=True)
178
+ make_project(tmp, rules={"r.md": GOOD_RULES})
179
+ (tmp / ".githooks").mkdir()
180
+ shutil.copy(REPO / ".githooks/pre-commit", tmp / ".githooks/pre-commit")
181
+ (tmp / ".githooks/pre-commit").chmod(0o755) # the mode itself is test_hooks_are_committed_executable's job
182
+ (tmp / ".claude/project-map").mkdir(parents=True)
183
+ shutil.copy(SCRIPT, tmp / ".claude/project-map/context-check.py")
184
+ commit = lambda msg: subprocess.run(git + ["commit", "-qm", msg], cwd=tmp, capture_output=True, text=True)
185
+
186
+ subprocess.run(["git", "add", "-A"], cwd=tmp, check=True)
187
+ r = commit("good")
188
+ assert r.returncode == 0, r.stdout + r.stderr
189
+
190
+ (tmp / ".claude/rules/r.md").write_text("- NEVER x — y. → `.documentation/gone.md`\n")
191
+ subprocess.run(["git", "add", "-A"], cwd=tmp, check=True)
192
+ r = commit("bad")
193
+ assert r.returncode != 0, "hook let a dangling pointer through"
194
+ assert "Commit blocked" in r.stderr, r.stderr
195
+
196
+
197
+ def main() -> int:
198
+ tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
199
+ failed = []
200
+ base = Path(tempfile.mkdtemp(prefix="context-check-test-"))
201
+ try:
202
+ for fn in tests:
203
+ d = base / fn.__name__
204
+ d.mkdir(parents=True)
205
+ try:
206
+ if "tmp" in inspect.signature(fn).parameters:
207
+ fn(d)
208
+ else:
209
+ fn()
210
+ print(f" ok {fn.__name__}")
211
+ except Exception as e:
212
+ failed.append(fn.__name__)
213
+ print(f" FAIL {fn.__name__}: {type(e).__name__}: {e}")
214
+ finally:
215
+ shutil.rmtree(base, ignore_errors=True)
216
+ print(f"\n{len(tests) - len(failed)}/{len(tests)} passed")
217
+ return 1 if failed else 0
218
+
219
+
220
+ if __name__ == "__main__":
221
+ sys.exit(main())
@@ -179,6 +179,19 @@ def test_plugin_repo_map_is_not_empty(g):
179
179
  assert "no vocabulary generated yet" not in section, "section 01 still renders the stub"
180
180
 
181
181
 
182
+ # ── Structure pointer (#22) ──────────────────────────────────────────────────
183
+
184
+ def test_structure_pointer_only_with_a_manifest(g):
185
+ assert g.build_structure_pointer() == []
186
+ (g.PROJECT_ROOT / ".claude").mkdir(exist_ok=True)
187
+ (g.PROJECT_ROOT / ".claude/structure.toml").write_text(
188
+ 'mode = "multi-repo"\n\n[[repo]]\nname = "api"\npath = "../api"\npurpose = "Backend"\n')
189
+ text = "\n".join(g.build_structure_pointer())
190
+ assert "`.claude/structure.toml`" in text, text
191
+ if sys.version_info >= (3, 11):
192
+ assert "**api** `../api` — Backend" in text, text
193
+
194
+
182
195
  # ── Glossary side-channel (#10) ──────────────────────────────────────────────
183
196
 
184
197
  def test_glossary_json_written_and_shaped(g):