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.
Files changed (114) hide show
  1. {loom_code-0.2.0 → loom_code-0.2.1}/PKG-INFO +1 -1
  2. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/__init__.py +1 -1
  3. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/hooks.py +21 -0
  4. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/repl.py +6 -1
  5. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/PKG-INFO +1 -1
  6. {loom_code-0.2.0 → loom_code-0.2.1}/pyproject.toml +2 -2
  7. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_loom_hooks.py +21 -6
  8. {loom_code-0.2.0 → loom_code-0.2.1}/LICENSE +0 -0
  9. {loom_code-0.2.0 → loom_code-0.2.1}/README.md +0 -0
  10. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/_post_commit.py +0 -0
  11. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/agent.py +0 -0
  12. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/approval.py +0 -0
  13. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/background.py +0 -0
  14. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/__init__.py +0 -0
  15. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/act.py +0 -0
  16. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/observe.py +0 -0
  17. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/session.py +0 -0
  18. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/browse/verify.py +0 -0
  19. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/checkpoint.py +0 -0
  20. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/cli.py +0 -0
  21. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/code_index.py +0 -0
  22. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/compact.py +0 -0
  23. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/consent.py +0 -0
  24. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/context_report.py +0 -0
  25. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/credentials.py +0 -0
  26. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/diagnostics.py +0 -0
  27. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/edit_tool.py +0 -0
  28. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/extensions.py +0 -0
  29. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/file_history.py +0 -0
  30. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/file_tools.py +0 -0
  31. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/git_hook.py +0 -0
  32. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/grep_tool.py +0 -0
  33. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/__init__.py +0 -0
  34. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_ast_walk.py +0 -0
  35. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_files.py +0 -0
  36. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_graph.py +0 -0
  37. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_resolve.py +0 -0
  38. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/_tests_map.py +0 -0
  39. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/extractor.py +0 -0
  40. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/repomap.py +0 -0
  41. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loominit/schema.py +0 -0
  42. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/loop_guard.py +0 -0
  43. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/lsp_tools.py +0 -0
  44. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/mcp_host.py +0 -0
  45. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/operator.py +0 -0
  46. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/paste.py +0 -0
  47. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/paths.py +0 -0
  48. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/permissions.py +0 -0
  49. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/project.py +0 -0
  50. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/prompts.py +0 -0
  51. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/render.py +0 -0
  52. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/rules.py +0 -0
  53. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/sandboxed_bash.py +0 -0
  54. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/scribe.py +0 -0
  55. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/skills/__init__.py +0 -0
  56. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/skills/graphify/SKILL.md +0 -0
  57. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/skills/graphify/tools.py +0 -0
  58. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/trust.py +0 -0
  59. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/turn.py +0 -0
  60. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/verify_gate.py +0 -0
  61. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/web_fetch.py +0 -0
  62. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/workers.py +0 -0
  63. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code/worktree.py +0 -0
  64. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/SOURCES.txt +0 -0
  65. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/dependency_links.txt +0 -0
  66. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/entry_points.txt +0 -0
  67. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/requires.txt +0 -0
  68. {loom_code-0.2.0 → loom_code-0.2.1}/loom_code.egg-info/top_level.txt +0 -0
  69. {loom_code-0.2.0 → loom_code-0.2.1}/setup.cfg +0 -0
  70. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_agent.py +0 -0
  71. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_antipoison_gate.py +0 -0
  72. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_approval.py +0 -0
  73. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_approval_danger.py +0 -0
  74. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_approval_integration.py +0 -0
  75. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_background.py +0 -0
  76. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_checkpoint.py +0 -0
  77. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_code_index.py +0 -0
  78. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_compact.py +0 -0
  79. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_context_observability.py +0 -0
  80. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_credentials.py +0 -0
  81. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_custom_commands.py +0 -0
  82. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_diagnostics.py +0 -0
  83. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_edit_tool.py +0 -0
  84. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_extensions.py +0 -0
  85. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_file_boundary.py +0 -0
  86. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_file_history.py +0 -0
  87. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_git_hook.py +0 -0
  88. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_goal_state.py +0 -0
  89. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_graphify_file_discovery.py +0 -0
  90. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_graphify_query_tiers.py +0 -0
  91. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_graphify_wiring.py +0 -0
  92. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_grep_tool.py +0 -0
  93. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_learned_notes.py +0 -0
  94. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_loop_guard.py +0 -0
  95. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_lsp_tools.py +0 -0
  96. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_mcp.py +0 -0
  97. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_paste.py +0 -0
  98. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_permissions.py +0 -0
  99. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_pricing.py +0 -0
  100. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_project.py +0 -0
  101. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_prompts.py +0 -0
  102. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_render.py +0 -0
  103. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_repl_guards.py +0 -0
  104. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_resume_migration.py +0 -0
  105. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_resume_preview.py +0 -0
  106. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_routing.py +0 -0
  107. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_rules.py +0 -0
  108. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_sandboxed_bash.py +0 -0
  109. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_session_tree.py +0 -0
  110. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_stream_liveness.py +0 -0
  111. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_turn_economy.py +0 -0
  112. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_verify_gate.py +0 -0
  113. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_web_fetch.py +0 -0
  114. {loom_code-0.2.0 → loom_code-0.2.1}/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loom-code
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: loom-code — a loomflow-native terminal coding agent
5
5
  Author: Anupam Nautiyal
6
6
  License: MIT License
@@ -19,4 +19,4 @@ belongs in the framework, not here. loom-code is the dogfood test
19
19
  that keeps loomflow honest.
20
20
  """
21
21
 
22
- __version__ = "0.2.0"
22
+ __version__ = "0.2.1"
@@ -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
- # Background processes the agent started (dev servers,
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
  Metadata-Version: 2.4
2
2
  Name: loom-code
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: loom-code — a loomflow-native terminal coding agent
5
5
  Author: Anupam Nautiyal
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "loom-code"
3
- version = "0.2.0"
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.0"
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 was scheduled give it a beat to actually run
395
- for _ in range(80):
396
- if marker.exists():
397
- break
398
- await anyio.sleep(0.05)
408
+ # Drain the scheduled task deterministicallyand 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