methodproof 0.7.27__tar.gz → 0.7.29__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.
- {methodproof-0.7.27 → methodproof-0.7.29}/CHANGELOG.md +17 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/PKG-INFO +1 -1
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/agents/terminal.py +18 -16
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/agents/watcher.py +23 -6
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/cli.py +61 -25
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/config.py +3 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hook.py +8 -5
- {methodproof-0.7.27 → methodproof-0.7.29}/pyproject.toml +1 -1
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_start.py +3 -2
- {methodproof-0.7.27 → methodproof-0.7.29}/.github/workflows/ci.yml +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/.gitignore +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/LICENSE +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/README.md +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/__init__.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/__main__.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/_daemon.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/agents/__init__.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/agents/base.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/agents/music.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/analysis.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/binding.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/bip39.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/bridge.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/crypto.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/e2e.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/graph.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/__init__.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/claude_code.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/claude_code.sh +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/cline_hook.sh +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/codex_hook.sh +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/gemini_hook.sh +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/install.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/kiro_hook.sh +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/mcp_register.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/openclaw/HOOK.md +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/openclaw/handler.ts +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/openclaw_install.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/opencode_plugin.js +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/hooks/wrappers.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/integrity.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/kdf.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/keychain.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/live.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/lock.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/mcp.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/migrate_db.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/proxy.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/proxy_daemon.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/repos.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/skills/methodproof/SKILL.md +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/store.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/sync.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/__init__.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/consent.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/init.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/log.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/login_success.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/review.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/start.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/status.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/tui/theme.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/viewer.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/methodproof/wordlist.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/test_windows_compat.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/__init__.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/conftest.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_analysis.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_auth.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_config.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_helpers.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_session.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_share.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_cli_update.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_e2e_integration.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_graph.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_hooks.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_live.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_openclaw_hooks.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_profiles.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_security.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_store.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_sync.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_viewer.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/tests/test_wrappers.py +0 -0
- {methodproof-0.7.27 → methodproof-0.7.29}/uv.lock +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.29] — 2026-04-12
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- **Journal mode output is now tier-aware** — Pro/Team/Admin/Superadmin users see "Unlimited journal entries (Pro plan)" everywhere journal credits are displayed (`mp journal on`, `mp journal status`, `mp start --journal`, session mode line, consent flow). Basic users get a live credit count fetched from `GET /auth/me` (falls back to local cache if offline). Free users see their local credit count as before. Credit deduction on `--journal` is skipped for unlimited tiers.
|
|
7
|
+
|
|
8
|
+
## [0.7.28] — 2026-04-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`cwd` in terminal events** — shell hook (bash/zsh/PowerShell) now writes working directory to `commands.jsonl`; terminal agent reads it into `terminal_cmd` metadata. Requires re-running `mp init` to update the installed hook.
|
|
12
|
+
- **Git commit enrichment** — `git_commit` events now include `author`, `author_email`, `committed_at` (ISO 8601), `parent_hash`, and `body` (full commit message body when it differs from subject). Single `git log` call instead of multiple.
|
|
13
|
+
- **`language` on `file_delete`** — was always empty string; now extracted from file extension same as `file_create`.
|
|
14
|
+
- **`skipped` count in `test_run`** — pytest, jest, and go test output now parsed for skipped test count.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **`agent_complete.last_message_preview` not journal-gated** — preview of agent's final message leaked content when journal mode was off. Now stripped with other content fields.
|
|
18
|
+
- **`git_commit.body` not journal-gated** — full commit body (multi-line messages) added to journal gate.
|
|
19
|
+
|
|
3
20
|
## [0.7.27] — 2026-04-12
|
|
4
21
|
|
|
5
22
|
### Fixed
|
|
@@ -22,8 +22,8 @@ TEST_FRAMEWORKS = {
|
|
|
22
22
|
"cargo_test": re.compile(r"\bcargo test\b"),
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
_PYTEST_RE = re.compile(r"(\d+) passed(?:.*?(\d+) failed)?")
|
|
26
|
-
_JEST_RE = re.compile(r"Tests:\s+(?:(\d+) failed,\s+)?(\d+) passed")
|
|
25
|
+
_PYTEST_RE = re.compile(r"(\d+) passed(?:.*?(\d+) failed)?(?:.*?(\d+) skipped)?")
|
|
26
|
+
_JEST_RE = re.compile(r"Tests:\s+(?:(\d+) failed,\s+)?(\d+) passed(?:,\s+(\d+) skipped)?")
|
|
27
27
|
_CARGO_RE = re.compile(r"(\d+) passed.*?(\d+) failed")
|
|
28
28
|
|
|
29
29
|
|
|
@@ -34,26 +34,25 @@ def _detect_test(command: str) -> str | None:
|
|
|
34
34
|
return None
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def _parse_test_results(output: str, framework: str, exit_code: int) -> tuple[int, int]:
|
|
38
|
-
"""Extract pass/fail counts from test output. Falls back to exit code heuristic."""
|
|
37
|
+
def _parse_test_results(output: str, framework: str, exit_code: int) -> tuple[int, int, int]:
|
|
38
|
+
"""Extract pass/fail/skipped counts from test output. Falls back to exit code heuristic."""
|
|
39
39
|
if framework == "pytest":
|
|
40
40
|
m = _PYTEST_RE.search(output)
|
|
41
41
|
if m:
|
|
42
|
-
return int(m.group(1)), int(m.group(2) or 0)
|
|
42
|
+
return int(m.group(1)), int(m.group(2) or 0), int(m.group(3) or 0)
|
|
43
43
|
elif framework == "jest":
|
|
44
44
|
m = _JEST_RE.search(output)
|
|
45
45
|
if m:
|
|
46
|
-
return int(m.group(2) or 0), int(m.group(1) or 0)
|
|
46
|
+
return int(m.group(2) or 0), int(m.group(1) or 0), int(m.group(3) or 0)
|
|
47
47
|
elif framework == "go_test":
|
|
48
|
-
return output.count("--- PASS"), output.count("--- FAIL")
|
|
48
|
+
return output.count("--- PASS"), output.count("--- FAIL"), output.count("--- SKIP")
|
|
49
49
|
elif framework == "cargo_test":
|
|
50
50
|
m = _CARGO_RE.search(output)
|
|
51
51
|
if m:
|
|
52
|
-
return int(m.group(1)), int(m.group(2))
|
|
53
|
-
# Fallback: exit code 0 = all passed, non-zero = at least 1 failure
|
|
52
|
+
return int(m.group(1)), int(m.group(2)), 0
|
|
54
53
|
if exit_code == 0:
|
|
55
|
-
return 1, 0
|
|
56
|
-
return 0, 1
|
|
54
|
+
return 1, 0, 0
|
|
55
|
+
return 0, 1, 0
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
def start(stop: threading.Event) -> None:
|
|
@@ -93,19 +92,22 @@ def _process(line: str) -> None:
|
|
|
93
92
|
exit_code = entry.get("exit_code", 0)
|
|
94
93
|
duration = entry.get("duration_ms", 0)
|
|
95
94
|
output = entry.get("output", "")[:500]
|
|
96
|
-
# Redact output if it contains secrets
|
|
97
95
|
if SENSITIVE.search(output):
|
|
98
96
|
output = "[redacted — contains sensitive content]"
|
|
97
|
+
cwd = entry.get("cwd", "")
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
meta: dict = {
|
|
101
100
|
"command": command, "exit_code": exit_code,
|
|
102
101
|
"output_snippet": output, "duration_ms": duration,
|
|
103
|
-
}
|
|
102
|
+
}
|
|
103
|
+
if cwd:
|
|
104
|
+
meta["cwd"] = cwd
|
|
105
|
+
base.emit("terminal_cmd", meta)
|
|
104
106
|
|
|
105
107
|
framework = _detect_test(command)
|
|
106
108
|
if framework:
|
|
107
|
-
passed, failed = _parse_test_results(output, framework, exit_code)
|
|
109
|
+
passed, failed, skipped = _parse_test_results(output, framework, exit_code)
|
|
108
110
|
base.emit("test_run", {
|
|
109
111
|
"framework": framework, "passed": passed, "failed": failed,
|
|
110
|
-
"duration_ms": duration,
|
|
112
|
+
"skipped": skipped, "duration_ms": duration,
|
|
111
113
|
})
|
|
@@ -139,7 +139,8 @@ class _Handler(FileSystemEventHandler):
|
|
|
139
139
|
return
|
|
140
140
|
path = self._relpath(event.src_path)
|
|
141
141
|
self._hashes.pop(path, None)
|
|
142
|
-
|
|
142
|
+
lang = Path(event.src_path).suffix.lstrip(".")
|
|
143
|
+
base.emit("file_delete", {"path": path, "language": lang})
|
|
143
144
|
|
|
144
145
|
|
|
145
146
|
def _poll_git(watch_dir: str, stop: threading.Event) -> None:
|
|
@@ -164,17 +165,33 @@ def _poll_git(watch_dir: str, stop: threading.Event) -> None:
|
|
|
164
165
|
|
|
165
166
|
def _log_commit(watch_dir: str, sha: str) -> None:
|
|
166
167
|
try:
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
# Single git call: subject\x00author\x00email\x00iso_date\x00parent_hash\x00full_body
|
|
169
|
+
fmt = subprocess.run(
|
|
170
|
+
["git", "-C", watch_dir, "log", "-1",
|
|
171
|
+
"--format=%s%x00%an%x00%ae%x00%ai%x00%P%x00%B", sha],
|
|
169
172
|
capture_output=True, text=True, timeout=5,
|
|
170
|
-
).stdout
|
|
173
|
+
).stdout
|
|
174
|
+
parts = fmt.split("\x00", 5)
|
|
175
|
+
subject = parts[0].strip() if len(parts) > 0 else ""
|
|
176
|
+
author = parts[1].strip() if len(parts) > 1 else ""
|
|
177
|
+
author_email = parts[2].strip() if len(parts) > 2 else ""
|
|
178
|
+
committed_at = parts[3].strip() if len(parts) > 3 else ""
|
|
179
|
+
parent_hash = parts[4].strip()[:7] if len(parts) > 4 else ""
|
|
180
|
+
body = parts[5].strip() if len(parts) > 5 else ""
|
|
171
181
|
files = subprocess.run(
|
|
172
182
|
["git", "-C", watch_dir, "diff-tree", "--no-commit-id", "-r", "--name-only", sha],
|
|
173
183
|
capture_output=True, text=True, timeout=5,
|
|
174
184
|
).stdout.strip().splitlines()
|
|
175
185
|
except Exception:
|
|
176
|
-
|
|
177
|
-
meta: dict[str, object] = {
|
|
186
|
+
subject, author, author_email, committed_at, parent_hash, body, files = "", "", "", "", "", "", []
|
|
187
|
+
meta: dict[str, object] = {
|
|
188
|
+
"hash": sha[:7], "message": subject, "files_changed": files,
|
|
189
|
+
"author": author, "author_email": author_email, "committed_at": committed_at,
|
|
190
|
+
}
|
|
191
|
+
if parent_hash:
|
|
192
|
+
meta["parent_hash"] = parent_hash
|
|
193
|
+
if body and body != subject:
|
|
194
|
+
meta["body"] = body[:2000]
|
|
178
195
|
diff = _git_show_diff(watch_dir, sha)
|
|
179
196
|
if diff:
|
|
180
197
|
meta["diff"] = diff
|
|
@@ -156,13 +156,50 @@ def _install_alias() -> None:
|
|
|
156
156
|
f.write(alias)
|
|
157
157
|
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
_PRO_TIERS = {"pro", "team", "admin", "superadmin"}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _journal_entitlement(cfg: dict) -> str | int:
|
|
163
|
+
"""Return 'unlimited' for Pro+ users, or an int credit count for others.
|
|
164
|
+
|
|
165
|
+
For Basic users, fetches live credit count from the platform; falls back
|
|
166
|
+
to the locally cached value if the request fails (e.g. offline).
|
|
167
|
+
"""
|
|
168
|
+
token = cfg.get("token", "")
|
|
169
|
+
claims = _decode_jwt_claims(token) if token else {}
|
|
170
|
+
account_type = claims.get("account_type", "free").lower()
|
|
171
|
+
|
|
172
|
+
if account_type in _PRO_TIERS:
|
|
173
|
+
return "unlimited"
|
|
174
|
+
|
|
175
|
+
if account_type == "basic":
|
|
176
|
+
try:
|
|
177
|
+
profile = _request("GET", "/auth/me", cfg.get("api_url", config.LOCAL_API_URL), token)
|
|
178
|
+
server_credits = profile.get("journal_credits")
|
|
179
|
+
if server_credits is not None:
|
|
180
|
+
cfg["journal_credits"] = int(server_credits)
|
|
181
|
+
config.save(cfg)
|
|
182
|
+
except Exception:
|
|
183
|
+
pass # offline — use local cache
|
|
184
|
+
|
|
185
|
+
return int(cfg.get("journal_credits", 0))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _journal_credits_line(entitlement: str | int) -> str:
|
|
189
|
+
if entitlement == "unlimited":
|
|
190
|
+
return "Unlimited journal entries (Pro plan)"
|
|
191
|
+
n = int(entitlement)
|
|
192
|
+
label = f"{n} free journal credit{'s' if n != 1 else ''}"
|
|
193
|
+
return f"{label} (up to {config.FREE_JOURNAL_MAX_HOURS}h per session)"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _print_journal_intro(cfg: dict) -> None:
|
|
197
|
+
"""Show journal mode introduction with tier-aware credit status."""
|
|
198
|
+
entitlement = _journal_entitlement(cfg)
|
|
161
199
|
print(" ┌─────────────────────────────────────────────────────┐")
|
|
162
200
|
print(" │ Journal Mode — full content capture │")
|
|
163
201
|
print(" └─────────────────────────────────────────────────────┘")
|
|
164
|
-
print(f"
|
|
165
|
-
f"(sessions up to {config.FREE_JOURNAL_MAX_HOURS}h each).")
|
|
202
|
+
print(f" {_journal_credits_line(entitlement)}")
|
|
166
203
|
print()
|
|
167
204
|
print(" By default, MethodProof captures structural metadata only —")
|
|
168
205
|
print(" file paths, line counts, timing, tool names. Journal mode")
|
|
@@ -199,9 +236,8 @@ def _run_consent(cfg: dict) -> dict:
|
|
|
199
236
|
cfg["research_consent"] = False
|
|
200
237
|
cfg["publish_redact"] = dict(config._DEFAULTS["publish_redact"])
|
|
201
238
|
cfg["consent_acknowledged"] = True
|
|
202
|
-
credits = cfg.get("journal_credits", config._DEFAULTS["journal_credits"])
|
|
203
239
|
print("\n Minimal capture enabled (file changes + git commits).\n")
|
|
204
|
-
_print_journal_intro(
|
|
240
|
+
_print_journal_intro(cfg)
|
|
205
241
|
print(" Customize anytime: `methodproof consent`\n")
|
|
206
242
|
return cfg
|
|
207
243
|
|
|
@@ -210,9 +246,8 @@ def _run_consent(cfg: dict) -> dict:
|
|
|
210
246
|
cfg["research_consent"] = False
|
|
211
247
|
cfg["publish_redact"] = dict(config._DEFAULTS["publish_redact"])
|
|
212
248
|
cfg["consent_acknowledged"] = True
|
|
213
|
-
credits = cfg.get("journal_credits", config._DEFAULTS["journal_credits"])
|
|
214
249
|
print(f"\n {_rainbow('Full Spectrum')} enabled — free live streaming unlocked.\n")
|
|
215
|
-
_print_journal_intro(
|
|
250
|
+
_print_journal_intro(cfg)
|
|
216
251
|
print(" Customize anytime: `methodproof consent`\n")
|
|
217
252
|
return cfg
|
|
218
253
|
|
|
@@ -873,7 +908,8 @@ def cmd_journal(args: argparse.Namespace) -> None:
|
|
|
873
908
|
"""Journal mode — full content capture."""
|
|
874
909
|
subcmd = getattr(args, "journal_cmd", None)
|
|
875
910
|
cfg = config.load()
|
|
876
|
-
|
|
911
|
+
entitlement = _journal_entitlement(cfg)
|
|
912
|
+
is_unlimited = entitlement == "unlimited"
|
|
877
913
|
|
|
878
914
|
if subcmd == "on":
|
|
879
915
|
print("Journal Mode — Full Content Capture\n")
|
|
@@ -884,12 +920,9 @@ def cmd_journal(args: argparse.Namespace) -> None:
|
|
|
884
920
|
print(" • Terminal output (not just commands)")
|
|
885
921
|
print(" • Tool call parameters and results\n")
|
|
886
922
|
print("All content is encrypted (AES-256-GCM) and subject to your consent settings.\n")
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
print("After credits are used, journal mode requires a Pro plan.\n")
|
|
891
|
-
else:
|
|
892
|
-
print("Journal mode requires a Pro plan (or free credits if available).\n")
|
|
923
|
+
print(f" {_journal_credits_line(entitlement)}\n")
|
|
924
|
+
if not is_unlimited and int(entitlement) == 0:
|
|
925
|
+
print("You have no credits remaining. Upgrade to Pro for unlimited journal entries.\n")
|
|
893
926
|
answer = input("Enable journal mode? [y/N] ").strip().lower()
|
|
894
927
|
if answer != "y":
|
|
895
928
|
print("Journal mode not enabled.")
|
|
@@ -913,9 +946,7 @@ def cmd_journal(args: argparse.Namespace) -> None:
|
|
|
913
946
|
print("Journal mode: OFF (structural only)")
|
|
914
947
|
print(" Only metadata captured: lengths, types, timing, file paths.")
|
|
915
948
|
print(" Enable with: methodproof journal on")
|
|
916
|
-
|
|
917
|
-
print(f" Free journal credits: {credits} "
|
|
918
|
-
f"(up to {config.FREE_JOURNAL_MAX_HOURS}h per session)")
|
|
949
|
+
print(f" {_journal_credits_line(entitlement)}")
|
|
919
950
|
|
|
920
951
|
else:
|
|
921
952
|
print("Usage: methodproof journal [on|off|status]")
|
|
@@ -1196,12 +1227,17 @@ def cmd_start(args: argparse.Namespace) -> None:
|
|
|
1196
1227
|
# Journal mode
|
|
1197
1228
|
if getattr(args, "journal", False):
|
|
1198
1229
|
cfg["journal_mode"] = True
|
|
1199
|
-
|
|
1200
|
-
if
|
|
1201
|
-
|
|
1202
|
-
print(f"Journal mode ON (free credit used — {credits - 1} remaining, {config.FREE_JOURNAL_MAX_HOURS}h cap).")
|
|
1230
|
+
entitlement = _journal_entitlement(cfg)
|
|
1231
|
+
if entitlement == "unlimited":
|
|
1232
|
+
print("Journal mode ON (full content capture).")
|
|
1203
1233
|
else:
|
|
1204
|
-
|
|
1234
|
+
credits = int(entitlement)
|
|
1235
|
+
if credits > 0:
|
|
1236
|
+
cfg["journal_credits"] = credits - 1
|
|
1237
|
+
remaining = credits - 1
|
|
1238
|
+
print(f"Journal mode ON (free credit used — {remaining} remaining, {config.FREE_JOURNAL_MAX_HOURS}h cap).")
|
|
1239
|
+
else:
|
|
1240
|
+
print("Journal mode ON for this session (full content capture).")
|
|
1205
1241
|
config.save(cfg)
|
|
1206
1242
|
|
|
1207
1243
|
# E2E mode
|
|
@@ -1558,8 +1594,8 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
1558
1594
|
# Modes
|
|
1559
1595
|
modes = []
|
|
1560
1596
|
if cfg.get("journal_mode"):
|
|
1561
|
-
|
|
1562
|
-
modes.append(f"journal ({
|
|
1597
|
+
ent = _journal_entitlement(cfg)
|
|
1598
|
+
modes.append("journal (unlimited)" if ent == "unlimited" else f"journal ({ent} credits)")
|
|
1563
1599
|
if cfg.get("e2e_mode"):
|
|
1564
1600
|
fp = cfg.get("e2e_fingerprint", "")
|
|
1565
1601
|
modes.append(f"e2e ({fp[:8]})" if fp else "e2e")
|
|
@@ -118,6 +118,9 @@ JOURNAL_CONTENT_FIELDS: list[tuple[str, str]] = [
|
|
|
118
118
|
("user_prompt", "prompt_text"),
|
|
119
119
|
("tool_call", "tool_input_preview"),
|
|
120
120
|
("tool_result", "result_preview"),
|
|
121
|
+
# Agent final message and commit body reveal content
|
|
122
|
+
("agent_complete", "last_message_preview"),
|
|
123
|
+
("git_commit", "body"),
|
|
121
124
|
]
|
|
122
125
|
|
|
123
126
|
|
|
@@ -10,10 +10,11 @@ _BASH = '''
|
|
|
10
10
|
# methodproof-hook
|
|
11
11
|
_mp_pre() { _MP_CMD="$BASH_COMMAND"; _MP_T=$SECONDS; }
|
|
12
12
|
_mp_post() {
|
|
13
|
-
local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 ))
|
|
13
|
+
local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 )) cwd
|
|
14
14
|
[ -z "$cmd" ] && return
|
|
15
15
|
cmd="${cmd//\\\\/\\\\\\\\}"; cmd="${cmd//\\"/\\\\\\"}"
|
|
16
|
-
|
|
16
|
+
cwd="$(pwd)"; cwd="${cwd//\\\\/\\\\\\\\}"; cwd="${cwd//\\"/\\\\\\"}"
|
|
17
|
+
echo "{\\"command\\":\\"$cmd\\",\\"exit_code\\":$ec,\\"duration_ms\\":$dur,\\"cwd\\":\\"$cwd\\"}" >> ~/.methodproof/commands.jsonl
|
|
17
18
|
unset _MP_CMD
|
|
18
19
|
}
|
|
19
20
|
trap '_mp_pre' DEBUG
|
|
@@ -24,10 +25,11 @@ _ZSH = '''
|
|
|
24
25
|
# methodproof-hook
|
|
25
26
|
_mp_pre() { _MP_CMD="$1"; _MP_T=$SECONDS; }
|
|
26
27
|
_mp_post() {
|
|
27
|
-
local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 ))
|
|
28
|
+
local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 )) cwd
|
|
28
29
|
[[ -z "$cmd" ]] && return
|
|
29
30
|
cmd="${cmd//\\\\/\\\\\\\\}"; cmd="${cmd//\\"/\\\\\\"}"
|
|
30
|
-
|
|
31
|
+
cwd="$(pwd)"; cwd="${cwd//\\\\/\\\\\\\\}"; cwd="${cwd//\\"/\\\\\\"}"
|
|
32
|
+
echo "{\\"command\\":\\"$cmd\\",\\"exit_code\\":$ec,\\"duration_ms\\":$dur,\\"cwd\\":\\"$cwd\\"}" >> ~/.methodproof/commands.jsonl
|
|
31
33
|
unset _MP_CMD
|
|
32
34
|
}
|
|
33
35
|
autoload -Uz add-zsh-hook
|
|
@@ -52,7 +54,8 @@ function prompt {
|
|
|
52
54
|
if ($global:_mpCmd) {
|
|
53
55
|
$dur = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - $global:_mpT
|
|
54
56
|
$cmd = $global:_mpCmd -replace '\\\\','\\\\\\\\' -replace '"','\\"'
|
|
55
|
-
$
|
|
57
|
+
$cwd = (Get-Location).Path -replace '\\\\','\\\\\\\\' -replace '"','\\"'
|
|
58
|
+
$line = "{`"command`":`"$cmd`",`"exit_code`":$ec,`"duration_ms`":$dur,`"cwd`":`"$cwd`"}"
|
|
56
59
|
$logPath = Join-Path $HOME ".methodproof" "commands.jsonl"
|
|
57
60
|
Add-Content -Path $logPath -Value $line
|
|
58
61
|
$global:_mpCmd = $null
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "methodproof"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.29"
|
|
4
4
|
description = "See how you code. Capture and visualize your engineering process."
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = ["watchdog>=4.0", "websocket-client>=1.7", "cryptography>=43.0", "keyring>=25.0", "textual>=0.59", "rich>=13.7"]
|
|
@@ -118,9 +118,10 @@ def test_start_creates_session_and_spawns_daemon(mock_sleep, mock_popen, mock_re
|
|
|
118
118
|
@patch("subprocess.Popen")
|
|
119
119
|
@patch("time.sleep")
|
|
120
120
|
def test_start_journal_decrements_credits(mock_sleep, mock_popen, mock_req, mock_repo, mock_update, mock_auth,
|
|
121
|
-
mock_hook, mock_alive, logged_in_cfg, cli_args,
|
|
121
|
+
mock_hook, mock_alive, logged_in_cfg, fake_jwt, cli_args,
|
|
122
122
|
monkeypatch, capsys):
|
|
123
|
-
|
|
123
|
+
# Use free-tier JWT — pro/team skips credit deduction (unlimited)
|
|
124
|
+
cfg = logged_in_cfg(account_id="acct-1", token=fake_jwt(user_id="acct-1", account_type="free"))
|
|
124
125
|
cfg["journal_credits"] = 2
|
|
125
126
|
config.save(cfg)
|
|
126
127
|
pidfile = config.DIR / "methodproof.pid"
|
|
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
|