brindle 0.3.5__tar.gz → 0.3.6__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 (129) hide show
  1. {brindle-0.3.5 → brindle-0.3.6}/PKG-INFO +2 -2
  2. {brindle-0.3.5 → brindle-0.3.6}/README.md +1 -1
  3. {brindle-0.3.5 → brindle-0.3.6}/pyproject.toml +6 -2
  4. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/usage.py +3 -3
  5. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/watch.py +20 -0
  6. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/watch_costs.py +2 -1
  7. {brindle-0.3.5 → brindle-0.3.6}/.gitignore +0 -0
  8. {brindle-0.3.5 → brindle-0.3.6}/LICENSE +0 -0
  9. {brindle-0.3.5 → brindle-0.3.6}/SCHEDULE-A +0 -0
  10. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/__init__.py +0 -0
  11. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/__main__.py +0 -0
  12. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/account.py +0 -0
  13. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/agents.py +0 -0
  14. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/airgap.py +0 -0
  15. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/antigravity.py +0 -0
  16. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/autopilot.py +0 -0
  17. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/budget.py +0 -0
  18. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/__init__.py +0 -0
  19. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/backend-auditor.md +0 -0
  20. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer-codex.md +0 -0
  21. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer-fable.md +0 -0
  22. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer-gemini.md +0 -0
  23. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  24. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer-light.md +0 -0
  25. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer-local.md +0 -0
  26. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/developer.md +0 -0
  27. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/qa.md +0 -0
  28. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
  29. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/reviewer-gemini.md +0 -0
  30. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/reviewer-local.md +0 -0
  31. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/reviewer.md +0 -0
  32. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/subagent.md +0 -0
  33. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_agents/supervisor.md +0 -0
  34. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_rules/__init__.py +0 -0
  35. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_rules/security/backend.md +0 -0
  36. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
  37. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/builtin_rules/tests/only.md +0 -0
  38. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/ci_adapters.py +0 -0
  39. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/ci_client.py +0 -0
  40. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/ci_federation.py +0 -0
  41. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/ci_hosts.py +0 -0
  42. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/cli.py +0 -0
  43. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/codemap.py +0 -0
  44. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/codex_hook.py +0 -0
  45. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/company_identity.py +0 -0
  46. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/company_login.py +0 -0
  47. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/config.py +0 -0
  48. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/conflicts.py +0 -0
  49. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/cost.py +0 -0
  50. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/cost_estimate.py +0 -0
  51. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/cull.py +0 -0
  52. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/db.py +0 -0
  53. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/demo.py +0 -0
  54. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/detect.py +0 -0
  55. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/doctor.py +0 -0
  56. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/escapes.py +0 -0
  57. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/events.py +0 -0
  58. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/gates.py +0 -0
  59. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/git.py +0 -0
  60. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/guardrails.py +0 -0
  61. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/history.py +0 -0
  62. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/identity_lock.py +0 -0
  63. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/inbox.py +0 -0
  64. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/keys_cmds.py +0 -0
  65. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/keystore.py +0 -0
  66. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/launch_health.py +0 -0
  67. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/learned_rules.py +0 -0
  68. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/learning.py +0 -0
  69. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/legacy.py +0 -0
  70. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/mcp_server.py +0 -0
  71. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/model_access.py +0 -0
  72. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/model_availability.py +0 -0
  73. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/__init__.py +0 -0
  74. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/client.py +0 -0
  75. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/loop.py +0 -0
  76. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/permissions.py +0 -0
  77. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/runner.py +0 -0
  78. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/serve.py +0 -0
  79. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/native/tools.py +0 -0
  80. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pane_auth.py +0 -0
  81. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/permissions.py +0 -0
  82. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pipeline.py +0 -0
  83. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/plugins.py +0 -0
  84. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/policy.py +0 -0
  85. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pool.py +0 -0
  86. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pricing.py +0 -0
  87. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/__init__.py +0 -0
  88. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/_files.py +0 -0
  89. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/account.py +0 -0
  90. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/audit_chain.py +0 -0
  91. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/audit_export.py +0 -0
  92. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/auth.py +0 -0
  93. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/brief_learning.py +0 -0
  94. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/cost_centers.py +0 -0
  95. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/credentials.py +0 -0
  96. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/features.py +0 -0
  97. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/keys.py +0 -0
  98. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/learning.py +0 -0
  99. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/license.py +0 -0
  100. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/loopback.py +0 -0
  101. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/managed_models.py +0 -0
  102. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/org_profiles.py +0 -0
  103. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/orgkey.py +0 -0
  104. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/rollout.py +0 -0
  105. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/settings_sync.py +0 -0
  106. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/status.py +0 -0
  107. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/team_events.py +0 -0
  108. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/pro/team_policy.py +0 -0
  109. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/procs.py +0 -0
  110. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/profiles.py +0 -0
  111. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/providers.py +0 -0
  112. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/quota.py +0 -0
  113. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/repo_cmds.py +0 -0
  114. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/repos.py +0 -0
  115. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/rewind.py +0 -0
  116. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/rule_checks.py +0 -0
  117. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/savings.py +0 -0
  118. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/scratch.py +0 -0
  119. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/secrets.py +0 -0
  120. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/self_serve.py +0 -0
  121. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/services.py +0 -0
  122. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/sessions.py +0 -0
  123. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/sidebar_follow.py +0 -0
  124. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/signin_pause.py +0 -0
  125. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/status_cache.py +0 -0
  126. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/tasks.py +0 -0
  127. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/tmux.py +0 -0
  128. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/view.py +0 -0
  129. {brindle-0.3.5 → brindle-0.3.6}/src/brindle/workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges
5
5
  Project-URL: Homepage, https://pawdelta.com/brindle/
6
6
  Project-URL: Documentation, https://pawdelta.com/brindle/docs/recommended-use
@@ -89,7 +89,7 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
89
89
  brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
90
90
 
91
91
  `brindle --version` prints the installed version; the tmux status bar of every brindle
92
- session shows it too (`brindle 0.3.5`). A session started before an upgrade keeps
92
+ session shows it too (`brindle 0.3.6`). A session started before an upgrade keeps
93
93
  running the old code, and shows the old number, until you restart it.
94
94
 
95
95
  ## Quick start
@@ -59,7 +59,7 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
59
59
  brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
60
60
 
61
61
  `brindle --version` prints the installed version; the tmux status bar of every brindle
62
- session shows it too (`brindle 0.3.5`). A session started before an upgrade keeps
62
+ session shows it too (`brindle 0.3.6`). A session started before an upgrade keeps
63
63
  running the old code, and shows the old number, until you restart it.
64
64
 
65
65
  ## Quick start
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brindle"
3
- version = "0.3.5"
3
+ version = "0.3.6"
4
4
  description = "Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -51,6 +51,7 @@ audit_export = "brindle.pro.audit_export:make"
51
51
  [dependency-groups]
52
52
  dev = [
53
53
  "pytest>=8",
54
+ "pytest-cov>=7.0",
54
55
  "pytest-xdist>=3.8.0",
55
56
  "python-hcl2>=8.1.4",
56
57
  "pyyaml>=6.0.3",
@@ -74,4 +75,7 @@ only-include = ["src", "README.md", "LICENSE", "SCHEDULE-A", "pyproject.toml"]
74
75
  [tool.pytest.ini_options]
75
76
  testpaths = ["tests"]
76
77
  pythonpath = ["tests"]
77
- markers = ["serial: run outside xdist (`pytest -m serial`); the parallel pass skips these"]
78
+ markers = [
79
+ "serial: run outside xdist (`pytest -m serial`); the parallel pass skips these",
80
+ "variant(reason): an input variant that covers no new code on purpose (see scripts/check_new_tests.py)",
81
+ ]
@@ -376,12 +376,12 @@ def antigravity_agent_usage(db: DB, agent) -> Usage | None:
376
376
  offset = 0
377
377
  inp, out, read, write = 0, 0, 0, 0
378
378
  model = "gemini-2.5-pro"
379
- if cached and getattr(cached, "inode", None) == st.st_ino and cached["size"] <= st.st_size:
379
+ if cached and cached["inode"] == st.st_ino and cached["size"] <= st.st_size:
380
380
  offset = cached["size"]
381
381
  inp = cached["input_tokens"]
382
382
  out = cached["output_tokens"]
383
383
  read = cached["cache_read_tokens"]
384
- write = cached["cache_write_tokens"]
384
+ write = cached["cache_creation_tokens"]
385
385
  if cached["model"]:
386
386
  model = cached["model"]
387
387
 
@@ -402,7 +402,7 @@ def antigravity_agent_usage(db: DB, agent) -> Usage | None:
402
402
  read += ev.get("cache_read_tokens", 0)
403
403
  if ev.get("model"):
404
404
  model = ev["model"]
405
- db.set_usage_cache(str(path), st.st_size, inp, out, read, write, model, inode=st.st_ino)
405
+ db.set_usage_cache(str(path), st.st_size, inp, out, read, write, model, None, st.st_ino)
406
406
 
407
407
  if inp == 0 and out == 0:
408
408
  return None
@@ -1006,4 +1006,24 @@ def run(repo_root: str | None, sidebar: bool = False) -> None:
1006
1006
  # Esc clears the filter; don't make it wait curses' default second to
1007
1007
  # tell a lone Esc from the start of an arrow key's escape sequence.
1008
1008
  os.environ.setdefault("ESCDELAY", "25")
1009
+ _log_to_file()
1009
1010
  curses.wrapper(_loop, repo_root, sidebar)
1011
+
1012
+
1013
+ def _log_to_file() -> None:
1014
+ """Send warnings and errors to ``watch.log``: without a handler, logging
1015
+ falls back to stderr, which prints tracebacks over the curses screen."""
1016
+ import logging
1017
+
1018
+ from brindle.config import brindle_home
1019
+
1020
+ try:
1021
+ brindle_home().mkdir(parents=True, exist_ok=True)
1022
+ handler: logging.Handler = logging.FileHandler(brindle_home() / "watch.log", encoding="utf-8")
1023
+ handler.setFormatter(logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s"))
1024
+ except OSError:
1025
+ handler = logging.NullHandler()
1026
+ root = logging.getLogger()
1027
+ root.addHandler(handler)
1028
+ if root.level == logging.NOTSET or root.level > logging.WARNING:
1029
+ root.setLevel(logging.WARNING)
@@ -343,7 +343,8 @@ def provider_rows(providers: list[tuple[str, int]], width: int) -> list[str]:
343
343
 
344
344
  def title(collapsed: bool, width: int, local_: Local | None) -> str:
345
345
  arrow = "▸ " if collapsed else "▾ "
346
- top = (local_.providers[:1] if (local_ and local_.providers)
346
+ # The provider with the least headroom left: the one that holds work up.
347
+ top = ([min(local_.providers, key=lambda p: (p[1], p[0]))] if (local_ and local_.providers)
347
348
  else (shares(local_.models)[:1] if local_ else []))
348
349
  tail = f" ({short(top[0][0])} {top[0][1]}%)" if top else ""
349
350
  if len(arrow) + len("Costs") + len(tail) > width:
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