@theglitchking/babel-fish 2.3.0 → 2.4.1

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.
@@ -17,7 +17,7 @@ from fnmatch import fnmatch
17
17
  import re
18
18
  import subprocess
19
19
  import sys
20
- from datetime import datetime
20
+ from datetime import datetime, timezone
21
21
  from pathlib import Path
22
22
  from typing import Any
23
23
 
@@ -34,12 +34,34 @@ MAP_DIR = SCRIPT_DIR
34
34
  SECTIONS_DIR = MAP_DIR / "sections"
35
35
  CHECKSUMS = MAP_DIR / "checksums.json"
36
36
  LEARNED_VOC = MAP_DIR / "learned-vocabulary.json"
37
+ GLOSSARY = MAP_DIR / "glossary.json"
38
+ # Bump when the glossary.json shape changes. Consumers should refuse a major
39
+ # they do not recognise rather than guess.
40
+ GLOSSARY_SCHEMA_VERSION = "1.0"
37
41
 
38
42
  # Resolve project root: two levels up from .claude/project-map/
39
43
  PROJECT_ROOT = MAP_DIR.parent.parent
40
44
 
41
45
  SECTIONS_DIR.mkdir(parents=True, exist_ok=True)
42
46
 
47
+
48
+ def configure_paths(project_root: Path) -> None:
49
+ """Point every output at <project_root>/.claude/project-map/.
50
+
51
+ --project-root previously moved only the READ root: the map was written back
52
+ into whichever directory the script itself lived in, so mapping another
53
+ project produced nothing for the target and destroyed the script repo's own
54
+ map. Writes follow the root now.
55
+ """
56
+ global PROJECT_ROOT, MAP_DIR, SECTIONS_DIR, CHECKSUMS, LEARNED_VOC, GLOSSARY
57
+ PROJECT_ROOT = project_root
58
+ MAP_DIR = project_root / '.claude' / 'project-map'
59
+ SECTIONS_DIR = MAP_DIR / 'sections'
60
+ CHECKSUMS = MAP_DIR / 'checksums.json'
61
+ LEARNED_VOC = MAP_DIR / 'learned-vocabulary.json'
62
+ GLOSSARY = MAP_DIR / 'glossary.json'
63
+ SECTIONS_DIR.mkdir(parents=True, exist_ok=True)
64
+
43
65
  # ── Secrets guard ────────────────────────────────────────────────────────────
44
66
  SECRET_PATTERNS = re.compile(
45
67
  r'(?i)(password|secret|token|api_key|apikey|private_key|auth_token|'
@@ -1452,6 +1474,50 @@ def build_doc_pointers_section() -> str:
1452
1474
  return '\n'.join(lines) + '\n'
1453
1475
 
1454
1476
 
1477
+ # ── Glossary side-channel ─────────────────────────────────────────────────────
1478
+
1479
+ def _relative_source() -> str:
1480
+ vocab_md = SECTIONS_DIR / '01-vocabulary.md'
1481
+ try:
1482
+ return str(vocab_md.relative_to(PROJECT_ROOT))
1483
+ except ValueError:
1484
+ return str(vocab_md)
1485
+
1486
+
1487
+ def write_glossary(vocab: list[dict], stack: dict) -> Path:
1488
+ """Structured vocabulary for machine consumers.
1489
+
1490
+ 01-vocabulary.md stays human-facing. Consumers read this instead of parsing
1491
+ prose: the markdown is a rendered table whose Notes column mixes
1492
+ descriptions with metadata and carries pipe-escaping (`a \\| b`), which is
1493
+ fine to read and hostile to parse.
1494
+ """
1495
+ entries = [
1496
+ {
1497
+ 'key': v.get('alias', ''),
1498
+ 'canonical_path': v.get('location', ''),
1499
+ 'section': v.get('type', ''),
1500
+ 'description': v.get('notes', '') or None,
1501
+ }
1502
+ for v in sorted(vocab, key=lambda x: x.get('alias', ''))
1503
+ if v.get('alias')
1504
+ ]
1505
+ payload = {
1506
+ 'schema_version': GLOSSARY_SCHEMA_VERSION,
1507
+ 'generated_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
1508
+ # SECTIONS_DIR is bound to the script's location, which is not under
1509
+ # PROJECT_ROOT when --project-root points elsewhere. Report a relative
1510
+ # path when there is one, the absolute path otherwise.
1511
+ 'source': _relative_source(),
1512
+ 'project': stack.get('name', PROJECT_ROOT.name),
1513
+ 'entry_count': len(entries),
1514
+ 'entries': entries,
1515
+ }
1516
+ GLOSSARY.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + '\n',
1517
+ encoding='utf-8')
1518
+ return GLOSSARY
1519
+
1520
+
1455
1521
  # ╔══════════════════════════════════════════════════════════════════════════╗
1456
1522
  # ║ PROJECT MAP TOC ║
1457
1523
  # ╚══════════════════════════════════════════════════════════════════════════╝
@@ -1564,9 +1630,8 @@ def main() -> None:
1564
1630
  parser.add_argument('--stack-json', type=Path, default=None, help='Path to stack.json from detect-stack.sh')
1565
1631
  args = parser.parse_args()
1566
1632
 
1567
- global PROJECT_ROOT
1568
1633
  if args.project_root:
1569
- PROJECT_ROOT = args.project_root.resolve()
1634
+ configure_paths(args.project_root.resolve())
1570
1635
 
1571
1636
  print(f"[generate] Project root: {PROJECT_ROOT}")
1572
1637
 
@@ -1622,6 +1687,9 @@ def main() -> None:
1622
1687
  print("[generate] Building vocabulary...")
1623
1688
  vocab = VocabularyBuilder().build(routes, models, schemas, features, stack, skills)
1624
1689
 
1690
+ glossary_path = write_glossary(vocab, stack)
1691
+ print(f"[generate] Glossary → {glossary_path.name} ({len(vocab)} entries)")
1692
+
1625
1693
  print("[generate] Tracing import chains...")
1626
1694
  chains = ImportChainTracer().trace(routes)
1627
1695
 
@@ -32,6 +32,21 @@ PROJECT_ROOT = MAP_DIR.parent.parent
32
32
 
33
33
  REPORTS_DIR.mkdir(parents=True, exist_ok=True)
34
34
 
35
+
36
+ def configure_paths(project_root: Path) -> None:
37
+ """Grade the map belonging to project_root, not the script's own.
38
+
39
+ Previously --project-root moved only the path-validation root, so the
40
+ script's own sections were graded against a different project's filesystem —
41
+ producing a confident but meaningless score (0% vocabulary accuracy).
42
+ """
43
+ global PROJECT_ROOT, MAP_DIR, SECTIONS_DIR, REPORTS_DIR
44
+ PROJECT_ROOT = project_root
45
+ MAP_DIR = project_root / '.claude' / 'project-map'
46
+ SECTIONS_DIR = MAP_DIR / 'sections'
47
+ REPORTS_DIR = MAP_DIR / 'reports'
48
+ REPORTS_DIR.mkdir(parents=True, exist_ok=True)
49
+
35
50
  PASS_THRESHOLD = 90.0
36
51
 
37
52
  # ── Secret pattern (mirrors generate.py) ─────────────────────────────────────
@@ -597,9 +612,8 @@ def main() -> None:
597
612
  help='Override report output path')
598
613
  args = parser.parse_args()
599
614
 
600
- global PROJECT_ROOT
601
615
  if args.project_root:
602
- PROJECT_ROOT = args.project_root.resolve()
616
+ configure_paths(args.project_root.resolve())
603
617
 
604
618
  print(f"[grader] Grading iteration {args.iteration}/{args.total}...")
605
619
 
@@ -36,6 +36,21 @@ LEARNED_VOC = SCRIPT_DIR / "learned-vocabulary.json"
36
36
  MINE_CURSOR = SCRIPT_DIR / ".mine-cursor.json"
37
37
  PROJECT_ROOT = SCRIPT_DIR.parent.parent
38
38
 
39
+
40
+ def configure_paths(project_root: Path) -> None:
41
+ """Write mined aliases into project_root's map, not the script's own.
42
+
43
+ Previously --project-root selected whose TRANSCRIPTS to mine but left the
44
+ output pointed at the script's directory, so mining another project wrote
45
+ its aliases into this one's learned vocabulary.
46
+ """
47
+ global PROJECT_ROOT, LEARNED_VOC, MINE_CURSOR
48
+ PROJECT_ROOT = project_root
49
+ map_dir = project_root / '.claude' / 'project-map'
50
+ LEARNED_VOC = map_dir / 'learned-vocabulary.json'
51
+ MINE_CURSOR = map_dir / '.mine-cursor.json'
52
+ map_dir.mkdir(parents=True, exist_ok=True)
53
+
39
54
  # ── Config ───────────────────────────────────────────────────────────────────
40
55
  MIN_SCORE = 5.0 # Minimum score to include in vocabulary
41
56
  RECENCY_WINDOWS = [ # (days_threshold, weight)
@@ -466,9 +481,8 @@ def main() -> None:
466
481
  help='Ignore the incremental cursor and re-mine every transcript')
467
482
  args = parser.parse_args()
468
483
 
469
- global PROJECT_ROOT
470
484
  if args.project_root:
471
- PROJECT_ROOT = args.project_root.resolve()
485
+ configure_paths(args.project_root.resolve())
472
486
 
473
487
  print(f"[mine-sessions] Project root: {PROJECT_ROOT}")
474
488
 
@@ -12,6 +12,7 @@ well-formedness; these measure usefulness.
12
12
  from __future__ import annotations
13
13
 
14
14
  import importlib.util
15
+ import pathlib
15
16
  import shutil
16
17
  import sys
17
18
  import tempfile
@@ -173,6 +174,102 @@ def test_plugin_repo_map_is_not_empty(g):
173
174
  assert "no vocabulary generated yet" not in section, "section 01 still renders the stub"
174
175
 
175
176
 
177
+ # ── Glossary side-channel (#10) ──────────────────────────────────────────────
178
+
179
+ def test_glossary_json_written_and_shaped(g):
180
+ """Consumers read this instead of parsing the markdown table."""
181
+ import json
182
+ skills = g.SkillParser().parse()
183
+ vocab = g.VocabularyBuilder().build([], [], [], [], g.load_stack(), skills)
184
+ path = g.write_glossary(vocab, g.load_stack())
185
+ assert path.exists(), "glossary.json not written"
186
+ data = json.loads(path.read_text())
187
+
188
+ for field in ("schema_version", "generated_at", "source", "project",
189
+ "entry_count", "entries"):
190
+ assert field in data, f"missing {field}: {sorted(data)}"
191
+ assert data["entry_count"] == len(data["entries"])
192
+ assert data["schema_version"].count(".") == 1, data["schema_version"]
193
+
194
+ for e in data["entries"]:
195
+ assert set(e) == {"key", "canonical_path", "section", "description"}, e
196
+ assert e["key"], e
197
+ keys = [e["key"] for e in data["entries"]]
198
+ assert keys == sorted(keys), "entries must be sorted by key"
199
+ assert len(keys) == len(set(keys)), "keys must be unique"
200
+
201
+
202
+ def test_glossary_source_path_is_real(g):
203
+ """v1.0 of the contract named `.babel-fish/`, which was never written."""
204
+ import json
205
+ data = json.loads(g.write_glossary([], g.load_stack()).read_text())
206
+ assert ".babel-fish" not in data["source"], data["source"]
207
+ assert data["source"].endswith("01-vocabulary.md"), data["source"]
208
+ assert pathlib.PurePath(data["source"]).parent.name == "sections", data["source"]
209
+
210
+
211
+ def test_glossary_values_are_unescaped(g):
212
+ """The markdown table escapes pipes into values (`auto \\| nudge`). JSON
213
+ must carry the real string — that escaping is the clearest reason not to
214
+ parse the rendered table."""
215
+ import json
216
+ vocab = [{"alias": "policy", "type": "command", "location": "commands/policy.md",
217
+ "notes": "Get or set the policy (auto | nudge | off)"}]
218
+ data = json.loads(g.write_glossary(vocab, g.load_stack()).read_text())
219
+ assert data["entries"][0]["description"] == "Get or set the policy (auto | nudge | off)"
220
+ assert "\\|" not in data["entries"][0]["description"]
221
+
222
+
223
+ def test_empty_glossary_is_valid(g):
224
+ """An empty vocabulary is a valid glossary, not an error."""
225
+ import json
226
+ data = json.loads(g.write_glossary([], g.load_stack()).read_text())
227
+ assert data["entries"] == [] and data["entry_count"] == 0
228
+
229
+
230
+ def test_glossary_survives_foreign_project_root(g):
231
+ """SECTIONS_DIR is bound to the script location, which is NOT under
232
+ PROJECT_ROOT when --project-root points elsewhere. That combination raised
233
+ ValueError from relative_to()."""
234
+ import json
235
+ g.write_glossary([], g.load_stack()) # PROJECT_ROOT is the temp fixture here
236
+ data = json.loads(g.GLOSSARY.read_text())
237
+ assert data["source"].endswith("01-vocabulary.md"), data["source"]
238
+
239
+
240
+ # ── --project-root write path (#15) ──────────────────────────────────────────
241
+
242
+ def test_project_root_relocates_every_output(g):
243
+ """--project-root used to move only the READ root: the map was written back
244
+ into the script's own directory, so the target got nothing and the script
245
+ repo's map was destroyed."""
246
+ target = g.PROJECT_ROOT / "elsewhere"
247
+ (target / "src").mkdir(parents=True)
248
+ (target / "src" / "app.py").write_text("# app\n")
249
+
250
+ script_owned = g.MAP_DIR
251
+ g.configure_paths(target)
252
+
253
+ assert g.PROJECT_ROOT == target
254
+ for name in ("MAP_DIR", "SECTIONS_DIR", "CHECKSUMS", "LEARNED_VOC", "GLOSSARY"):
255
+ p = getattr(g, name)
256
+ assert str(p).startswith(str(target)), f"{name} still outside the target: {p}"
257
+ assert not str(p).startswith(str(script_owned)), f"{name} still in the script dir: {p}"
258
+ assert g.SECTIONS_DIR.is_dir(), "sections dir not created under the target"
259
+
260
+
261
+ def test_project_root_writes_land_in_the_target(g):
262
+ import json
263
+ target = g.PROJECT_ROOT / "elsewhere"
264
+ target.mkdir(parents=True)
265
+ g.configure_paths(target)
266
+ g.write_glossary([{"alias": "x", "type": "feature",
267
+ "location": "src/x.py", "notes": "n"}], g.load_stack())
268
+ written = target / ".claude" / "project-map" / "glossary.json"
269
+ assert written.exists(), "glossary.json was not written under the target"
270
+ assert json.loads(written.read_text())["entry_count"] == 1
271
+
272
+
176
273
  def main() -> int:
177
274
  tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
178
275
  failed = []
@@ -136,6 +136,22 @@ def test_generate_emits_prose_not_a_row_for_empty_vocab():
136
136
  assert "No vocabulary generated yet" in out
137
137
 
138
138
 
139
+ def test_project_root_relocates_grader_paths(tmp):
140
+ """The grader used to read its OWN sections while validating paths against
141
+ a different project — a confident but meaningless score."""
142
+ g = load_grader(tmp)
143
+ target = tmp / "elsewhere"
144
+ target.mkdir(parents=True)
145
+ script_owned = g.SECTIONS_DIR
146
+ g.configure_paths(target)
147
+ assert g.PROJECT_ROOT == target
148
+ for name in ("MAP_DIR", "SECTIONS_DIR", "REPORTS_DIR"):
149
+ p = getattr(g, name)
150
+ assert str(p).startswith(str(target)), f"{name} outside target: {p}"
151
+ assert p != script_owned, f"{name} still the script's: {p}"
152
+ assert g.REPORTS_DIR.is_dir()
153
+
154
+
139
155
  def main() -> int:
140
156
  import inspect
141
157
  tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
@@ -172,6 +172,19 @@ def test_junk_phrases_filtered(m, root):
172
172
  assert not any("," in g or ":" in g for g in got), got
173
173
 
174
174
 
175
+ def test_project_root_relocates_miner_output(m, root):
176
+ """Mining another project used to write its aliases into THIS project's
177
+ learned vocabulary."""
178
+ target = root / "elsewhere"
179
+ target.mkdir(parents=True)
180
+ m.configure_paths(target)
181
+ assert m.PROJECT_ROOT == target
182
+ for name in ("LEARNED_VOC", "MINE_CURSOR"):
183
+ p = getattr(m, name)
184
+ assert str(p).startswith(str(target)), f"{name} outside target: {p}"
185
+ assert m.LEARNED_VOC.parent.is_dir()
186
+
187
+
175
188
  def main() -> int:
176
189
  import inspect
177
190
  tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
@@ -6,13 +6,13 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for babel-fish - Codebase introspection and vocabulary translation for AI coding assistants",
9
- "version": "2.3.0"
9
+ "version": "2.4.1"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "babel-fish",
14
14
  "description": "Auto-generates a project map, vocabulary translation layer, and developer skill for any codebase. Introspects routes, models, services, features, infrastructure, and session history to give Claude instant full-stack context. Self-updates via pre-commit hook.",
15
- "version": "2.3.0",
15
+ "version": "2.4.1",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "babel-fish",
3
3
  "description": "Auto-generates a project map, vocabulary translation layer, and developer skill for any codebase. Introspects routes, models, services, features, infrastructure, and session history to give Claude instant full-stack context. Self-updates via pre-commit hook.",
4
- "version": "2.3.0",
4
+ "version": "2.4.1",
5
5
  "author": {
6
6
  "name": "TheGlitchKing",
7
7
  "email": "theglitchking@users.noreply.github.com"
@@ -14,7 +14,8 @@ if echo "$STAGED_FILES" | grep -qE "$EXTENSIONS_PATTERN" 2>/dev/null; then
14
14
  echo "[codebase-mapper] Regenerating project map..."
15
15
  if $PYTHON "$MAP_SCRIPT" 2>/dev/null; then
16
16
  git add .claude/project-map/PROJECT_MAP.md .claude/project-map/checksums.json \
17
- .claude/project-map/sections/*.md .claude/project-map/learned-vocabulary.json 2>/dev/null || true
17
+ .claude/project-map/sections/*.md .claude/project-map/learned-vocabulary.json \
18
+ .claude/project-map/glossary.json 2>/dev/null || true
18
19
  fi
19
20
  fi
20
21
  fi
package/CHANGELOG.md CHANGED
@@ -2,6 +2,87 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.4.1] - 2026-09-04
6
+
7
+ ### Fixed
8
+
9
+ - **`--project-root` changed only the read root**
10
+ ([#15](https://github.com/TheGlitchKing/babel-fish/issues/15)). All three
11
+ scripts kept writing to the directory the script itself lives in, so pointing
12
+ the flag at another project silently corrupted the script's own map instead of
13
+ mapping the target:
14
+
15
+ - `generate.py` wrote the map into its own directory — the target got nothing,
16
+ and the script repo's `sections/`, `glossary.json` and `checksums.json` were
17
+ overwritten, while the run reported success.
18
+ - `grader.py` graded its **own** sections while validating vocabulary paths
19
+ against the **target's** filesystem, producing a confident but meaningless
20
+ score (0% vocabulary accuracy, 77% FAIL). The nastiest of the three: it
21
+ neither crashed nor produced nothing.
22
+ - `mine-sessions.py` mined the target's transcripts and wrote the aliases into
23
+ its own `learned-vocabulary.json` and `.mine-cursor.json`.
24
+
25
+ Each script gained `configure_paths()`, called from `main()` after argument
26
+ parsing, which relocates every output under `<project-root>/.claude/project-map/`.
27
+ Module-level defaults are unchanged, so the common case and the test suites
28
+ are unaffected.
29
+
30
+ Not reachable during install: `.claude/install.sh` runs the copied scripts
31
+ inside the target, where `SCRIPT_DIR` and `--project-root` coincide. The
32
+ pre-commit hook and `babel-fish regen` / `grade` pass no `--project-root`.
33
+ Verified by a full install after the change.
34
+
35
+ ## [2.4.0] - 2026-09-04
36
+
37
+ ### Added
38
+
39
+ - **`glossary.json` — a structured vocabulary artifact for machine consumers**
40
+ ([#10](https://github.com/TheGlitchKing/babel-fish/issues/10)). Written to
41
+ `.claude/project-map/glossary.json` on every map build and staged by the
42
+ pre-commit hook. Consumers read this instead of parsing `01-vocabulary.md`.
43
+
44
+ The markdown was never a good parsing target: its `Notes` column mixes
45
+ descriptions with metadata, and pipe-escaping leaks into values — this repo's
46
+ own output contained `(auto \| nudge \| off)`. The JSON carries the real
47
+ string.
48
+
49
+ ### Fixed
50
+
51
+ - **The glossary contract described a format babel-fish has never emitted.**
52
+ `glossary-contract.md` v1.0 specified bullet entries
53
+ (`- **key** → \`path\` — desc`) and stated that non-conforming bullets are
54
+ ignored; the generator has always written a markdown table. A consumer built
55
+ strictly to that spec would extract **zero entries**. Rewritten (v2.0) around
56
+ `glossary.json`, with the markdown documented as human-facing output that is
57
+ not parsed.
58
+
59
+ - **Both sides of that contract documented a directory neither produces.** The
60
+ contract, the integration guide and the README referred to `.babel-fish/`;
61
+ babel-fish writes `.claude/project-map/`. The same error is mirrored in
62
+ semantic-memory's `smart-middle-activation.md` and `corpora-json.md`, where it
63
+ would have made Phase 3.1.0 find nothing — silently. Corrected here and filed
64
+ there as
65
+ [semantic-memory#28](https://github.com/the-glitch-kingdom/semantic-memory/issues/28).
66
+
67
+ - **`--project-root` crashed the generator.** `write_glossary()` computed the
68
+ source path with `SECTIONS_DIR.relative_to(PROJECT_ROOT)`, but `SECTIONS_DIR`
69
+ is bound to the script's own location, so pointing `--project-root` elsewhere
70
+ raised `ValueError`. Found by a test written for the new artifact.
71
+
72
+ - `integration-with-semantic-memory.md` no longer describes a setup that does
73
+ not exist. semantic-memory 1.5.1 ships no `translate` verbs, no `project-map`
74
+ corpus and no glossary reader; the guide now says so at the top instead of
75
+ giving instructions for it.
76
+
77
+ ### Note
78
+
79
+ The consumer is unbuilt, so nothing was pinned to the old format. Issue #10
80
+ originally advised caution about "a breaking change to a format a downstream
81
+ consumer pins to" — checking semantic-memory's source showed zero
82
+ `translate`/`reverse_translate`/`list_vocabulary` verbs in its 164-entry tool
83
+ surface and no `glossary` string in `src/`. That freed the format choice
84
+ entirely.
85
+
5
86
  ## [2.3.0] - 2026-09-04
6
87
 
7
88
  ### Fixed
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![GitHub: TheGlitchKing/babel-fish](https://img.shields.io/badge/GitHub-TheGlitchKing%2Fbabel--fish-blue)](https://github.com/TheGlitchKing/babel-fish)
10
10
 
11
11
  > [!NOTE]
12
- > **Pairs with [`semantic-memory`](https://github.com/TheGlitchKing/semantic-sidekick) (formerly `semantic-sidekick`).** When both are installed, semantic-memory consumes babel-fish's auto-generated `.babel-fish/` output as a `project-map` corpus AND extracts `01-vocabulary.md` into a structured `glossary.json` side-channel. That gives your AI a deterministic `translate("deals page") → "features/deal-pipeline/DealPipeline.tsx"` MCP verb instead of relying on semantic-search-luck. See [`.documentation/api/glossary-contract.md`](./.documentation/api/glossary-contract.md) for the producer/consumer data contract and [`.documentation/quickstart/integration-with-semantic-memory.md`](./.documentation/quickstart/integration-with-semantic-memory.md) for the setup walkthrough. babel-fish standalone behavior is unchanged — semantic-memory is purely additive.
12
+ > **Pairs with [`semantic-memory`](https://github.com/TheGlitchKing/semantic-sidekick) (formerly `semantic-sidekick`).** When both are installed, semantic-memory consumes babel-fish's auto-generated `.claude/project-map/` output as a `project-map` corpus AND reads the structured `glossary.json` babel-fish emits alongside it. That gives your AI a deterministic `translate("deals page") → "features/deal-pipeline/DealPipeline.tsx"` MCP verb instead of relying on semantic-search-luck. See [`.documentation/api/glossary-contract.md`](./.documentation/api/glossary-contract.md) for the producer/consumer data contract and [`.documentation/quickstart/integration-with-semantic-memory.md`](./.documentation/quickstart/integration-with-semantic-memory.md) for the setup walkthrough. babel-fish standalone behavior is unchanged — semantic-memory is purely additive.
13
13
 
14
14
  ---
15
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theglitchking/babel-fish",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Gives your AI coding assistant instant, accurate knowledge of every route, model, service, feature, and infrastructure element in your codebase.",
5
5
  "type": "module",
6
6
  "bin": {