swarph-cli 0.9.3__tar.gz → 0.9.4__tar.gz

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.
Files changed (53) hide show
  1. {swarph_cli-0.9.3/src/swarph_cli.egg-info → swarph_cli-0.9.4}/PKG-INFO +2 -2
  2. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/pyproject.toml +2 -2
  3. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/cell.py +60 -8
  5. {swarph_cli-0.9.3 → swarph_cli-0.9.4/src/swarph_cli.egg-info}/PKG-INFO +2 -2
  6. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_cell_loader.py +103 -4
  7. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_spawn_command.py +3 -3
  8. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/LICENSE +0 -0
  9. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/README.md +0 -0
  10. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/setup.cfg +0 -0
  11. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/caller.py +0 -0
  12. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/__init__.py +0 -0
  13. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/chat.py +0 -0
  14. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/daemon.py +0 -0
  15. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/hook_output.py +0 -0
  16. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/import_session.py +0 -0
  17. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/init.py +0 -0
  18. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/install_hook.py +0 -0
  19. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/memory_sync.py +0 -0
  20. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/mesh.py +0 -0
  21. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/onboard.py +0 -0
  22. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/ratify.py +0 -0
  23. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/spawn.py +0 -0
  24. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/commands/watchdog.py +0 -0
  25. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/main.py +0 -0
  26. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/parsers/__init__.py +0 -0
  27. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/parsers/claude.py +0 -0
  28. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  29. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  30. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  31. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
  32. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  33. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  34. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli.egg-info/requires.txt +0 -0
  35. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/src/swarph_cli.egg-info/top_level.txt +0 -0
  36. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_chat_command.py +0 -0
  37. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_claude_parser.py +0 -0
  38. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_daemon_command.py +0 -0
  39. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_hook_output.py +0 -0
  40. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_import_command.py +0 -0
  41. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_init_command.py +0 -0
  42. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_install_hook.py +0 -0
  43. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_main.py +0 -0
  44. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_memory_sync.py +0 -0
  45. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_mesh_command.py +0 -0
  46. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_mesh_sidecar.py +0 -0
  47. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_onboard_command.py +0 -0
  48. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_ratify_command.py +0 -0
  49. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_smoke_chat.py +0 -0
  50. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_smoke_one_shot.py +0 -0
  51. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_smoke_phase_5_5.py +0 -0
  52. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_spawn_windows_relaunch.py +0 -0
  53. {swarph_cli-0.9.3 → swarph_cli-0.9.4}/tests/test_watchdog.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.9.3
4
- Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider spawn (claude/codex/antigravity per cell.provider via a ProviderMembrane), `swarph init` (interactive cell.yaml scaffolder), `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.3 adds `swarph init` + hardens assisted_memory restore (fail-closed pull, empty-guards, codex AGENTS.md) and the mesh sidecar (no DM-loss on idle-guard).
3
+ Version: 0.9.4
4
+ Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider `swarph spawn` (claude/codex/antigravity per cell.provider via a ProviderMembrane + subprocess billing-scrub), interactive `swarph init`, `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.4: fixes session-pin resume across a changed cwd.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/darw007d/swarph-cli
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.9.3"
8
- description = "The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider spawn (claude/codex/antigravity per cell.provider via a ProviderMembrane), `swarph init` (interactive cell.yaml scaffolder), `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.3 adds `swarph init` + hardens assisted_memory restore (fail-closed pull, empty-guards, codex AGENTS.md) and the mesh sidecar (no DM-loss on idle-guard)."
7
+ version = "0.9.4"
8
+ description = "The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider `swarph spawn` (claude/codex/antigravity per cell.provider via a ProviderMembrane + subprocess billing-scrub), interactive `swarph init`, `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.4: fixes session-pin resume across a changed cwd."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
  requires-python = ">=3.10"
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.9.3"
19
+ __version__ = "0.9.4"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -252,6 +252,42 @@ def load_cell(path: Path) -> Cell:
252
252
  return cell
253
253
 
254
254
 
255
+ def _write_session_sidecar(target: Path, session_id: str, cwd) -> None:
256
+ """Write the two-line ``{uuid, cwd}`` session sidecar atomically.
257
+
258
+ v0.9.4 (session-pin cwd-mismatch fix): the sidecar records the cwd
259
+ the session UUID was minted for so a later spawn from a DIFFERENT
260
+ cwd can detect the mismatch and re-pin a fresh UUID rather than
261
+ handing a project-scoped UUID to ``claude --resume`` from the wrong
262
+ project (which dies with "No conversation found with session ID").
263
+
264
+ Format (trailing-newline convention preserved):
265
+
266
+ <uuid>\\n
267
+ <cwd>\\n
268
+ """
269
+ target.parent.mkdir(parents=True, exist_ok=True)
270
+ _atomic_write_text(target, f"{session_id}\n{str(cwd)}\n")
271
+
272
+
273
+ def _read_session_sidecar(path: Path) -> tuple[Optional[str], Optional[str]]:
274
+ """Parse a session sidecar → ``(uuid, cwd)``.
275
+
276
+ Returns ``(None, None)`` for a missing/empty file. ``cwd`` is
277
+ ``None`` for a **legacy one-line** sidecar (bare UUID, pre-0.9.4)
278
+ — we cannot know which project that UUID belongs to.
279
+ """
280
+ if not path.exists():
281
+ return None, None
282
+ raw = path.read_text(encoding="utf-8")
283
+ lines = raw.splitlines()
284
+ if not lines:
285
+ return None, None
286
+ uuid_str = lines[0].strip() or None
287
+ cwd_str = lines[1].strip() if len(lines) >= 2 and lines[1].strip() else None
288
+ return uuid_str, cwd_str
289
+
290
+
255
291
  def load_or_create_session_id(
256
292
  role: str,
257
293
  cell: Cell,
@@ -266,6 +302,10 @@ def load_or_create_session_id(
266
302
  2. ``new_instance=True`` AND base sidecar exists — auto-suffix slot
267
303
  3. ``new_instance=True`` AND no base sidecar — fall through (degenerate)
268
304
  4. session_state_path(role) reused — default re-resume path
305
+ (v0.9.4: only when the sidecar's recorded cwd matches cell.cwd;
306
+ on cwd-mismatch a fresh UUID is minted + re-pinned for the new
307
+ cwd, because ``claude --resume`` is PROJECT-scoped and a pin
308
+ from another cwd dies with "No conversation found")
269
309
  5. mint new uuid4 + persist
270
310
 
271
311
  Caller-side discipline (mother iter-1 #986): the ``effective_role``
@@ -282,23 +322,33 @@ def load_or_create_session_id(
282
322
  sibling_role = next_free_slot_role(role)
283
323
  sibling_state = session_state_path(sibling_role)
284
324
  new_id = str(uuid.uuid4())
285
- sibling_state.parent.mkdir(parents=True, exist_ok=True)
286
- _atomic_write_text(sibling_state, new_id + "\n")
325
+ _write_session_sidecar(sibling_state, new_id, cell.cwd)
287
326
  return new_id, True, sibling_role
288
327
 
289
328
  state_file = session_state_path(role)
290
329
  if state_file.exists():
291
- existing = state_file.read_text(encoding="utf-8").strip()
292
- if existing:
330
+ existing_uuid, recorded_cwd = _read_session_sidecar(state_file)
331
+ if existing_uuid:
293
332
  try:
294
- return validate_uuid_str(existing), False, role
333
+ valid_uuid = validate_uuid_str(existing_uuid)
295
334
  except CellError:
296
335
  # Corrupted state — fall through and regenerate.
297
- pass
336
+ valid_uuid = None
337
+ if valid_uuid is not None:
338
+ if recorded_cwd is not None and recorded_cwd != str(cell.cwd):
339
+ # v0.9.4: pin belongs to a different project. A
340
+ # project-scoped --resume would die from this cwd, so
341
+ # mint a fresh (clean) UUID + re-pin for the new cwd.
342
+ fresh = str(uuid.uuid4())
343
+ _write_session_sidecar(state_file, fresh, cell.cwd)
344
+ return fresh, True, role
345
+ # Legacy (recorded_cwd is None) → reuse as-is; do NOT
346
+ # rewrite, since the UUID's true origin cwd is unknown.
347
+ # Matching cwd → reuse.
348
+ return valid_uuid, False, role
298
349
 
299
350
  new_id = str(uuid.uuid4())
300
- state_file.parent.mkdir(parents=True, exist_ok=True)
301
- _atomic_write_text(state_file, new_id + "\n")
351
+ _write_session_sidecar(state_file, new_id, cell.cwd)
302
352
  return new_id, True, role
303
353
 
304
354
 
@@ -352,6 +402,8 @@ __all__ = [
352
402
  "read_starter_prompt",
353
403
  "load_cell",
354
404
  "load_or_create_session_id",
405
+ "_read_session_sidecar",
406
+ "_write_session_sidecar",
355
407
  # Backward-compat aliases
356
408
  "_PEER_NAME_RE",
357
409
  "_VALID_SCHEMA_VERSIONS",
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.9.3
4
- Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider spawn (claude/codex/antigravity per cell.provider via a ProviderMembrane), `swarph init` (interactive cell.yaml scaffolder), `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.3 adds `swarph init` + hardens assisted_memory restore (fail-closed pull, empty-guards, codex AGENTS.md) and the mesh sidecar (no DM-loss on idle-guard).
3
+ Version: 0.9.4
4
+ Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider `swarph spawn` (claude/codex/antigravity per cell.provider via a ProviderMembrane + subprocess billing-scrub), interactive `swarph init`, `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.4: fixes session-pin resume across a changed cwd.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/darw007d/swarph-cli
@@ -22,6 +22,8 @@ from swarph_cli.cell import (
22
22
  load_or_create_session_id,
23
23
  resolve_cell_path,
24
24
  session_state_path,
25
+ _read_session_sidecar,
26
+ _write_session_sidecar,
25
27
  )
26
28
 
27
29
 
@@ -264,7 +266,9 @@ def test_load_or_create_session_id_mints_and_persists(
264
266
  uuid.UUID(sid1) # raises if not a valid UUID
265
267
  sidecar = session_state_path("lab")
266
268
  assert sidecar.exists()
267
- assert sidecar.read_text().strip() == sid1
269
+ rec_uuid, rec_cwd = _read_session_sidecar(sidecar)
270
+ assert rec_uuid == sid1
271
+ assert rec_cwd == str(cell.cwd)
268
272
 
269
273
 
270
274
  def test_load_or_create_session_id_reuses_persisted_value(
@@ -287,7 +291,102 @@ def test_load_or_create_session_id_corrupted_sidecar_regenerates(
287
291
  sid, generated, _role = load_or_create_session_id("lab", cell)
288
292
  uuid.UUID(sid)
289
293
  assert generated is True
290
- assert sidecar.read_text().strip() == sid
294
+ rec_uuid, _rec_cwd = _read_session_sidecar(sidecar)
295
+ assert rec_uuid == sid
296
+
297
+
298
+ # ---------------------------------------------------------------------------
299
+ # v0.9.4 — session-pin cwd-mismatch fix (project-scoped --resume)
300
+ # ---------------------------------------------------------------------------
301
+
302
+
303
+ def test_session_sidecar_fresh_mint_writes_two_line_uuid_and_cwd(
304
+ isolated_xdg, cell_yaml_factory, tmp_path
305
+ ):
306
+ """v0.9.4 — first mint records BOTH the uuid AND the cwd (cwd=A)."""
307
+ cell = load_cell(cell_yaml_factory()) # cwd == tmp_path (== A)
308
+ sid, gen, _role = load_or_create_session_id("lab", cell)
309
+ assert gen is True
310
+ uuid.UUID(sid)
311
+ rec_uuid, rec_cwd = _read_session_sidecar(session_state_path("lab"))
312
+ assert rec_uuid == sid
313
+ assert rec_cwd == str(tmp_path)
314
+ assert rec_cwd == str(cell.cwd)
315
+
316
+
317
+ def test_session_sidecar_same_cwd_reuses_same_uuid(
318
+ isolated_xdg, cell_yaml_factory, tmp_path
319
+ ):
320
+ """v0.9.4 — pre-pinned {uuid=U, cwd=A}; spawn from cwd=A reuses U."""
321
+ cell = load_cell(cell_yaml_factory()) # cwd == tmp_path (== A)
322
+ fixed = "550e8400-e29b-41d4-a716-446655440000"
323
+ _write_session_sidecar(session_state_path("lab"), fixed, str(tmp_path))
324
+ sid, gen, role = load_or_create_session_id("lab", cell)
325
+ assert sid == fixed
326
+ assert gen is False
327
+ assert role == "lab"
328
+
329
+
330
+ def test_session_sidecar_different_cwd_mints_fresh_and_repins(
331
+ isolated_xdg, cell_yaml_factory, tmp_path
332
+ ):
333
+ """v0.9.4 REGRESSION — the bug: pin minted for cwd=A, spawn from cwd=B.
334
+
335
+ A project-scoped ``claude --resume <U>`` from B dies with
336
+ "No conversation found". Fix: mint a FRESH (clean) uuid and re-pin
337
+ it for cwd=B.
338
+ """
339
+ cwd_b = tmp_path / "project_b"
340
+ cwd_b.mkdir()
341
+ cell = load_cell(cell_yaml_factory(cwd=str(cwd_b))) # cell.cwd == B
342
+ fixed = "550e8400-e29b-41d4-a716-446655440000" # pinned for cwd=A
343
+ _write_session_sidecar(session_state_path("lab"), fixed, str(tmp_path))
344
+
345
+ sid, gen, role = load_or_create_session_id("lab", cell)
346
+ assert gen is True
347
+ assert sid != fixed # genuine fresh UUID
348
+ uuid.UUID(sid)
349
+ assert role == "lab"
350
+ # Sidecar now re-pinned for cwd=B with the fresh UUID.
351
+ rec_uuid, rec_cwd = _read_session_sidecar(session_state_path("lab"))
352
+ assert rec_uuid == sid
353
+ assert rec_cwd == str(cwd_b)
354
+
355
+
356
+ def test_session_sidecar_legacy_one_line_is_reused_not_reminted(
357
+ isolated_xdg, cell_yaml_factory, tmp_path
358
+ ):
359
+ """v0.9.4 back-compat — legacy bare-uuid (no cwd) sidecar is reused
360
+ as-is regardless of cwd (origin cwd unknown; do NOT re-mint)."""
361
+ cell = load_cell(cell_yaml_factory())
362
+ fixed = "550e8400-e29b-41d4-a716-446655440000"
363
+ sidecar = session_state_path("lab")
364
+ sidecar.parent.mkdir(parents=True, exist_ok=True)
365
+ sidecar.write_text(fixed + "\n") # legacy one-line format
366
+ sid, gen, role = load_or_create_session_id("lab", cell)
367
+ assert sid == fixed
368
+ assert gen is False
369
+ assert role == "lab"
370
+ # Not upgraded/rewritten — stays legacy one-line.
371
+ assert sidecar.read_text() == fixed + "\n"
372
+
373
+
374
+ def test_read_session_sidecar_parses_both_formats(tmp_path):
375
+ """v0.9.4 — _read_session_sidecar handles 2-line AND legacy 1-line."""
376
+ two_line = tmp_path / "two.session-id"
377
+ two_line.write_text("550e8400-e29b-41d4-a716-446655440000\n/home/x/proj\n")
378
+ assert _read_session_sidecar(two_line) == (
379
+ "550e8400-e29b-41d4-a716-446655440000",
380
+ "/home/x/proj",
381
+ )
382
+ one_line = tmp_path / "one.session-id"
383
+ one_line.write_text("550e8400-e29b-41d4-a716-446655440000\n")
384
+ assert _read_session_sidecar(one_line) == (
385
+ "550e8400-e29b-41d4-a716-446655440000",
386
+ None,
387
+ )
388
+ missing = tmp_path / "missing.session-id"
389
+ assert _read_session_sidecar(missing) == (None, None)
291
390
 
292
391
 
293
392
  def test_load_or_create_session_id_atomic_no_tempfile_left_behind(
@@ -435,8 +534,8 @@ def test_new_instance_with_existing_base_mints_into_slot_2(
435
534
  base_path = session_state_path("drop")
436
535
  sib_path = session_state_path("drop-2")
437
536
  assert base_path.exists() and sib_path.exists()
438
- assert base_path.read_text().strip() == sid_base
439
- assert sib_path.read_text().strip() == sid_sib
537
+ assert _read_session_sidecar(base_path)[0] == sid_base
538
+ assert _read_session_sidecar(sib_path)[0] == sid_sib
440
539
 
441
540
 
442
541
  def test_new_instance_third_call_lands_at_slot_3(
@@ -459,7 +459,7 @@ def test_run_spawn_new_instance_with_base_mints_sibling_slot(
459
459
  ):
460
460
  """v0.7 PR-B — `--new-instance` with base sidecar present allocates
461
461
  slot 2 AND persists. Sibling resumable via `swarph spawn <role>-2`."""
462
- from swarph_cli.cell import session_state_path
462
+ from swarph_cli.cell import session_state_path, _read_session_sidecar
463
463
 
464
464
  # First spawn establishes base slot
465
465
  run_spawn(argv=[str(fake_cell_yaml), "--dry-run", "--print-id"])
@@ -474,8 +474,8 @@ def test_run_spawn_new_instance_with_base_mints_sibling_slot(
474
474
 
475
475
  base_sidecar = session_state_path("lab-test")
476
476
  sibling_sidecar = session_state_path("lab-test-2")
477
- assert base_sidecar.read_text().strip() == base_uuid
478
- assert sibling_sidecar.read_text().strip() == sibling_uuid
477
+ assert _read_session_sidecar(base_sidecar)[0] == base_uuid
478
+ assert _read_session_sidecar(sibling_sidecar)[0] == sibling_uuid
479
479
 
480
480
  # Dry-run output shows the sibling slot label
481
481
  assert "lab-test-2" in captured.err
File without changes
File without changes
File without changes