swarph-cli 0.13.2__tar.gz → 0.13.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.
- {swarph_cli-0.13.2/src/swarph_cli.egg-info → swarph_cli-0.13.4}/PKG-INFO +1 -1
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/pyproject.toml +1 -1
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/chat.py +9 -5
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/compress/levers.py +7 -1
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/compress/verify.py +6 -1
- {swarph_cli-0.13.2 → swarph_cli-0.13.4/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli.egg-info/SOURCES.txt +4 -0
- swarph_cli-0.13.4/tests/test_caller_meta_guard.py +117 -0
- swarph_cli-0.13.4/tests/test_compress_caller_convention.py +21 -0
- swarph_cli-0.13.4/tests/test_repl_caller_convention.py +43 -0
- swarph_cli-0.13.4/tests/test_version_consistency.py +26 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/LICENSE +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/README.md +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/setup.cfg +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_import_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_init_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_main.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_security.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.13.2 → swarph_cli-0.13.4}/tests/test_watchdog_stale_peers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.4
|
|
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.5: foolproof Windows auto-relaunch (genuine-WT detection via process ancestry, not WT_SESSION).
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.13.
|
|
7
|
+
version = "0.13.4"
|
|
8
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.5: foolproof Windows auto-relaunch (genuine-WT detection via process ancestry, not WT_SESSION)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -35,7 +35,7 @@ import sys
|
|
|
35
35
|
from dataclasses import dataclass, field
|
|
36
36
|
from typing import Optional
|
|
37
37
|
|
|
38
|
-
from swarph_cli.caller import default_caller
|
|
38
|
+
from swarph_cli.caller import _sanitize_username, default_caller
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
_BANNER = """\
|
|
@@ -123,13 +123,17 @@ def _default_repl_caller() -> str:
|
|
|
123
123
|
"""``cli.repl.<user>`` — distinct from ``cli.oneshot.<user>`` so
|
|
124
124
|
attribution rows distinguish REPL turns from one-shot calls.
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
Reuses ``caller._sanitize_username`` (the SAME sanitizer ``default_caller``
|
|
127
|
+
uses) so the slug is guaranteed convention-valid: leading letter (``u_``
|
|
128
|
+
prefix if not), collapsed underscores, ``unknown`` on empty. The previous
|
|
129
|
+
inline slug omitted those guards, so a leading-digit username produced a
|
|
130
|
+
non-conforming ``cli.repl.3bob`` that crashed SwarphCall at runtime (#83).
|
|
131
|
+
Falls back to the caller-convention default if env-derived user extraction
|
|
132
|
+
fails."""
|
|
128
133
|
user = os.environ.get("USER") or os.environ.get("LOGNAME")
|
|
129
134
|
if not user:
|
|
130
135
|
return default_caller()
|
|
131
|
-
|
|
132
|
-
return f"cli.repl.{user_slug}"
|
|
136
|
+
return f"cli.repl.{_sanitize_username(user)}"
|
|
133
137
|
|
|
134
138
|
|
|
135
139
|
def _print(msg: str = "", *, file=None) -> None:
|
|
@@ -4,6 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
import re
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
|
+
# Caller tag for the shorthand model call. MUST satisfy swarph_shared's
|
|
8
|
+
# caller convention (^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$ — dotted, not
|
|
9
|
+
# hyphenated), or SwarphCall(...) raises ValueError at runtime. See
|
|
10
|
+
# tests/test_compress_caller_convention.py.
|
|
11
|
+
SHORTHAND_CALLER = "swarph.compress"
|
|
12
|
+
|
|
7
13
|
|
|
8
14
|
@dataclass
|
|
9
15
|
class ArchivalResult:
|
|
@@ -41,7 +47,7 @@ async def shorthand(source: str, *, system_prompt: str = SHORTHAND_SYSTEM, chat=
|
|
|
41
47
|
(messages, system_prompt, **kw)->resp with .text; defaults to swarph_mesh."""
|
|
42
48
|
if chat is None:
|
|
43
49
|
from swarph_mesh import ChatMessage, SwarphCall
|
|
44
|
-
sc = SwarphCall(provider="claude", caller=
|
|
50
|
+
sc = SwarphCall(provider="claude", caller=SHORTHAND_CALLER)
|
|
45
51
|
async def chat(messages, system_prompt=None, **kw):
|
|
46
52
|
return await sc.chat(messages=messages, system_prompt=system_prompt, **kw)
|
|
47
53
|
msgs = [ChatMessage(role="user", content=source)]
|
|
@@ -4,6 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
import gzip, re
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
# Caller tag for the adversarial verify-expand model call. MUST satisfy
|
|
8
|
+
# swarph_shared's dotted caller convention or SwarphCall raises at runtime.
|
|
9
|
+
# See tests/test_compress_caller_convention.py.
|
|
10
|
+
VERIFY_EXPAND_CALLER = "swarph.compress.verify"
|
|
11
|
+
|
|
7
12
|
_LINK_RE = re.compile(r"\]\(([^)]+)\)")
|
|
8
13
|
|
|
9
14
|
|
|
@@ -67,7 +72,7 @@ async def verify_expand(source: str, compressed: str, *, chat=None) -> bool:
|
|
|
67
72
|
fact in source not recoverable from compressed. One dropped fact -> False."""
|
|
68
73
|
if chat is None:
|
|
69
74
|
from swarph_mesh import ChatMessage, SwarphCall
|
|
70
|
-
sc = SwarphCall(provider="claude", caller=
|
|
75
|
+
sc = SwarphCall(provider="claude", caller=VERIFY_EXPAND_CALLER)
|
|
71
76
|
async def chat(messages, system_prompt=None, **kw):
|
|
72
77
|
return await sc.chat(messages=messages, system_prompt=system_prompt, **kw)
|
|
73
78
|
msgs = [ChatMessage(role="user", content=f"ORIGINAL:\n{source}\n\nCOMPRESSED:\n{compressed}")]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.4
|
|
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.5: foolproof Windows auto-relaunch (genuine-WT detection via process ancestry, not WT_SESSION).
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -55,6 +55,7 @@ tests/test_artifact_mcp.py
|
|
|
55
55
|
tests/test_artifact_skill.py
|
|
56
56
|
tests/test_artifact_tool.py
|
|
57
57
|
tests/test_artifact_uri.py
|
|
58
|
+
tests/test_caller_meta_guard.py
|
|
58
59
|
tests/test_capture_lineage.py
|
|
59
60
|
tests/test_capture_liveness.py
|
|
60
61
|
tests/test_capture_manifest.py
|
|
@@ -67,6 +68,7 @@ tests/test_cell_verify.py
|
|
|
67
68
|
tests/test_chat_command.py
|
|
68
69
|
tests/test_claude_parser.py
|
|
69
70
|
tests/test_claude_tmux_template.py
|
|
71
|
+
tests/test_compress_caller_convention.py
|
|
70
72
|
tests/test_compress_command.py
|
|
71
73
|
tests/test_compress_levers.py
|
|
72
74
|
tests/test_compress_marker.py
|
|
@@ -90,6 +92,7 @@ tests/test_mesh_sidecar.py
|
|
|
90
92
|
tests/test_onboard_command.py
|
|
91
93
|
tests/test_protocol_handler.py
|
|
92
94
|
tests/test_ratify_command.py
|
|
95
|
+
tests/test_repl_caller_convention.py
|
|
93
96
|
tests/test_security.py
|
|
94
97
|
tests/test_session_path_role_gate.py
|
|
95
98
|
tests/test_smoke_chat.py
|
|
@@ -100,6 +103,7 @@ tests/test_spawn_live_pin.py
|
|
|
100
103
|
tests/test_spawn_mitosis_lineage.py
|
|
101
104
|
tests/test_spawn_tmux_session.py
|
|
102
105
|
tests/test_spawn_windows_relaunch.py
|
|
106
|
+
tests/test_version_consistency.py
|
|
103
107
|
tests/test_watchdog.py
|
|
104
108
|
tests/test_watchdog_dm_wake.py
|
|
105
109
|
tests/test_watchdog_dm_wake_cooldown.py
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Repo-wide caller-convention meta-guard (issue #81).
|
|
2
|
+
|
|
3
|
+
The #80 bug: a hyphenated caller tag (`swarph-compress`) was silently dead at
|
|
4
|
+
runtime because it fails swarph_shared's dotted `role.subrole` convention — and it
|
|
5
|
+
was masked because the levers/verify tests injected a fake chat and never built a
|
|
6
|
+
real SwarphCall. That fix added a per-caller regression test for the two compress
|
|
7
|
+
callers. THIS guard generalizes it: it discovers EVERY static caller tag across the
|
|
8
|
+
whole `swarph_cli` package by import-introspection and validates each, so the bug
|
|
9
|
+
class can't be re-introduced in ANY module without someone remembering to add a
|
|
10
|
+
per-caller test.
|
|
11
|
+
|
|
12
|
+
Design (converged lab+droplet, #81): import-introspection, not AST literal-walking.
|
|
13
|
+
The callers live in named `*_CALLER` constants — `caller=SHORTHAND_CALLER` is an
|
|
14
|
+
ast.Name, not a literal, so a naive literal-walk would miss exactly the callers #80
|
|
15
|
+
hardened. Introspection resolves the value for free and nudges the light convention
|
|
16
|
+
that caller tags live in discoverable `*_CALLER` constants.
|
|
17
|
+
|
|
18
|
+
NOT covered here (separate follow-up, flagged in the PR): the DYNAMIC default-caller
|
|
19
|
+
producers (`default_caller` / `_default_repl_caller`) do not guarantee an
|
|
20
|
+
[a-z]-leading slug, so a leading-digit/empty OS username yields a non-conforming
|
|
21
|
+
`cli.repl.<digit...>` tag — a real robustness bug in the sanitizers, out of scope
|
|
22
|
+
for this static meta-guard.
|
|
23
|
+
"""
|
|
24
|
+
import importlib
|
|
25
|
+
import pkgutil
|
|
26
|
+
|
|
27
|
+
import swarph_cli
|
|
28
|
+
from swarph_shared.caller_convention import validate_caller
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _iter_caller_constants():
|
|
32
|
+
"""Yield (module, attr, value) for every module-level ``*_CALLER`` str constant
|
|
33
|
+
across the swarph_cli package. Walks the package so a NEW module's caller
|
|
34
|
+
constant is covered automatically. A submodule that can't be imported in this
|
|
35
|
+
env (e.g. an optional dep) is skipped — importability is other tests' concern,
|
|
36
|
+
not this guard's."""
|
|
37
|
+
for mod in pkgutil.walk_packages(swarph_cli.__path__, swarph_cli.__name__ + "."):
|
|
38
|
+
try:
|
|
39
|
+
m = importlib.import_module(mod.name)
|
|
40
|
+
except Exception:
|
|
41
|
+
continue
|
|
42
|
+
for attr in dir(m):
|
|
43
|
+
if attr.endswith("_CALLER"):
|
|
44
|
+
val = getattr(m, attr, None)
|
|
45
|
+
if isinstance(val, str):
|
|
46
|
+
yield mod.name, attr, val
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_all_caller_constants_conform():
|
|
50
|
+
"""Every ``*_CALLER`` constant in the package satisfies the dotted convention."""
|
|
51
|
+
found = list(_iter_caller_constants())
|
|
52
|
+
# Sanity: the compress callers from #80 must be discovered, else the walk is broken
|
|
53
|
+
# (e.g. swarph_cli resolving to a stale install rather than this tree).
|
|
54
|
+
names = {f"{m.rsplit('.', 1)[-1]}.{a}" for m, a, _ in found}
|
|
55
|
+
assert "levers.SHORTHAND_CALLER" in names and "verify.VERIFY_EXPAND_CALLER" in names, (
|
|
56
|
+
f"meta-guard didn't discover the known compress callers; found: {sorted(names)}")
|
|
57
|
+
for modname, attr, val in found:
|
|
58
|
+
try:
|
|
59
|
+
validate_caller(val)
|
|
60
|
+
except ValueError as e:
|
|
61
|
+
raise AssertionError(
|
|
62
|
+
f"caller constant {modname}.{attr} = {val!r} violates the convention "
|
|
63
|
+
f"(dotted role.subrole) — would crash SwarphCall at runtime: {e}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_default_caller_producers_conform_happy_path(monkeypatch):
|
|
67
|
+
"""The default-caller producers emit a conforming tag for a normal username.
|
|
68
|
+
Username is injected so this is deterministic regardless of the CI runner's
|
|
69
|
+
real user (the adversarial leading-digit case is a flagged follow-up, not here)."""
|
|
70
|
+
monkeypatch.setenv("USER", "alice")
|
|
71
|
+
monkeypatch.setenv("LOGNAME", "alice")
|
|
72
|
+
from swarph_cli.main import default_caller
|
|
73
|
+
from swarph_cli.commands.chat import _default_repl_caller
|
|
74
|
+
for fn in (default_caller, _default_repl_caller):
|
|
75
|
+
val = fn()
|
|
76
|
+
try:
|
|
77
|
+
validate_caller(val)
|
|
78
|
+
except ValueError as e:
|
|
79
|
+
raise AssertionError(
|
|
80
|
+
f"{fn.__module__}.{fn.__name__}() -> {val!r} violates the convention: {e}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_guard_catches_a_planted_bad_caller(monkeypatch):
|
|
84
|
+
"""PIN the catch-property — a guard's catch must be COMMITTED, not remembered
|
|
85
|
+
(the very #80 lesson this PR generalizes; droplet review). Plant a hyphenated
|
|
86
|
+
`*_CALLER` on a real, discovered module and assert the guard both DISCOVERS it
|
|
87
|
+
and REJECTS it. monkeypatch auto-removes the plant so the real guard test stays
|
|
88
|
+
clean regardless of order."""
|
|
89
|
+
import pytest
|
|
90
|
+
|
|
91
|
+
import swarph_cli.compress.levers as lev
|
|
92
|
+
monkeypatch.setattr(lev, "PLANTED_BAD_CALLER", "swarph-bad-hyphen", raising=False)
|
|
93
|
+
|
|
94
|
+
# 1) introspection DISCOVERS the planted constant
|
|
95
|
+
found = {f"{m}.{a}": v for m, a, v in _iter_caller_constants()}
|
|
96
|
+
assert found.get("swarph_cli.compress.levers.PLANTED_BAD_CALLER") == "swarph-bad-hyphen", (
|
|
97
|
+
"the walk failed to discover a planted *_CALLER — discovery is broken")
|
|
98
|
+
|
|
99
|
+
# 2) the guard CATCHES it (fails loud, names the offending constant)
|
|
100
|
+
with pytest.raises(AssertionError, match="violates the convention"):
|
|
101
|
+
test_all_caller_constants_conform()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_no_modules_skipped_by_the_walk():
|
|
105
|
+
"""The guard resiliently SKIPS an unimportable submodule (so an unrelated optional-
|
|
106
|
+
dep import error can't break it) — but a silent skip could hide a bad caller in a
|
|
107
|
+
module that's importable in prod yet not in this env. Assert NONE are skipped here,
|
|
108
|
+
so the walk is exhaustive; if this ever fails in CI, run with full extras."""
|
|
109
|
+
skipped = []
|
|
110
|
+
for mod in pkgutil.walk_packages(swarph_cli.__path__, swarph_cli.__name__ + "."):
|
|
111
|
+
try:
|
|
112
|
+
importlib.import_module(mod.name)
|
|
113
|
+
except Exception as e: # noqa: BLE001 — we want to surface ANY import failure
|
|
114
|
+
skipped.append((mod.name, repr(e)))
|
|
115
|
+
assert not skipped, (
|
|
116
|
+
f"caller meta-guard walk skipped modules (a bad caller in one could ship "
|
|
117
|
+
f"uncaught): {skipped}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Regression: the compress model-call caller tags MUST satisfy swarph_shared's
|
|
2
|
+
caller convention (dotted `role.subrole`, not hyphenated). The existing
|
|
3
|
+
levers/verify tests inject a fake `chat` and never construct a real SwarphCall,
|
|
4
|
+
so they passed while the production path crashed at SwarphCall(...) on a
|
|
5
|
+
hyphenated caller ('swarph-compress'). This test checks the tags directly so
|
|
6
|
+
the break can't silently regress again."""
|
|
7
|
+
from swarph_shared.caller_convention import validate_caller
|
|
8
|
+
|
|
9
|
+
from swarph_cli.compress.levers import SHORTHAND_CALLER
|
|
10
|
+
from swarph_cli.compress.verify import VERIFY_EXPAND_CALLER
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_shorthand_caller_matches_convention():
|
|
14
|
+
# raises ValueError if it doesn't match ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$
|
|
15
|
+
validate_caller(SHORTHAND_CALLER)
|
|
16
|
+
assert SHORTHAND_CALLER == "swarph.compress"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_verify_expand_caller_matches_convention():
|
|
20
|
+
validate_caller(VERIFY_EXPAND_CALLER)
|
|
21
|
+
assert VERIFY_EXPAND_CALLER == "swarph.compress.verify"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Regression (#83): `_default_repl_caller` must emit a convention-valid caller for
|
|
2
|
+
ANY OS username — including the adversarial cases the old inline slug logic broke on.
|
|
3
|
+
|
|
4
|
+
#80 fixed a hyphenated STATIC caller constant. This is the same crash-class on the
|
|
5
|
+
DYNAMIC repl-default path: `_default_repl_caller` rolled its own slug
|
|
6
|
+
(`"".join(c if c.isalnum() else "_" ...)`) that — unlike `caller._sanitize_username`
|
|
7
|
+
which `default_caller` uses — did NOT guarantee a leading letter, collapse underscores,
|
|
8
|
+
or fall back on empty. So a leading-digit username produced `cli.repl.3bob`, which
|
|
9
|
+
fails `^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$` and would crash SwarphCall at runtime.
|
|
10
|
+
Fix: reuse `_sanitize_username` (single sanitizer, no divergence).
|
|
11
|
+
"""
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from swarph_shared.caller_convention import validate_caller
|
|
15
|
+
|
|
16
|
+
from swarph_cli.commands.chat import _default_repl_caller
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize("username", [
|
|
20
|
+
"3bob", # leading digit — the reported crash
|
|
21
|
+
"9", # all-digit
|
|
22
|
+
"!!!", # all-special -> empty slug -> must fall back, not emit `cli.repl.`
|
|
23
|
+
"ALICE", # uppercase
|
|
24
|
+
"bob.smith", # dot (would otherwise split the segment)
|
|
25
|
+
"a-b-c", # hyphens (the #80 char)
|
|
26
|
+
"_", # underscore-only -> empty after strip
|
|
27
|
+
" ", # whitespace-only
|
|
28
|
+
])
|
|
29
|
+
def test_repl_caller_conforms_for_adversarial_usernames(monkeypatch, username):
|
|
30
|
+
"""The repl default caller validates clean for every adversarial username."""
|
|
31
|
+
monkeypatch.setenv("USER", username)
|
|
32
|
+
monkeypatch.delenv("LOGNAME", raising=False)
|
|
33
|
+
# raises ValueError if the produced tag violates the convention
|
|
34
|
+
validate_caller(_default_repl_caller())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_repl_caller_stays_distinct_from_oneshot(monkeypatch):
|
|
38
|
+
"""Still `cli.repl.*` (not `cli.oneshot.*`) — the separate producer exists so
|
|
39
|
+
attribution distinguishes REPL turns from one-shot calls; the fix must not
|
|
40
|
+
collapse them."""
|
|
41
|
+
monkeypatch.setenv("USER", "alice")
|
|
42
|
+
monkeypatch.delenv("LOGNAME", raising=False)
|
|
43
|
+
assert _default_repl_caller().startswith("cli.repl.")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Guard against the 0.13.3 bug class: the __version__ constant in
|
|
2
|
+
src/swarph_cli/__init__.py drifting from the pyproject version (0.13.3 shipped
|
|
3
|
+
with the constant left at 0.13.2, so `swarph --version` misreported). Reads both
|
|
4
|
+
straight from the source tree so it validates what will be built, independent of
|
|
5
|
+
whatever is pip-installed in the test env."""
|
|
6
|
+
import pathlib
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _read_version(text: str, pattern: str) -> str:
|
|
11
|
+
m = re.search(pattern, text, re.MULTILINE)
|
|
12
|
+
assert m, f"version not found via {pattern!r}"
|
|
13
|
+
return m.group(1)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_version_constant_matches_pyproject():
|
|
17
|
+
root = pathlib.Path(__file__).resolve().parent.parent
|
|
18
|
+
pyproject_version = _read_version(
|
|
19
|
+
(root / "pyproject.toml").read_text(), r'^version\s*=\s*"([^"]+)"')
|
|
20
|
+
init_version = _read_version(
|
|
21
|
+
(root / "src" / "swarph_cli" / "__init__.py").read_text(),
|
|
22
|
+
r'^__version__\s*=\s*"([^"]+)"')
|
|
23
|
+
assert init_version == pyproject_version, (
|
|
24
|
+
f"src/swarph_cli/__init__.py __version__={init_version!r} but pyproject "
|
|
25
|
+
f"version={pyproject_version!r}. Bump BOTH on every release."
|
|
26
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|