Skip to content

Commit a3be71d

Browse files
authored
fix(platform-integrations): add missing entity_io lib to Bob (#105)
* fix(platform-integrations): add missing entity_io lib to Bob integration * refactor(platform-integrations): use single shared lib and rename to kaizen-lib * fix(entity_io): honor KAIZEN_ENTITIES_DIR in get_default_entities_dir * fix(entity_io): persist normalized type value before serialization * fix(entity_io): use atomic claim loop and os.replace for cross-platform compatibility * fix(platform-integrations): guard against missing shared lib in Bob install * fix(bob): replace hardcoded path traversal with walk-up search for kaizen-lib * fix(bob): suppress E402 lint for entity_io imports after sys.path setup * fix(entity_io): clean up 0-byte orphan target file on write failure
1 parent d88baf1 commit a3be71d

4 files changed

Lines changed: 56 additions & 16 deletions

File tree

platform-integrations/bob/kaizen-lite/skills/kaizen-learn/scripts/save_entities.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,18 @@
99
import sys
1010
from pathlib import Path
1111

12-
# Add lib to path so we can import entity_io
13-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent / "lib"))
14-
from entity_io import (
12+
# Walk up from script to find kaizen-lib
13+
_script = Path(__file__).resolve()
14+
_lib = None
15+
for _ancestor in _script.parents:
16+
_candidate = _ancestor / "kaizen-lib"
17+
if _candidate.is_dir():
18+
_lib = _candidate
19+
break
20+
if _lib is None:
21+
raise ImportError(f"Cannot find kaizen-lib directory above {_script}")
22+
sys.path.insert(0, str(_lib))
23+
from entity_io import ( # noqa: E402
1524
find_entities_dir,
1625
get_default_entities_dir,
1726
load_all_entities,

platform-integrations/bob/kaizen-lite/skills/kaizen-recall/scripts/retrieve_entities.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@
66
import sys
77
from pathlib import Path
88

9-
# Add lib to path so we can import entity_io
10-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent / "lib"))
11-
from entity_io import find_entities_dir, load_all_entities, log as _log
9+
# Walk up from script to find kaizen-lib
10+
_script = Path(__file__).resolve()
11+
_lib = None
12+
for _ancestor in _script.parents:
13+
_candidate = _ancestor / "kaizen-lib"
14+
if _candidate.is_dir():
15+
_lib = _candidate
16+
break
17+
if _lib is None:
18+
raise ImportError(f"Cannot find kaizen-lib directory above {_script}")
19+
sys.path.insert(0, str(_lib))
20+
from entity_io import find_entities_dir, load_all_entities, log as _log # noqa: E402
1221

1322

1423
def log(message):

platform-integrations/claude/plugins/kaizen-lite/lib/entity_io.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,16 @@ def find_entities_dir():
8484
def get_default_entities_dir():
8585
"""Return (and create) the default entities directory.
8686
87-
Prefers ``{CLAUDE_PROJECT_ROOT}/.kaizen/entities/``, falls back to
87+
Prefers ``KAIZEN_ENTITIES_DIR``, then
88+
``{CLAUDE_PROJECT_ROOT}/.kaizen/entities/``, falls back to
8889
``.kaizen/entities/``.
8990
"""
91+
env_dir = os.environ.get("KAIZEN_ENTITIES_DIR")
92+
if env_dir:
93+
base = Path(env_dir)
94+
base.mkdir(parents=True, exist_ok=True)
95+
return base.resolve()
96+
9097
project_root = os.environ.get("CLAUDE_PROJECT_ROOT", "")
9198
if project_root:
9299
base = Path(project_root) / ".kaizen" / "entities"
@@ -258,6 +265,7 @@ def write_entity_file(directory, entity):
258265
entity_type = entity.get("type", "general")
259266
if not re.fullmatch(r"[a-z0-9][a-z0-9_-]*", entity_type):
260267
entity_type = "general"
268+
entity["type"] = entity_type
261269
type_dir = Path(directory) / entity_type
262270
type_dir.mkdir(parents=True, exist_ok=True)
263271

@@ -271,20 +279,24 @@ def write_entity_file(directory, entity):
271279
os.close(fd)
272280
fd = None
273281

274-
# Atomically claim the target using O_EXCL to detect races
275-
target = unique_filename(type_dir, slug)
276-
try:
277-
claim_fd = os.open(str(target), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
278-
os.close(claim_fd)
279-
except FileExistsError:
280-
# Another writer beat us — re-discover a free name
282+
# Atomically claim the target using O_EXCL; retry on race
283+
while True:
281284
target = unique_filename(type_dir, slug)
282-
283-
os.rename(tmp_path, target)
285+
try:
286+
claim_fd = os.open(str(target), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
287+
os.close(claim_fd)
288+
break
289+
except FileExistsError:
290+
continue
291+
292+
os.replace(tmp_path, target)
284293
return target
285294
except BaseException:
286295
if fd is not None:
287296
os.close(fd)
288297
if os.path.exists(tmp_path):
289298
os.unlink(tmp_path)
299+
# Clean up the 0-byte placeholder if the replace didn't happen
300+
if target and os.path.exists(str(target)) and os.path.getsize(str(target)) == 0:
301+
os.unlink(str(target))
290302
raise

platform-integrations/install.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,14 @@ def install_bob(source_dir, target_dir, mode="lite"):
502502
503503
info(f"Installing Bob ({mode} mode) → {bob_target}")
504504
505+
# Shared lib (entity_io) — single source of truth lives in the Claude plugin
506+
shared_lib = Path(source_dir) / "platform-integrations" / "claude" / "plugins" / "kaizen-lite" / "lib"
507+
if not shared_lib.is_dir():
508+
error(f"Shared lib not found: {shared_lib} — is the Claude plugin present in the source tree?")
509+
sys.exit(1)
510+
copy_tree(shared_lib, bob_target / "kaizen-lib")
511+
success("Copied Bob lib")
512+
505513
# Skills
506514
copy_tree(bob_source_lite / "skills" / "kaizen-learn", bob_target / "skills" / "kaizen-learn")
507515
copy_tree(bob_source_lite / "skills" / "kaizen-recall", bob_target / "skills" / "kaizen-recall")
@@ -537,6 +545,7 @@ def uninstall_bob(target_dir, mode="full"):
537545
bob_target = Path(target_dir) / ".bob"
538546
info(f"Uninstalling Bob from {bob_target}")
539547
548+
remove_dir(bob_target / "kaizen-lib")
540549
remove_dir(bob_target / "skills" / "kaizen-learn")
541550
remove_dir(bob_target / "skills" / "kaizen-recall")
542551
remove_file(bob_target / "commands" / "kaizen:learn.md")
@@ -550,6 +559,7 @@ def uninstall_bob(target_dir, mode="full"):
550559
def status_bob(target_dir):
551560
bob_target = Path(target_dir) / ".bob"
552561
print(f" Bob (.bob/):")
562+
print(f" kaizen-lib/entity_io : {'✓' if (bob_target / 'kaizen-lib' / 'entity_io.py').is_file() else '✗'}")
553563
print(f" skills/kaizen-learn : {'✓' if (bob_target / 'skills' / 'kaizen-learn').is_dir() else '✗'}")
554564
print(f" skills/kaizen-recall : {'✓' if (bob_target / 'skills' / 'kaizen-recall').is_dir() else '✗'}")
555565
print(f" commands/ : {'✓' if (bob_target / 'commands' / 'kaizen:learn.md').is_file() else '✗'}")

0 commit comments

Comments
 (0)