tycho-cli 0.0.3__tar.gz → 0.0.4__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.
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/PKG-INFO +1 -1
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_update.py +9 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/__init__.py +1 -1
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/cli.py +1 -1
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/doctor.py +10 -2
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/version.py +22 -6
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/.github/workflows/ci.yml +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/.github/workflows/release.yml +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/.gitignore +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/.tycho.toml +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/CONTRIBUTING.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/LICENSE +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/README.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/docs/bundler-spike.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/docs/harness-support.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/docs/hooks.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/docs/transcript-structures.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/docs/tycho-web.md +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/packaging/homebrew/tycho.rb +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/packaging/npm/bin/tycho.js +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/packaging/npm/package.json +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/pyproject.toml +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/conftest.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/codex_transcript_sample.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/cursor_stop_payload.json +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/cursor_transcript_sample.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/eval/claude_masked_red_suite.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/eval/codex_masked_red_suite.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/eval/cursor_masked_red_suite.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/eval/opencode_masked_red_suite.json +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/opencode_transcript_sample.json +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/transcript_multiturn.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/fixtures/transcript_sample.jsonl +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_count.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_doctor.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_e2e_install.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_eval.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_init_safety.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_m1.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_m2.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_m3.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_m4.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_m5.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_m6.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_relay.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_scope.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_smoke.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_status.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tests/test_turn_scope.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/__main__.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/astdiff.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/checks.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/config.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/events.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/fsstate.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/gitstate.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/harness.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/hook.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/init.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/model.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/opencode.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/report.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/runlog.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/state.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/status.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho/verify.py +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/tycho-target.ps1.example +0 -0
- {tycho_cli-0.0.3 → tycho_cli-0.0.4}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tycho-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Tycho — a free, offline, open-source local verifier that proves an AI agent actually did what it claims, from git, the filesystem, process exit codes, and the harness event stream. No account, no LLM in the trust path.
|
|
5
5
|
Project-URL: Homepage, https://swail.dev/tycho
|
|
6
6
|
Project-URL: Repository, https://github.com/swail-labs/tycho
|
|
@@ -94,6 +94,15 @@ def test_stale_cache_triggers_a_refetch(_online, monkeypatch):
|
|
|
94
94
|
assert version_mod.refresh() == "2.0.0"
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def test_force_bypasses_a_fresh_cache(_online, monkeypatch):
|
|
98
|
+
# The bug: a same-day release is invisible because the ≤24h cache short-circuits refresh().
|
|
99
|
+
# `tycho update`/`doctor` pass force=True to re-hit the index now (TYCHO-53).
|
|
100
|
+
state.write_update_cache(latest="0.0.2", checked_at=time.time()) # fresh, but stale content
|
|
101
|
+
_fetches(monkeypatch, "0.0.3")
|
|
102
|
+
assert version_mod.refresh() == "0.0.2" # cached path still honors the day
|
|
103
|
+
assert version_mod.refresh(force=True) == "0.0.3" # forced path sees the new release
|
|
104
|
+
|
|
105
|
+
|
|
97
106
|
def test_status_bar_path_reads_cache_only_never_network(_online, monkeypatch):
|
|
98
107
|
# refresh_first=False must not fetch — the status bar renders constantly.
|
|
99
108
|
monkeypatch.setattr(version_mod, "_fetch", lambda: pytest.fail("must not hit the network"))
|
|
@@ -389,7 +389,7 @@ def _update(skip: bool) -> int:
|
|
|
389
389
|
from . import state
|
|
390
390
|
from . import version as version_mod
|
|
391
391
|
|
|
392
|
-
newest = version_mod.refresh()
|
|
392
|
+
newest = version_mod.refresh(force=True) # explicit command — never trust the daily cache
|
|
393
393
|
behind = bool(newest) and version_mod.is_newer(newest, __version__)
|
|
394
394
|
if skip:
|
|
395
395
|
if behind:
|
|
@@ -96,7 +96,7 @@ def diagnose(repo: Path) -> list[Finding]:
|
|
|
96
96
|
findings: list[Finding] = []
|
|
97
97
|
# A new version is worth surfacing where someone is already looking; doctor is manual,
|
|
98
98
|
# so the (once-a-day, fail-open) network check is fine here (TYCHO-53).
|
|
99
|
-
note = version_mod.notice(refresh_first=True)
|
|
99
|
+
note = version_mod.notice(refresh_first=True, force=True) # explicit command — bypass the daily cache
|
|
100
100
|
if note:
|
|
101
101
|
findings.append(Finding(INFO, note, "run `tycho update`"))
|
|
102
102
|
wired = _wired_harnesses(repo, findings)
|
|
@@ -293,7 +293,15 @@ def healthy(findings: list[Finding]) -> bool:
|
|
|
293
293
|
|
|
294
294
|
|
|
295
295
|
def render(findings: list[Finding]) -> str:
|
|
296
|
-
|
|
296
|
+
# `diagnose` already ran the forced check, so the cache now holds the true latest (TYCHO-53).
|
|
297
|
+
latest = version_mod.cached_latest()
|
|
298
|
+
if latest and version_mod.is_newer(latest, __version__):
|
|
299
|
+
head = f"tycho doctor (v{__version__} · latest {latest} — run `tycho update`)"
|
|
300
|
+
elif latest:
|
|
301
|
+
head = f"tycho doctor (v{__version__} · latest {latest})"
|
|
302
|
+
else:
|
|
303
|
+
head = f"tycho doctor (v{__version__})" # index unreachable / opted out
|
|
304
|
+
lines = [head]
|
|
297
305
|
for f in findings:
|
|
298
306
|
mark = {OK: "✓", INFO: "•", DRIFT: "⚠"}.get(f.level, "✗")
|
|
299
307
|
label = "" if f.level in (OK, INFO, DRIFT) else f"{f.level}: "
|
|
@@ -54,13 +54,18 @@ def _fetch() -> str | None:
|
|
|
54
54
|
return None # offline, slow, blocked, unparseable — say nothing
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def refresh() -> str | None:
|
|
57
|
+
def refresh(force: bool = False) -> str | None:
|
|
58
58
|
"""Newest published version, hitting the network only if the cache is stale (≤ once/day)
|
|
59
|
-
and the user hasn't opted out. Cached machine-wide; never raises.
|
|
59
|
+
and the user hasn't opted out. Cached machine-wide; never raises.
|
|
60
|
+
|
|
61
|
+
`force=True` ignores the daily cache and re-hits the index now — for the explicit,
|
|
62
|
+
interactive paths (`tycho update`, `tycho doctor`), where a same-day release must be
|
|
63
|
+
seen immediately. The passive paths (status bar, per-session bootup) leave it False so
|
|
64
|
+
they never add a network round-trip (TYCHO-53)."""
|
|
60
65
|
if _env_opted_out():
|
|
61
66
|
return None
|
|
62
67
|
cache = state.read_update_cache()
|
|
63
|
-
if cache.get("latest") and (time.time() - cache.get("checked_at", 0)) < _CHECK_TTL:
|
|
68
|
+
if not force and cache.get("latest") and (time.time() - cache.get("checked_at", 0)) < _CHECK_TTL:
|
|
64
69
|
return cache["latest"]
|
|
65
70
|
got = _fetch()
|
|
66
71
|
if got:
|
|
@@ -69,6 +74,16 @@ def refresh() -> str | None:
|
|
|
69
74
|
return cache.get("latest")
|
|
70
75
|
|
|
71
76
|
|
|
77
|
+
def cached_latest() -> str | None:
|
|
78
|
+
"""The newest version already known from the cache — no network. For display after a
|
|
79
|
+
caller has (or hasn't) refreshed; the status header reads this."""
|
|
80
|
+
try:
|
|
81
|
+
got = state.read_update_cache().get("latest")
|
|
82
|
+
return got if isinstance(got, str) and got else None
|
|
83
|
+
except Exception:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
72
87
|
def _tuple(v: str) -> tuple[int, ...]:
|
|
73
88
|
"""Numeric release tuple: "0.1.0" -> (0, 1, 0). Non-numeric suffixes are dropped — fine
|
|
74
89
|
for the 0.0.x-alpha → 0.1.0 scheme (no pre-release tags in play)."""
|
|
@@ -79,14 +94,15 @@ def is_newer(candidate: str, than: str) -> bool:
|
|
|
79
94
|
return _tuple(candidate) > _tuple(than)
|
|
80
95
|
|
|
81
96
|
|
|
82
|
-
def notice(refresh_first: bool = False) -> str | None:
|
|
97
|
+
def notice(refresh_first: bool = False, force: bool = False) -> str | None:
|
|
83
98
|
"""One line if a newer version exists and the user hasn't opted out or dismissed it — else
|
|
84
99
|
None. `refresh_first=True` permits the network check (doctor/init/bootup); the default reads
|
|
85
|
-
only the cache, which is what the status bar must use.
|
|
100
|
+
only the cache, which is what the status bar must use. `force=True` additionally bypasses the
|
|
101
|
+
daily cache (explicit `tycho update`/`doctor`). Never raises."""
|
|
86
102
|
if _env_opted_out():
|
|
87
103
|
return None
|
|
88
104
|
try:
|
|
89
|
-
newest = refresh() if refresh_first else state.read_update_cache().get("latest")
|
|
105
|
+
newest = refresh(force=force) if refresh_first else state.read_update_cache().get("latest")
|
|
90
106
|
if not isinstance(newest, str) or not is_newer(newest, __version__):
|
|
91
107
|
return None
|
|
92
108
|
if state.read_update_cache().get("dismissed_version") == newest:
|
|
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
|