assertion-cli 0.1.1__tar.gz → 0.4.0__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 (35) hide show
  1. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/PKG-INFO +19 -6
  2. assertion_cli-0.4.0/README.md +47 -0
  3. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/assertion_cli.egg-info/PKG-INFO +19 -6
  4. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/assertion_cli.egg-info/SOURCES.txt +1 -2
  5. assertion_cli-0.4.0/bundle.py +42 -0
  6. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/git.py +14 -78
  7. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/main.py +210 -133
  8. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/models.py +12 -1
  9. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/pyproject.toml +1 -1
  10. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/session.py +35 -51
  11. assertion_cli-0.4.0/templates/ACTIVATION.md +14 -0
  12. assertion_cli-0.4.0/templates/SKILL.md +184 -0
  13. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_bundle.py +5 -1
  14. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_git.py +44 -10
  15. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_init.py +63 -7
  16. assertion_cli-0.4.0/tests/test_main.py +82 -0
  17. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_session.py +64 -8
  18. assertion_cli-0.1.1/README.md +0 -34
  19. assertion_cli-0.1.1/bundle.py +0 -26
  20. assertion_cli-0.1.1/templates/ACTIVATION.md +0 -14
  21. assertion_cli-0.1.1/templates/SKILL.md +0 -177
  22. assertion_cli-0.1.1/tests/test_main.py +0 -23
  23. assertion_cli-0.1.1/tests/test_stack_resolve.py +0 -120
  24. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/api.py +0 -0
  25. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/assertion_cli.egg-info/dependency_links.txt +0 -0
  26. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/assertion_cli.egg-info/entry_points.txt +0 -0
  27. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/assertion_cli.egg-info/requires.txt +0 -0
  28. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/assertion_cli.egg-info/top_level.txt +0 -0
  29. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/link.py +0 -0
  30. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/setup.cfg +0 -0
  31. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/templates/__init__.py +0 -0
  32. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_api.py +0 -0
  33. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_decision.py +0 -0
  34. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_link.py +0 -0
  35. {assertion_cli-0.1.1 → assertion_cli-0.4.0}/tests/test_prompt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assertion-cli
3
- Version: 0.1.1
3
+ Version: 0.4.0
4
4
  Summary: CLI for the Assertion API
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
@@ -26,14 +26,15 @@ Run locally from the workspace:
26
26
  uv run --package assertion-cli asrt --help
27
27
  ```
28
28
 
29
- Install from Git as a `uv` tool:
29
+ Install as a `uv` tool from PyPI:
30
30
 
31
31
  ```bash
32
- uv tool install git+ssh://git@github.com/prooflayer-ai/backend.git#subdirectory=cli
32
+ uv tool install assertion-cli # latest
33
+ uv tool install --reinstall assertion-cli # upgrade in place
33
34
  ```
34
35
 
35
36
  The CLI package declares all of its direct runtime dependencies. At the moment
36
- that set is `httpx`, `pydantic`, and `typer`.
37
+ that set is `httpx`, `pydantic`, `python-dotenv`, and `typer`.
37
38
 
38
39
  After installation:
39
40
 
@@ -42,7 +43,19 @@ asrt stacks
42
43
  asrt checkpoint --stack <stack-id> "Implemented X\nUpdated Y"
43
44
  asrt checkpoint --continue "Implemented Y"
44
45
  asrt decision --yes <checkpoint-id> # optional, only after a failed checkpoint
45
- asrt verify
46
+ asrt verify # submit final verification (non-blocking)
47
+ asrt verify-status # one-shot poll; loop with your own sleep until terminal
46
48
  ```
47
49
 
48
- This expects the installer to already have GitHub SSH access to `prooflayer-ai/backend`.
50
+ ## Publishing a new version
51
+
52
+ ```bash
53
+ # 1. Bump the version in cli/pyproject.toml.
54
+ # 2. Build the sdist + wheel.
55
+ uv build --package assertion-cli
56
+ # 3. Upload (requires UV_PUBLISH_TOKEN, or `--token` on the command).
57
+ uv publish dist/*
58
+ # 4. Tag the release so consumers can correlate to git:
59
+ git tag cli-v$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2)
60
+ git push origin --tags
61
+ ```
@@ -0,0 +1,47 @@
1
+ # Assertion CLI
2
+
3
+ CLI for the Assertion API.
4
+
5
+ ## Usage
6
+
7
+ The CLI currently targets a local backend at `http://localhost:8000`.
8
+
9
+ Run locally from the workspace:
10
+
11
+ ```bash
12
+ uv run --package assertion-cli asrt --help
13
+ ```
14
+
15
+ Install as a `uv` tool from PyPI:
16
+
17
+ ```bash
18
+ uv tool install assertion-cli # latest
19
+ uv tool install --reinstall assertion-cli # upgrade in place
20
+ ```
21
+
22
+ The CLI package declares all of its direct runtime dependencies. At the moment
23
+ that set is `httpx`, `pydantic`, `python-dotenv`, and `typer`.
24
+
25
+ After installation:
26
+
27
+ ```bash
28
+ asrt stacks
29
+ asrt checkpoint --stack <stack-id> "Implemented X\nUpdated Y"
30
+ asrt checkpoint --continue "Implemented Y"
31
+ asrt decision --yes <checkpoint-id> # optional, only after a failed checkpoint
32
+ asrt verify # submit final verification (non-blocking)
33
+ asrt verify-status # one-shot poll; loop with your own sleep until terminal
34
+ ```
35
+
36
+ ## Publishing a new version
37
+
38
+ ```bash
39
+ # 1. Bump the version in cli/pyproject.toml.
40
+ # 2. Build the sdist + wheel.
41
+ uv build --package assertion-cli
42
+ # 3. Upload (requires UV_PUBLISH_TOKEN, or `--token` on the command).
43
+ uv publish dist/*
44
+ # 4. Tag the release so consumers can correlate to git:
45
+ git tag cli-v$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2)
46
+ git push origin --tags
47
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assertion-cli
3
- Version: 0.1.1
3
+ Version: 0.4.0
4
4
  Summary: CLI for the Assertion API
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
@@ -26,14 +26,15 @@ Run locally from the workspace:
26
26
  uv run --package assertion-cli asrt --help
27
27
  ```
28
28
 
29
- Install from Git as a `uv` tool:
29
+ Install as a `uv` tool from PyPI:
30
30
 
31
31
  ```bash
32
- uv tool install git+ssh://git@github.com/prooflayer-ai/backend.git#subdirectory=cli
32
+ uv tool install assertion-cli # latest
33
+ uv tool install --reinstall assertion-cli # upgrade in place
33
34
  ```
34
35
 
35
36
  The CLI package declares all of its direct runtime dependencies. At the moment
36
- that set is `httpx`, `pydantic`, and `typer`.
37
+ that set is `httpx`, `pydantic`, `python-dotenv`, and `typer`.
37
38
 
38
39
  After installation:
39
40
 
@@ -42,7 +43,19 @@ asrt stacks
42
43
  asrt checkpoint --stack <stack-id> "Implemented X\nUpdated Y"
43
44
  asrt checkpoint --continue "Implemented Y"
44
45
  asrt decision --yes <checkpoint-id> # optional, only after a failed checkpoint
45
- asrt verify
46
+ asrt verify # submit final verification (non-blocking)
47
+ asrt verify-status # one-shot poll; loop with your own sleep until terminal
46
48
  ```
47
49
 
48
- This expects the installer to already have GitHub SSH access to `prooflayer-ai/backend`.
50
+ ## Publishing a new version
51
+
52
+ ```bash
53
+ # 1. Bump the version in cli/pyproject.toml.
54
+ # 2. Build the sdist + wheel.
55
+ uv build --package assertion-cli
56
+ # 3. Upload (requires UV_PUBLISH_TOKEN, or `--token` on the command).
57
+ uv publish dist/*
58
+ # 4. Tag the release so consumers can correlate to git:
59
+ git tag cli-v$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2)
60
+ git push origin --tags
61
+ ```
@@ -24,5 +24,4 @@ tests/test_init.py
24
24
  tests/test_link.py
25
25
  tests/test_main.py
26
26
  tests/test_prompt.py
27
- tests/test_session.py
28
- tests/test_stack_resolve.py
27
+ tests/test_session.py
@@ -0,0 +1,42 @@
1
+ import io
2
+ import json
3
+ import zipfile
4
+
5
+ from models import MetadataPayload
6
+
7
+ ASSERTION_DIR_NAME = ".assertion"
8
+ DIFF_ARCHIVE_PATH = "git.diff"
9
+ METADATA_ARCHIVE_PATH = f"{ASSERTION_DIR_NAME}/metadata.json"
10
+ PROMPTS_ARCHIVE_PATH = f"{ASSERTION_DIR_NAME}/prompts"
11
+ CHECKPOINT_ARCHIVE_PATH = f"{ASSERTION_DIR_NAME}/checkpoint"
12
+
13
+
14
+ def _metadata_wire_json(metadata: MetadataPayload) -> str:
15
+ """Serialize metadata for the bundle, remapping `base_sha` → `head_sha`.
16
+
17
+ The CLI stores the diff base as `base_sha` because that matches what the
18
+ field means from the CLI's perspective (the foundation the diff builds
19
+ on). The backend reads it as `head_sha` because after its `git checkout`
20
+ that SHA literally is the clone's HEAD. Same value, different name per
21
+ side; the remap lives here so neither side has to know about the other's
22
+ perspective.
23
+ """
24
+ payload = metadata.model_dump()
25
+ payload["head_sha"] = payload.pop("base_sha", None)
26
+ return json.dumps(payload, indent=2) + "\n"
27
+
28
+
29
+ def build_bundle(
30
+ *,
31
+ metadata: MetadataPayload,
32
+ diff_text: str,
33
+ prompts_text: str,
34
+ checkpoint_text: str,
35
+ ) -> bytes:
36
+ buf = io.BytesIO()
37
+ with zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
38
+ zf.writestr(METADATA_ARCHIVE_PATH, _metadata_wire_json(metadata))
39
+ zf.writestr(PROMPTS_ARCHIVE_PATH, prompts_text)
40
+ zf.writestr(CHECKPOINT_ARCHIVE_PATH, checkpoint_text)
41
+ zf.writestr(DIFF_ARCHIVE_PATH, diff_text)
42
+ return buf.getvalue()
@@ -37,41 +37,6 @@ def find_git_root(start_path: Path) -> Path:
37
37
  return Path(completed.stdout.strip())
38
38
 
39
39
 
40
- def require_head_pushed(repo_root: Path) -> None:
41
- try:
42
- remote_refs = run_git_command(
43
- repo_root, ["for-each-ref", "--format=%(refname:short)", "refs/remotes"]
44
- )
45
- except RuntimeError as exc:
46
- exit_with_error(f"Failed to inspect remote refs: {exc}")
47
-
48
- refs = [
49
- ref for ref in remote_refs.splitlines() if ref and not ref.endswith("/HEAD")
50
- ]
51
- if not refs:
52
- exit_with_error(
53
- "Current HEAD commit is not present on any remote-tracking branch."
54
- )
55
-
56
- for ref in refs:
57
- completed = subprocess.run(
58
- ["git", "merge-base", "--is-ancestor", "HEAD", ref],
59
- cwd=repo_root,
60
- capture_output=True,
61
- text=True,
62
- check=False,
63
- )
64
- if completed.returncode == 0:
65
- return
66
- if completed.returncode != 1:
67
- message = (
68
- completed.stderr.strip() or completed.stdout.strip() or "git failed"
69
- )
70
- exit_with_error(f"Failed to verify remote commit state: {message}")
71
-
72
- exit_with_error("Current HEAD commit is not present on any remote-tracking branch.")
73
-
74
-
75
40
  def get_head_sha(repo_root: Path) -> str:
76
41
  try:
77
42
  return run_git_command(repo_root, ["rev-parse", "HEAD"])
@@ -88,40 +53,6 @@ def get_head_branch(repo_root: Path) -> str | None:
88
53
  return name if name and name != "HEAD" else None
89
54
 
90
55
 
91
- def get_origin_github_repo(repo_root: Path) -> str | None:
92
- """Return the current repo's GitHub `owner/name` from origin, or None if not parseable.
93
-
94
- Accepts the common remote URL forms:
95
- git@github.com:owner/name(.git)?
96
- https://github.com/owner/name(.git)?
97
- ssh://git@github.com/owner/name(.git)?
98
- """
99
- try:
100
- url = run_git_command(repo_root, ["remote", "get-url", "origin"]).strip()
101
- except RuntimeError:
102
- return None
103
- if not url:
104
- return None
105
-
106
- if url.startswith("git@github.com:"):
107
- path = url[len("git@github.com:") :]
108
- elif url.startswith("ssh://git@github.com/"):
109
- path = url[len("ssh://git@github.com/") :]
110
- elif url.startswith("https://github.com/"):
111
- path = url[len("https://github.com/") :]
112
- elif url.startswith("http://github.com/"):
113
- path = url[len("http://github.com/") :]
114
- else:
115
- return None
116
-
117
- path = path.rstrip("/")
118
- if path.endswith(".git"):
119
- path = path[: -len(".git")]
120
- if path.count("/") != 1 or not all(path.split("/")):
121
- return None
122
- return path
123
-
124
-
125
56
  def _build_untracked_diff(repo_root: Path, rel_path: str) -> str:
126
57
  completed = subprocess.run(
127
58
  [
@@ -148,9 +79,9 @@ def _build_untracked_diff(repo_root: Path, rel_path: str) -> str:
148
79
  # Paths the assertion-cli owns end-to-end. Excluded from the diff bundle so
149
80
  # reviewers don't flag our own bootstrap files as "unrelated changes" — they're
150
81
  # generated/refreshed by `asrt init` and `asrt checkpoint` and have nothing to
151
- # do with the customer's feature work. CLAUDE.md / AGENTS.md are intentionally
152
- # NOT in this list: those are customer-owned files we only patch a marked
153
- # block into, so the customer's other edits to them should still flow through.
82
+ # do with the user's feature work. CLAUDE.md / AGENTS.md are intentionally
83
+ # NOT in this list: those are user-owned files we only patch a marked
84
+ # block into, so the user's other edits to them should still flow through.
154
85
  _ASSERTION_EXCLUDED_PATHSPECS = [
155
86
  ":(exclude).assertion",
156
87
  ":(exclude).claude/skills/assertion-cli",
@@ -158,13 +89,18 @@ _ASSERTION_EXCLUDED_PATHSPECS = [
158
89
  ]
159
90
 
160
91
 
161
- def get_uncommitted_diff(repo_root: Path) -> str:
92
+ def get_uncommitted_diff(repo_root: Path, base_sha: str) -> str:
93
+ """Build a unified diff from `base_sha` to the current working tree.
94
+
95
+ `git diff <base_sha>` compares the working tree directly to the base
96
+ commit, which subsumes both committed-since-base and unstaged tweaks in
97
+ one shot — so a single call covers all tracked changes regardless of
98
+ whether the agent committed locally. Untracked files are still added
99
+ separately as new-file diffs.
100
+ """
162
101
  try:
163
102
  tracked = run_git_command(
164
- repo_root, ["diff", "--", *_ASSERTION_EXCLUDED_PATHSPECS]
165
- )
166
- staged = run_git_command(
167
- repo_root, ["diff", "--cached", "--", *_ASSERTION_EXCLUDED_PATHSPECS]
103
+ repo_root, ["diff", base_sha, "--", *_ASSERTION_EXCLUDED_PATHSPECS]
168
104
  )
169
105
 
170
106
  untracked_output = run_git_command(
@@ -183,7 +119,7 @@ def get_uncommitted_diff(repo_root: Path) -> str:
183
119
  if rel_path
184
120
  ]
185
121
 
186
- parts = [p for p in [tracked, staged, "\n".join(untracked_diffs)] if p]
122
+ parts = [p for p in [tracked, "\n".join(untracked_diffs)] if p]
187
123
  return "\n".join(parts)
188
124
  except RuntimeError as exc:
189
125
  exit_with_error(f"Failed to collect git diff: {exc}")