@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.
- package/.claude/project-map/generate.py +71 -3
- package/.claude/project-map/grader.py +16 -2
- package/.claude/project-map/mine-sessions.py +16 -2
- package/.claude/project-map/test_generate.py +97 -0
- package/.claude/project-map/test_grader.py +16 -0
- package/.claude/project-map/test_mine_sessions.py +13 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.githooks/pre-commit +2 -1
- package/CHANGELOG.md +81 -0
- package/README.md +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
4
|
+
"version": "2.4.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TheGlitchKing",
|
|
7
7
|
"email": "theglitchking@users.noreply.github.com"
|
package/.githooks/pre-commit
CHANGED
|
@@ -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
|
|
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
|
[](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 `.
|
|
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
|
+
"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": {
|