tycho-cli 0.0.3__tar.gz → 0.0.5__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 (68) hide show
  1. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/PKG-INFO +1 -1
  2. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_update.py +9 -0
  3. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/__init__.py +1 -1
  4. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/cli.py +1 -1
  5. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/doctor.py +10 -2
  6. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/version.py +22 -6
  7. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/.github/workflows/ci.yml +0 -0
  8. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/.github/workflows/release.yml +0 -0
  9. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/.gitignore +0 -0
  10. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/.tycho.toml +0 -0
  11. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/CONTRIBUTING.md +0 -0
  12. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/LICENSE +0 -0
  13. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/README.md +0 -0
  14. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/docs/bundler-spike.md +0 -0
  15. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/docs/harness-support.md +0 -0
  16. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/docs/hooks.md +0 -0
  17. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/docs/transcript-structures.md +0 -0
  18. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/docs/tycho-web.md +0 -0
  19. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/packaging/homebrew/tycho.rb +0 -0
  20. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/packaging/npm/bin/tycho.js +0 -0
  21. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/packaging/npm/package.json +0 -0
  22. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/pyproject.toml +0 -0
  23. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/conftest.py +0 -0
  24. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/codex_transcript_sample.jsonl +0 -0
  25. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/cursor_stop_payload.json +0 -0
  26. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/cursor_transcript_sample.jsonl +0 -0
  27. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/eval/claude_masked_red_suite.jsonl +0 -0
  28. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/eval/codex_masked_red_suite.jsonl +0 -0
  29. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/eval/cursor_masked_red_suite.jsonl +0 -0
  30. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/eval/opencode_masked_red_suite.json +0 -0
  31. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/opencode_transcript_sample.json +0 -0
  32. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/transcript_multiturn.jsonl +0 -0
  33. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/fixtures/transcript_sample.jsonl +0 -0
  34. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_count.py +0 -0
  35. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_doctor.py +0 -0
  36. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_e2e_install.py +0 -0
  37. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_eval.py +0 -0
  38. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_init_safety.py +0 -0
  39. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_m1.py +0 -0
  40. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_m2.py +0 -0
  41. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_m3.py +0 -0
  42. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_m4.py +0 -0
  43. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_m5.py +0 -0
  44. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_m6.py +0 -0
  45. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_relay.py +0 -0
  46. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_scope.py +0 -0
  47. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_smoke.py +0 -0
  48. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_status.py +0 -0
  49. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tests/test_turn_scope.py +0 -0
  50. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/__main__.py +0 -0
  51. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/astdiff.py +0 -0
  52. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/checks.py +0 -0
  53. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/config.py +0 -0
  54. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/events.py +0 -0
  55. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/fsstate.py +0 -0
  56. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/gitstate.py +0 -0
  57. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/harness.py +0 -0
  58. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/hook.py +0 -0
  59. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/init.py +0 -0
  60. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/model.py +0 -0
  61. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/opencode.py +0 -0
  62. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/report.py +0 -0
  63. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/runlog.py +0 -0
  64. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/state.py +0 -0
  65. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/status.py +0 -0
  66. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho/verify.py +0 -0
  67. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/tycho-target.ps1.example +0 -0
  68. {tycho_cli-0.0.3 → tycho_cli-0.0.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tycho-cli
3
- Version: 0.0.3
3
+ Version: 0.0.5
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"))
@@ -6,4 +6,4 @@ trust path. Free, open source (Apache 2.0), offline, no account needed.
6
6
  """
7
7
 
8
8
  # Alpha. Bumped to 0.1.0 for the first public release (TYCHO-10).
9
- __version__ = "0.0.3"
9
+ __version__ = "0.0.5"
@@ -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
- lines = [f"tycho doctor (v{__version__})"]
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. Never raises."""
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