gitvow 0.12.2__tar.gz → 0.12.3__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 (49) hide show
  1. {gitvow-0.12.2/src/gitvow.egg-info → gitvow-0.12.3}/PKG-INFO +1 -1
  2. {gitvow-0.12.2 → gitvow-0.12.3}/pyproject.toml +1 -1
  3. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/transcript.py +48 -10
  4. {gitvow-0.12.2 → gitvow-0.12.3/src/gitvow.egg-info}/PKG-INFO +1 -1
  5. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_transcript_formats.py +59 -0
  6. {gitvow-0.12.2 → gitvow-0.12.3}/LICENSE +0 -0
  7. {gitvow-0.12.2 → gitvow-0.12.3}/README.md +0 -0
  8. {gitvow-0.12.2 → gitvow-0.12.3}/setup.cfg +0 -0
  9. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/__init__.py +0 -0
  10. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/__main__.py +0 -0
  11. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/adapters.py +0 -0
  12. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/cli.py +0 -0
  13. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/collect.py +0 -0
  14. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/decisions.py +0 -0
  15. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/default_policy.json +0 -0
  16. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/digest.py +0 -0
  17. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/hooks/__init__.py +0 -0
  18. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/install.py +0 -0
  19. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/policy.py +0 -0
  20. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/pricing.py +0 -0
  21. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/providers.py +0 -0
  22. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/recall.py +0 -0
  23. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/redact.py +0 -0
  24. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/report.py +0 -0
  25. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/rules.py +0 -0
  26. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/selftest.py +0 -0
  27. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/snapshots.py +0 -0
  28. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/state.py +0 -0
  29. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/SOURCES.txt +0 -0
  30. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/dependency_links.txt +0 -0
  31. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/entry_points.txt +0 -0
  32. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/requires.txt +0 -0
  33. {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/top_level.txt +0 -0
  34. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_adapters.py +0 -0
  35. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_collect.py +0 -0
  36. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_cost.py +0 -0
  37. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_decisions.py +0 -0
  38. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_digest.py +0 -0
  39. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_external_adapter.py +0 -0
  40. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_hooks.py +0 -0
  41. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_install_cli.py +0 -0
  42. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_packaging.py +0 -0
  43. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_policy.py +0 -0
  44. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_providers.py +0 -0
  45. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_recall.py +0 -0
  46. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_redact.py +0 -0
  47. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_report.py +0 -0
  48. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_rules.py +0 -0
  49. {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_snapshots.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitvow
3
- Version: 0.12.2
3
+ Version: 0.12.3
4
4
  Summary: Provenance and policy gate for AI-agent coding sessions: session trailers on commits, redacted session notes in git, a tool-call gate, and a local ledger. No runtime dependencies.
5
5
  Author-email: Nikhil Bora <nikhil@wirevow.com>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitvow"
7
- version = "0.12.2"
7
+ version = "0.12.3"
8
8
  description = "Provenance and policy gate for AI-agent coding sessions: session trailers on commits, redacted session notes in git, a tool-call gate, and a local ledger. No runtime dependencies."
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import os
7
+ import re
7
8
  from collections.abc import Iterable
8
9
  from typing import Any
9
10
 
@@ -40,8 +41,6 @@ def _text_of(content: Any) -> str:
40
41
 
41
42
 
42
43
  def _patch_files(text: str) -> list[str]:
43
- import re
44
-
45
44
  return re.findall(r"^\*\*\* (?:Update|Add|Delete) File: (.+)$", text or "", re.M)
46
45
 
47
46
 
@@ -167,13 +166,50 @@ def _finish_usage(usage: dict[str, Any]) -> None:
167
166
  )
168
167
 
169
168
 
170
- def _is_person_text(content: Any) -> bool:
171
- """A user message typed by a person, not a tool_result the harness sent back."""
169
+ INJECTED_RE = re.compile(r"^\s*<[a-z_]+>") # Codex prepends <recommended_plugins>, <environment_context> and the like
170
+ PERSON_TEXT = ("text", "input_text")
171
+
172
+
173
+ def _person_text(content: Any) -> str:
172
174
  if isinstance(content, str):
173
- return bool(content.strip())
175
+ return content
174
176
  if isinstance(content, list):
175
- return any(isinstance(c, dict) and c.get("type") == "text" and str(c.get("text", "")).strip() for c in content)
176
- return False
177
+ return "\n".join(
178
+ str(c.get("text", ""))
179
+ for c in content
180
+ if isinstance(c, dict) and c.get("type") in PERSON_TEXT and str(c.get("text", "")).strip()
181
+ )
182
+ return ""
183
+
184
+
185
+ def _is_person_text(content: Any) -> bool:
186
+ """A user message typed by a person: not a tool result, and not a block the harness injected."""
187
+ text = _person_text(content).strip()
188
+ return bool(text) and not INJECTED_RE.match(text)
189
+
190
+
191
+ TOOLS_CALL_RE = re.compile(r"tools\.([A-Za-z_][A-Za-z0-9_]*)\s*\(")
192
+
193
+
194
+ def _codex_unwrap(text: str) -> list[tuple[str, Any]]:
195
+ """Codex 0.15 wraps every tool call in a JavaScript snippet: `await tools.exec_command({...})`.
196
+
197
+ Returns (function name, decoded argument) pairs; the argument is whatever JSON follows the bracket.
198
+ """
199
+ out: list[tuple[str, Any]] = []
200
+ dec = json.JSONDecoder()
201
+ for m in TOOLS_CALL_RE.finditer(text or ""):
202
+ i = m.end()
203
+ while i < len(text) and text[i] in " \t\n\r":
204
+ i += 1
205
+ arg: Any = ""
206
+ if i < len(text) and text[i] in '{"[':
207
+ try:
208
+ arg, _ = dec.raw_decode(text, i)
209
+ except ValueError:
210
+ arg = ""
211
+ out.append((m.group(1), arg))
212
+ return out
177
213
 
178
214
 
179
215
  def _add_tool(out: dict[str, Any], tool: str, brief: str, rules: list[tuple[str, str]], max_tools: int) -> None:
@@ -256,9 +292,11 @@ def _codex_event(
256
292
  elif pt in ("function_call", "custom_tool_call", "local_shell_call"):
257
293
  name = str(p.get("name") or ("local_shell" if pt == "local_shell_call" else ""))
258
294
  arguments = p.get("arguments") if pt == "function_call" else (p.get("input") or p.get("action") or "")
259
- tool, brief, files = _codex_tool(name, arguments)
260
- _add_tool(out, tool, brief, rules, max_tools)
261
- written.update(files)
295
+ inner = _codex_unwrap(arguments) if isinstance(arguments, str) and "tools." in arguments else []
296
+ for fn, arg in inner or [(name, arguments)]:
297
+ tool, brief, files = _codex_tool(fn, arg)
298
+ _add_tool(out, tool, brief, rules, max_tools)
299
+ written.update(files)
262
300
  elif t == "turn_context":
263
301
  if p.get("model"):
264
302
  out["_codex_model"] = str(p["model"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitvow
3
- Version: 0.12.2
3
+ Version: 0.12.3
4
4
  Summary: Provenance and policy gate for AI-agent coding sessions: session trailers on commits, redacted session notes in git, a tool-call gate, and a local ledger. No runtime dependencies.
5
5
  Author-email: Nikhil Bora <nikhil@wirevow.com>
6
6
  License: Apache-2.0
@@ -126,3 +126,62 @@ def test_claude_format_still_default(transcript):
126
126
  def test_redaction_applies_to_all_formats(tmp_path):
127
127
  s = summarize(_write(tmp_path, "rollout.jsonl", CODEX[:4]))
128
128
  assert "[github-token]" in s["last_assistant_text"] and "ghp_" not in s["last_assistant_text"]
129
+
130
+
131
+ # Codex 0.15 shape, captured from a real session: one `exec` custom tool whose input is a JavaScript
132
+ # snippet calling tools.exec_command / tools.apply_patch, and user text as input_text parts.
133
+ CODEX_WRAPPED = [
134
+ {"timestamp": "t", "type": "session_meta", "payload": {"id": "c2", "cwd": "/r"}},
135
+ {
136
+ "timestamp": "t",
137
+ "type": "response_item",
138
+ "payload": {
139
+ "type": "message",
140
+ "role": "user",
141
+ "content": [{"type": "input_text", "text": "<recommended_plugins>\nAirtable\n</recommended_plugins>"}],
142
+ },
143
+ },
144
+ {
145
+ "timestamp": "t",
146
+ "type": "response_item",
147
+ "payload": {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "fix add"}]},
148
+ },
149
+ {
150
+ "timestamp": "t",
151
+ "type": "response_item",
152
+ "payload": {
153
+ "type": "custom_tool_call",
154
+ "name": "exec",
155
+ "input": 'const r = await tools.exec_command({"cmd":"git status","workdir":"/r"});\nconsole.log(r);',
156
+ },
157
+ },
158
+ {
159
+ "timestamp": "t",
160
+ "type": "response_item",
161
+ "payload": {
162
+ "type": "custom_tool_call",
163
+ "name": "exec",
164
+ "input": 'await tools.apply_patch("*** Begin Patch\\n*** Update File: /r/calc.py\\n+a + b\\n*** End Patch\\n");',
165
+ },
166
+ },
167
+ {
168
+ "timestamp": "t",
169
+ "type": "response_item",
170
+ "payload": {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Fixed."}]},
171
+ },
172
+ {
173
+ "timestamp": "t",
174
+ "type": "response_item",
175
+ "payload": {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "accept it"}]},
176
+ },
177
+ ]
178
+
179
+
180
+ def test_codex_wrapped_tool_calls_and_user_turns(tmp_path):
181
+ s = summarize(_write(tmp_path, "rollout.jsonl", CODEX_WRAPPED))
182
+ assert s["format"] == "codex"
183
+ assert [t["tool"] for t in s["tool_calls"]] == ["Bash", "Edit"]
184
+ assert s["tool_calls"][0]["arg"] == "git status" and s["tool_calls"][1]["arg"] == "/r/calc.py"
185
+ assert s["files_written"] == ["/r/calc.py"]
186
+ # the injected <recommended_plugins> block is not a person speaking; the two real prompts are
187
+ assert s["user_turns"] == 2
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