loom-code 0.2.0__tar.gz → 0.2.1__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.
- {loom_code-0.2.0 → loom_code-0.2.1}/PKG-INFO +1 -1
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/__init__.py +1 -1
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/hooks.py +21 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/repl.py +6 -1
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/PKG-INFO +1 -1
- {loom_code-0.2.0 → loom_code-0.2.1}/pyproject.toml +2 -2
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_loom_hooks.py +21 -6
- {loom_code-0.2.0 → loom_code-0.2.1}/LICENSE +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/README.md +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/_post_commit.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/agent.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/approval.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/background.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/__init__.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/act.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/observe.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/session.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/verify.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/checkpoint.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/cli.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/code_index.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/compact.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/consent.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/context_report.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/credentials.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/diagnostics.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/edit_tool.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/extensions.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/file_history.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/file_tools.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/git_hook.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/grep_tool.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/__init__.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_ast_walk.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_files.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_graph.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_resolve.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_tests_map.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/extractor.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/repomap.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/schema.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loop_guard.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/lsp_tools.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/mcp_host.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/operator.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/paste.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/paths.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/permissions.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/project.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/prompts.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/render.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/rules.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/sandboxed_bash.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/scribe.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/skills/__init__.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/skills/graphify/SKILL.md +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/skills/graphify/tools.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/trust.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/turn.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/verify_gate.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/web_fetch.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/workers.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/worktree.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/SOURCES.txt +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/dependency_links.txt +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/entry_points.txt +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/requires.txt +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/top_level.txt +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/setup.cfg +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_agent.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_antipoison_gate.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_approval.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_approval_danger.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_approval_integration.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_background.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_checkpoint.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_code_index.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_compact.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_context_observability.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_credentials.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_custom_commands.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_diagnostics.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_edit_tool.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_extensions.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_file_boundary.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_file_history.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_git_hook.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_goal_state.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_graphify_file_discovery.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_graphify_query_tiers.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_graphify_wiring.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_grep_tool.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_learned_notes.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_loop_guard.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_lsp_tools.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_mcp.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_paste.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_permissions.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_pricing.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_project.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_prompts.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_render.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_repl_guards.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_resume_migration.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_resume_preview.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_routing.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_rules.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_sandboxed_bash.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_session_tree.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_stream_liveness.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_turn_economy.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_verify_gate.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_web_fetch.py +0 -0
- {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_workers.py +0 -0
|
@@ -101,6 +101,27 @@ def _fire_and_forget(
|
|
|
101
101
|
task.add_done_callback(_background_tasks.discard)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
async def drain_background_hooks(deadline_s: float = 5.0) -> None:
|
|
105
|
+
"""Await every in-flight background hook (bounded by
|
|
106
|
+
``deadline_s`` wall-clock). The REPL calls this on exit so a
|
|
107
|
+
fire-and-forget notification isn't cut off when the process ends;
|
|
108
|
+
tests call it to keep a scheduled task from outliving its event
|
|
109
|
+
loop (which surfaces as loop-teardown noise and, under load,
|
|
110
|
+
cross-test interference). No-op when idle."""
|
|
111
|
+
import asyncio
|
|
112
|
+
|
|
113
|
+
tasks = list(_background_tasks)
|
|
114
|
+
if not tasks:
|
|
115
|
+
return
|
|
116
|
+
try:
|
|
117
|
+
await asyncio.wait_for(
|
|
118
|
+
asyncio.gather(*tasks, return_exceptions=True),
|
|
119
|
+
timeout=deadline_s,
|
|
120
|
+
)
|
|
121
|
+
except TimeoutError:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
104
125
|
async def _run(
|
|
105
126
|
spec: HookSpec, payload: dict[str, Any], *, cwd: Path
|
|
106
127
|
) -> HookOutcome:
|
|
@@ -867,7 +867,12 @@ class Repl:
|
|
|
867
867
|
finally:
|
|
868
868
|
await self._aclose_mcp()
|
|
869
869
|
await self._aclose_browsers()
|
|
870
|
-
#
|
|
870
|
+
# Let any in-flight background HOOKS (fire-and-forget
|
|
871
|
+
# notifications) finish rather than being cut off by exit.
|
|
872
|
+
from .hooks import drain_background_hooks
|
|
873
|
+
|
|
874
|
+
await drain_background_hooks(deadline_s=3.0)
|
|
875
|
+
# Background PROCESSES the agent started (dev servers,
|
|
871
876
|
# watchers) die with the session — no orphans. atexit
|
|
872
877
|
# backstops a hard crash; this is the friendly path.
|
|
873
878
|
from . import background
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "loom-code"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "loom-code — a loomflow-native terminal coding agent"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -134,7 +134,7 @@ select = ["E", "F", "I", "B", "UP", "ASYNC"]
|
|
|
134
134
|
# publishing — no tokens). Daily flow stays plain git; ``make release
|
|
135
135
|
# BUMP=patch|minor|major`` is the explicit "this is a release" flag.
|
|
136
136
|
[tool.bumpversion]
|
|
137
|
-
current_version = "0.2.
|
|
137
|
+
current_version = "0.2.1"
|
|
138
138
|
search = "{current_version}"
|
|
139
139
|
replace = "{new_version}"
|
|
140
140
|
commit = true
|
|
@@ -34,6 +34,21 @@ from loom_code.trust import (
|
|
|
34
34
|
pytestmark = pytest.mark.anyio
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
@pytest.fixture(autouse=True)
|
|
38
|
+
def _no_leaked_background_hooks():
|
|
39
|
+
"""Reap any background hook a test scheduled BEFORE the next test's
|
|
40
|
+
event loop starts — a task that outlives its loop tied up the
|
|
41
|
+
subprocess watcher on slower CI runners and mangled a later test's
|
|
42
|
+
hook output (observed once as an empty additionalContext). Cheap,
|
|
43
|
+
deterministic, module-global set so it works across tests."""
|
|
44
|
+
from loom_code import hooks
|
|
45
|
+
|
|
46
|
+
yield
|
|
47
|
+
for task in list(hooks._background_tasks): # noqa: SLF001
|
|
48
|
+
task.cancel()
|
|
49
|
+
hooks._background_tasks.clear() # noqa: SLF001
|
|
50
|
+
|
|
51
|
+
|
|
37
52
|
# ---- matcher --------------------------------------------------------
|
|
38
53
|
|
|
39
54
|
|
|
@@ -376,7 +391,6 @@ async def test_background_repl_hook_does_not_block_or_inject(
|
|
|
376
391
|
) -> None:
|
|
377
392
|
"""background=true: the hook runs (side effect lands) but its
|
|
378
393
|
stdout/exit code are ignored — no context, no block, no wait."""
|
|
379
|
-
import anyio
|
|
380
394
|
|
|
381
395
|
marker = tmp_path / "bg-ran"
|
|
382
396
|
specs = [
|
|
@@ -391,11 +405,12 @@ async def test_background_repl_hook_does_not_block_or_inject(
|
|
|
391
405
|
result = await run_repl_hooks(specs, "SessionEnd", cwd=tmp_path)
|
|
392
406
|
assert not result.blocked
|
|
393
407
|
assert not result.contexts
|
|
394
|
-
# the task
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
408
|
+
# Drain the scheduled task deterministically — and so it never
|
|
409
|
+
# outlives this test's event loop (a leaked task surfaces as
|
|
410
|
+
# loop-teardown noise / cross-test flakiness under CI load).
|
|
411
|
+
from loom_code.hooks import drain_background_hooks
|
|
412
|
+
|
|
413
|
+
await drain_background_hooks()
|
|
399
414
|
assert marker.exists()
|
|
400
415
|
|
|
401
416
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|