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.
- {gitvow-0.12.2/src/gitvow.egg-info → gitvow-0.12.3}/PKG-INFO +1 -1
- {gitvow-0.12.2 → gitvow-0.12.3}/pyproject.toml +1 -1
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/transcript.py +48 -10
- {gitvow-0.12.2 → gitvow-0.12.3/src/gitvow.egg-info}/PKG-INFO +1 -1
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_transcript_formats.py +59 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/LICENSE +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/README.md +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/setup.cfg +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/__init__.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/__main__.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/adapters.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/cli.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/collect.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/decisions.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/default_policy.json +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/digest.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/hooks/__init__.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/install.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/policy.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/pricing.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/providers.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/recall.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/redact.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/report.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/rules.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/selftest.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/snapshots.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow/state.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/SOURCES.txt +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/dependency_links.txt +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/entry_points.txt +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/requires.txt +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/src/gitvow.egg-info/top_level.txt +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_adapters.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_collect.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_cost.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_decisions.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_digest.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_external_adapter.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_hooks.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_install_cli.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_packaging.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_policy.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_providers.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_recall.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_redact.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_report.py +0 -0
- {gitvow-0.12.2 → gitvow-0.12.3}/tests/test_rules.py +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
171
|
-
|
|
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
|
|
175
|
+
return content
|
|
174
176
|
if isinstance(content, list):
|
|
175
|
-
return
|
|
176
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|