assurance-cli 0.6.0__tar.gz → 0.6.2__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 (34) hide show
  1. assurance_cli-0.6.2/PKG-INFO +89 -0
  2. assurance_cli-0.6.2/README.md +56 -0
  3. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/cli.py +62 -18
  4. assurance_cli-0.6.2/assurance_cli.egg-info/PKG-INFO +89 -0
  5. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/pyproject.toml +1 -1
  6. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_cli.py +38 -2
  7. assurance_cli-0.6.0/PKG-INFO +0 -364
  8. assurance_cli-0.6.0/README.md +0 -331
  9. assurance_cli-0.6.0/assurance_cli.egg-info/PKG-INFO +0 -364
  10. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/LICENSE +0 -0
  11. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/__init__.py +0 -0
  12. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/__main__.py +0 -0
  13. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/baseline.py +0 -0
  14. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/drift.py +0 -0
  15. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/gather.py +0 -0
  16. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/paths.py +0 -0
  17. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/pin.py +0 -0
  18. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/profile.py +0 -0
  19. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/py.typed +0 -0
  20. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli/setdiff.py +0 -0
  21. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli.egg-info/SOURCES.txt +0 -0
  22. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli.egg-info/dependency_links.txt +0 -0
  23. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli.egg-info/entry_points.txt +0 -0
  24. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli.egg-info/requires.txt +0 -0
  25. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/assurance_cli.egg-info/top_level.txt +0 -0
  26. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/setup.cfg +0 -0
  27. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_drift.py +0 -0
  28. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_gapped_series.py +0 -0
  29. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_installed_wheel.py +0 -0
  30. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_pin.py +0 -0
  31. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_read_only.py +0 -0
  32. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_readme_blocks.py +0 -0
  33. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_setdiff.py +0 -0
  34. {assurance_cli-0.6.0 → assurance_cli-0.6.2}/tests/test_version.py +0 -0
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: assurance-cli
3
+ Version: 0.6.2
4
+ Summary: CLI for folder assurance checks — coverage, staleness, and baselines.
5
+ Author-email: "I-Ops Operations Intelligence, LLC" <hello@i-ops.dev>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://i-ops.dev
8
+ Project-URL: Source, https://github.com/i-ops-hq/assurance
9
+ Keywords: cli,ai-agents,governance,auditing,assurance,reproducibility
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Quality Assurance
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: assurance-core>=0.14.0
23
+ Requires-Dist: openpyxl>=3.1
24
+ Provides-Extra: mcp
25
+ Requires-Dist: mcp<3,>=1.27.0; extra == "mcp"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: mypy>=1.0; extra == "dev"
29
+ Requires-Dist: types-openpyxl; extra == "dev"
30
+ Provides-Extra: deps
31
+ Requires-Dist: assurance-deps>=0.2.4; extra == "deps"
32
+ Dynamic: license-file
33
+
34
+ # assurance-cli
35
+
36
+ [![PyPI](https://img.shields.io/pypi/v/assurance-cli)](https://pypi.org/project/assurance-cli/)
37
+ [![Tests](https://github.com/i-ops-hq/assurance/actions/workflows/tests.yml/badge.svg)](https://github.com/i-ops-hq/assurance/actions/workflows/tests.yml)
38
+ [![Python](https://img.shields.io/pypi/pyversions/assurance-cli)](https://pypi.org/project/assurance-cli/)
39
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/i-ops-hq/assurance/blob/main/packages/cli/LICENSE)
40
+
41
+ Did the job cover everything it was supposed to cover? Arithmetic, not models.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install assurance-cli
47
+ # or: pip install assurance # every tool
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ `assurance --help` lists: `init`, `check`, `diff`, `pin`, `drift`, `deps`, `budget`, `authority`, `audit`.
53
+ `audit`, `budget`, `authority`, and `deps` forward to their packages when installed.
54
+
55
+ Point `check` at a folder of dated files (real output from a temp folder with Jan, Feb, Apr CSVs):
56
+
57
+ ```
58
+ $ assurance check reports
59
+ 3 of 4 months from 2026-01 to 2026-04 in reports — not in this folder: March 2026 — Range inferred from filenames: earliest 2026-01, latest 2026-04 — uneven spacing, so monthly was read from the names rather than detected. Override with --expect / --from / --to.
60
+ ```
61
+
62
+ With `--fail-on-gap` that same line exits `1`.
63
+
64
+ `diff` compares any two sets of keys (no folder required). `pin` snapshots MCP tool definitions and detects drift. `drift` watches a binary outcome stream.
65
+
66
+ ## What it checks
67
+
68
+ - Dated or numbered series in a folder (`check`) — months, weeks, days, numbered runs
69
+ - Coverage over any two key sets (`diff`)
70
+ - MCP tool-description drift since a pin (`pin`)
71
+ - Shift in a binary outcome stream (`drift`)
72
+ - Forwards: session audit, run budget, authority review, dependency install hooks
73
+
74
+ ## In CI
75
+
76
+ | exit | means |
77
+ |---|---|
78
+ | `0` | checked; no gap (or not asked to fail on one) |
79
+ | `1` | gap / unexpected key / stale pin / nothing it could check |
80
+ | `2` | could not run (bad path, missing sibling package, bad JSON) |
81
+
82
+ ## Limits
83
+
84
+ - **Does not invent the expected set.** `diff` takes your declaration; `check` derives one and prints how.
85
+ - **No network, no telemetry, no keys.**
86
+ - **A folder it cannot parse exits 1**, not 0 — silence is not a pass.
87
+ - Skipped tool directories (`.git`, `node_modules`, …) are named, not folded into “files not opened” as if they were content.
88
+
89
+ See the [root README](https://github.com/i-ops-hq/assurance#readme) and [CHANGELOG.md](CHANGELOG.md).
@@ -0,0 +1,56 @@
1
+ # assurance-cli
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/assurance-cli)](https://pypi.org/project/assurance-cli/)
4
+ [![Tests](https://github.com/i-ops-hq/assurance/actions/workflows/tests.yml/badge.svg)](https://github.com/i-ops-hq/assurance/actions/workflows/tests.yml)
5
+ [![Python](https://img.shields.io/pypi/pyversions/assurance-cli)](https://pypi.org/project/assurance-cli/)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/i-ops-hq/assurance/blob/main/packages/cli/LICENSE)
7
+
8
+ Did the job cover everything it was supposed to cover? Arithmetic, not models.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install assurance-cli
14
+ # or: pip install assurance # every tool
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ `assurance --help` lists: `init`, `check`, `diff`, `pin`, `drift`, `deps`, `budget`, `authority`, `audit`.
20
+ `audit`, `budget`, `authority`, and `deps` forward to their packages when installed.
21
+
22
+ Point `check` at a folder of dated files (real output from a temp folder with Jan, Feb, Apr CSVs):
23
+
24
+ ```
25
+ $ assurance check reports
26
+ 3 of 4 months from 2026-01 to 2026-04 in reports — not in this folder: March 2026 — Range inferred from filenames: earliest 2026-01, latest 2026-04 — uneven spacing, so monthly was read from the names rather than detected. Override with --expect / --from / --to.
27
+ ```
28
+
29
+ With `--fail-on-gap` that same line exits `1`.
30
+
31
+ `diff` compares any two sets of keys (no folder required). `pin` snapshots MCP tool definitions and detects drift. `drift` watches a binary outcome stream.
32
+
33
+ ## What it checks
34
+
35
+ - Dated or numbered series in a folder (`check`) — months, weeks, days, numbered runs
36
+ - Coverage over any two key sets (`diff`)
37
+ - MCP tool-description drift since a pin (`pin`)
38
+ - Shift in a binary outcome stream (`drift`)
39
+ - Forwards: session audit, run budget, authority review, dependency install hooks
40
+
41
+ ## In CI
42
+
43
+ | exit | means |
44
+ |---|---|
45
+ | `0` | checked; no gap (or not asked to fail on one) |
46
+ | `1` | gap / unexpected key / stale pin / nothing it could check |
47
+ | `2` | could not run (bad path, missing sibling package, bad JSON) |
48
+
49
+ ## Limits
50
+
51
+ - **Does not invent the expected set.** `diff` takes your declaration; `check` derives one and prints how.
52
+ - **No network, no telemetry, no keys.**
53
+ - **A folder it cannot parse exits 1**, not 0 — silence is not a pass.
54
+ - Skipped tool directories (`.git`, `node_modules`, …) are named, not folded into “files not opened” as if they were content.
55
+
56
+ See the [root README](https://github.com/i-ops-hq/assurance#readme) and [CHANGELOG.md](CHANGELOG.md).
@@ -25,23 +25,65 @@ FORWARDED: dict[str, tuple[str, str]] = {
25
25
  "budget": ("assurance_budget.cli", "pip install assurance-budget"),
26
26
  "authority": ("assurance_authority.cli", "pip install assurance-authority"),
27
27
  "audit": ("assurance_budget.session_cli", "pip install assurance-budget"),
28
+ "hook": ("assurance_budget.hook_setup", "pip install assurance-budget"),
28
29
  }
29
30
 
30
31
  _START_HERE = """\
31
- assurance — what an agent run covered, spent, may do, and is about to install.
32
+ assurance — your AI agent says it's done. This tells you what it didn't check.
33
+ No model, no network, no account.
32
34
 
33
- assurance diff --expected A --found B what was required vs. what was actually read
34
- assurance check [FOLDER] a folder of dated or numbered files: what is absent
35
+ assurance audit what the Claude Code session in this folder did, and skipped
36
+ assurance audit --demo the same report on a bundled sample session
37
+ assurance hook install run the audit after every Claude Code turn (remove undoes it)
38
+ assurance diff --expected A --found B was everything that should have been read, read?
35
39
  assurance pin --save | --check did an MCP server change a tool after you approved it?
36
- assurance deps requirements.txt what an install will execute, read without running it
37
- assurance budget runs.jsonl where an agent run's budget went
40
+ assurance deps package.json what an install will execute, read without running it
41
+ assurance budget runs.jsonl where an agent run's budget went, and where it looped
38
42
  assurance authority --example may a task proceed for the person who asked?
39
- assurance audit [TRANSCRIPT] what a Claude Code session did, and what it could not classify
43
+ assurance check [FOLDER] a folder of dated or numbered files: what is absent
40
44
 
45
+ Run it after every Claude Code session: https://github.com/i-ops-hq/assurance#run-it-after-every-session
41
46
  assurance <command> --help for more.
42
47
  """
43
48
 
44
49
 
50
+ #: Every distribution the `assurance` command can reach, in the order `--version` names them.
51
+ _DISTRIBUTIONS = (
52
+ ("assurance-cli", "cli"),
53
+ ("assurance-budget", "budget"),
54
+ ("assurance-deps", "deps"),
55
+ ("assurance-authority", "authority"),
56
+ ("assurance-mcp", "mcp"),
57
+ ("assurance-core", "core"),
58
+ )
59
+
60
+
61
+ def _version_line() -> str:
62
+ """`assurance 0.1.2 (cli 0.6.1, budget 0.2.2, …)` — every installed part, so a bug report
63
+ names what actually ran. `pip install assurance` is one version number over several packages;
64
+ printing only the cli's used to say 0.6.0 to someone who had installed 0.1.1."""
65
+ parts = []
66
+ for dist, short in _DISTRIBUTIONS:
67
+ try:
68
+ parts.append(f"{short} {importlib.metadata.version(dist)}")
69
+ except importlib.metadata.PackageNotFoundError:
70
+ continue
71
+ try:
72
+ front = f"assurance {importlib.metadata.version('assurance')}"
73
+ except importlib.metadata.PackageNotFoundError:
74
+ front = "assurance"
75
+ return f"{front} ({', '.join(parts)})" if parts else front
76
+
77
+
78
+ class _PrintVersion(argparse.Action):
79
+ """argparse's own version action rewraps the text to the terminal width, splitting a version
80
+ number across lines. This prints the line as it is."""
81
+
82
+ def __call__(self, parser: argparse.ArgumentParser, namespace: object, values: object, option_string: str | None = None) -> None:
83
+ print(_version_line())
84
+ parser.exit()
85
+
86
+
45
87
  def main(argv: list[str] | None = None) -> int:
46
88
  """Command-line entry for assurance checks."""
47
89
  # `deps` is forwarded whole, before argparse sees any of it. argparse claims `--help` for the
@@ -58,17 +100,23 @@ def main(argv: list[str] | None = None) -> int:
58
100
  parser = argparse.ArgumentParser(
59
101
  prog="assurance",
60
102
  description=(
61
- "Did the job cover everything it was supposed to cover? Arithmetic, not models. "
62
- "`diff` is the general command — it compares any two sets of keys. `check` is the "
63
- "special case for a folder of dated or numbered files."
103
+ "Your AI agent says it's done. Assurance tells you what it didn't check. Every answer "
104
+ "is arithmetic over what happened; no model decides anything."
64
105
  ),
65
106
  )
66
- parser.add_argument(
67
- "--version",
68
- action="version",
69
- version=f"%(prog)s {importlib.metadata.version('assurance-cli')}",
70
- )
107
+ parser.add_argument("--version", action=_PrintVersion, nargs=0, help="Show every installed part's version")
71
108
  sub = parser.add_subparsers(dest="command", required=True)
109
+ # First, because it is the one most people install this for. `main` forwards it before
110
+ # argparse runs; it is listed here so `assurance --help` shows it.
111
+ sub.add_parser(
112
+ "audit", add_help=False,
113
+ help="What the Claude Code session in this folder did, and what it skipped (--demo for a sample)",
114
+ )
115
+
116
+ sub.add_parser(
117
+ "hook", add_help=False,
118
+ help="Run the audit after every Claude Code turn: install, remove, status",
119
+ )
72
120
 
73
121
  init_parser = sub.add_parser("init", help="Write .assurance.json baseline")
74
122
  init_parser.add_argument("folder", help="Folder to baseline")
@@ -192,10 +240,6 @@ def main(argv: list[str] | None = None) -> int:
192
240
  "authority", add_help=False,
193
241
  help="Whether a task may proceed for the person who asked (assurance-authority)",
194
242
  )
195
- sub.add_parser(
196
- "audit", add_help=False,
197
- help="What a Claude Code session did, and what could not be classified (assurance-budget)",
198
- )
199
243
 
200
244
  args = parser.parse_args(argv)
201
245
 
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: assurance-cli
3
+ Version: 0.6.2
4
+ Summary: CLI for folder assurance checks — coverage, staleness, and baselines.
5
+ Author-email: "I-Ops Operations Intelligence, LLC" <hello@i-ops.dev>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://i-ops.dev
8
+ Project-URL: Source, https://github.com/i-ops-hq/assurance
9
+ Keywords: cli,ai-agents,governance,auditing,assurance,reproducibility
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Quality Assurance
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: assurance-core>=0.14.0
23
+ Requires-Dist: openpyxl>=3.1
24
+ Provides-Extra: mcp
25
+ Requires-Dist: mcp<3,>=1.27.0; extra == "mcp"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: mypy>=1.0; extra == "dev"
29
+ Requires-Dist: types-openpyxl; extra == "dev"
30
+ Provides-Extra: deps
31
+ Requires-Dist: assurance-deps>=0.2.4; extra == "deps"
32
+ Dynamic: license-file
33
+
34
+ # assurance-cli
35
+
36
+ [![PyPI](https://img.shields.io/pypi/v/assurance-cli)](https://pypi.org/project/assurance-cli/)
37
+ [![Tests](https://github.com/i-ops-hq/assurance/actions/workflows/tests.yml/badge.svg)](https://github.com/i-ops-hq/assurance/actions/workflows/tests.yml)
38
+ [![Python](https://img.shields.io/pypi/pyversions/assurance-cli)](https://pypi.org/project/assurance-cli/)
39
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/i-ops-hq/assurance/blob/main/packages/cli/LICENSE)
40
+
41
+ Did the job cover everything it was supposed to cover? Arithmetic, not models.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install assurance-cli
47
+ # or: pip install assurance # every tool
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ `assurance --help` lists: `init`, `check`, `diff`, `pin`, `drift`, `deps`, `budget`, `authority`, `audit`.
53
+ `audit`, `budget`, `authority`, and `deps` forward to their packages when installed.
54
+
55
+ Point `check` at a folder of dated files (real output from a temp folder with Jan, Feb, Apr CSVs):
56
+
57
+ ```
58
+ $ assurance check reports
59
+ 3 of 4 months from 2026-01 to 2026-04 in reports — not in this folder: March 2026 — Range inferred from filenames: earliest 2026-01, latest 2026-04 — uneven spacing, so monthly was read from the names rather than detected. Override with --expect / --from / --to.
60
+ ```
61
+
62
+ With `--fail-on-gap` that same line exits `1`.
63
+
64
+ `diff` compares any two sets of keys (no folder required). `pin` snapshots MCP tool definitions and detects drift. `drift` watches a binary outcome stream.
65
+
66
+ ## What it checks
67
+
68
+ - Dated or numbered series in a folder (`check`) — months, weeks, days, numbered runs
69
+ - Coverage over any two key sets (`diff`)
70
+ - MCP tool-description drift since a pin (`pin`)
71
+ - Shift in a binary outcome stream (`drift`)
72
+ - Forwards: session audit, run budget, authority review, dependency install hooks
73
+
74
+ ## In CI
75
+
76
+ | exit | means |
77
+ |---|---|
78
+ | `0` | checked; no gap (or not asked to fail on one) |
79
+ | `1` | gap / unexpected key / stale pin / nothing it could check |
80
+ | `2` | could not run (bad path, missing sibling package, bad JSON) |
81
+
82
+ ## Limits
83
+
84
+ - **Does not invent the expected set.** `diff` takes your declaration; `check` derives one and prints how.
85
+ - **No network, no telemetry, no keys.**
86
+ - **A folder it cannot parse exits 1**, not 0 — silence is not a pass.
87
+ - Skipped tool directories (`.git`, `node_modules`, …) are named, not folded into “files not opened” as if they were content.
88
+
89
+ See the [root README](https://github.com/i-ops-hq/assurance#readme) and [CHANGELOG.md](CHANGELOG.md).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "assurance-cli"
7
- version = "0.6.0"
7
+ version = "0.6.2"
8
8
  description = "CLI for folder assurance checks — coverage, staleness, and baselines."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import importlib.metadata
6
+
5
7
  import csv
6
8
  import json
7
9
  from pathlib import Path
@@ -838,15 +840,49 @@ def test_help_lists_every_forwarded_command(capsys) -> None:
838
840
  with pytest.raises(SystemExit):
839
841
  main(["--help"])
840
842
  out = capsys.readouterr().out
841
- for name in ("deps", "budget", "authority"):
843
+ for name in ("deps", "budget", "authority", "audit", "hook"):
842
844
  assert name in out
843
845
 
844
846
 
847
+ def test_the_hook_subcommand_reaches_the_installer(tmp_path: Path, capsys, monkeypatch) -> None:
848
+ """`assurance hook` is forwarded whole to assurance-budget, like `audit`."""
849
+ pytest.importorskip("assurance_budget.hook_setup")
850
+ monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path / "claude"))
851
+ monkeypatch.setenv("XDG_STATE_HOME", str(tmp_path / "state"))
852
+ monkeypatch.setenv("LOCALAPPDATA", str(tmp_path / "state")) # where backups go on Windows
853
+ monkeypatch.chdir(tmp_path)
854
+ assert main(["hook", "install", "--yes", "--command", "uvx assurance@9.9.9 audit --hook --nudge"]) == 0
855
+ assert "uvx assurance@9.9.9 audit --hook --nudge" in (tmp_path / "claude" / "settings.json").read_text()
856
+ assert main(["hook", "status"]) in (0, 1)
857
+ assert "uvx assurance@9.9.9 audit --hook --nudge" in capsys.readouterr().out
858
+ assert main(["hook", "remove", "--yes"]) == 0
859
+ assert not (tmp_path / "claude" / "settings.json").exists()
860
+
861
+
845
862
  def test_version_flag(capsys) -> None:
846
863
  with pytest.raises(SystemExit) as exc:
847
864
  main(["--version"])
848
865
  assert exc.value.code == 0
849
- assert capsys.readouterr().out.strip().startswith("assurance ")
866
+ out = capsys.readouterr().out
867
+ assert out.startswith("assurance ")
868
+ # Every installed part is named, on one unwrapped line: `pip install assurance` is one version
869
+ # over several packages, and printing only the cli's told a 0.1.1 user "0.6.0".
870
+ assert out.count("\n") == 1
871
+ assert f"cli {importlib.metadata.version('assurance-cli')}" in out
872
+ assert f"core {importlib.metadata.version('assurance-core')}" in out
873
+
874
+
875
+ def test_help_lists_audit_first(capsys) -> None:
876
+ with pytest.raises(SystemExit):
877
+ main(["--help"])
878
+ usage = capsys.readouterr().out.splitlines()[1]
879
+ assert usage.strip().startswith("{audit,")
880
+
881
+
882
+ def test_start_here_leads_with_audit_and_the_demo(capsys) -> None:
883
+ assert main([]) == 0
884
+ listed = [line.split()[1] for line in capsys.readouterr().out.splitlines() if line.startswith(" assurance ")]
885
+ assert listed[:2] == ["audit", "audit"]
850
886
 
851
887
 
852
888
  def test_budget_version_flag(capsys) -> None: