guard-cli 0.1.1__tar.gz → 0.1.3__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 (51) hide show
  1. {guard_cli-0.1.1 → guard_cli-0.1.3}/PKG-INFO +15 -5
  2. {guard_cli-0.1.1 → guard_cli-0.1.3}/README.md +10 -4
  3. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/__init__.py +1 -1
  4. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/api.py +43 -0
  5. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/cli.py +38 -9
  6. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/doctor_cmd.py +8 -0
  7. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/hook.py +328 -137
  8. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/ingest/coverage.py +13 -2
  9. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/init_cmd.py +10 -8
  10. guard_cli-0.1.3/guard_cli/integrations/antigravity.py +258 -0
  11. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/integrations/claude.py +70 -34
  12. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/state.py +55 -0
  13. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli.egg-info/PKG-INFO +15 -5
  14. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli.egg-info/SOURCES.txt +2 -0
  15. {guard_cli-0.1.1 → guard_cli-0.1.3}/pyproject.toml +7 -1
  16. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_antigravity_integration.py +2 -2
  17. guard_cli-0.1.3/tests/test_antigravity_pre_recording_fixes.py +345 -0
  18. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_api_and_hook.py +122 -13
  19. guard_cli-0.1.3/tests/test_commit_bypass_fresh_session.py +177 -0
  20. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_integration_init_doctor.py +5 -1
  21. guard_cli-0.1.1/guard_cli/integrations/antigravity.py +0 -154
  22. {guard_cli-0.1.1 → guard_cli-0.1.3}/LICENSE +0 -0
  23. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/agent_integration.py +0 -0
  24. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/backend.py +0 -0
  25. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/db.py +0 -0
  26. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/decision.py +0 -0
  27. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/events.py +0 -0
  28. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/feedback.py +0 -0
  29. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/ingest/__init__.py +0 -0
  30. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/ingest/static.py +0 -0
  31. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/integrations/__init__.py +0 -0
  32. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli/thin_slice.py +0 -0
  33. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli.egg-info/dependency_links.txt +0 -0
  34. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli.egg-info/entry_points.txt +0 -0
  35. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli.egg-info/requires.txt +0 -0
  36. {guard_cli-0.1.1 → guard_cli-0.1.3}/guard_cli.egg-info/top_level.txt +0 -0
  37. {guard_cli-0.1.1 → guard_cli-0.1.3}/setup.cfg +0 -0
  38. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_backend.py +0 -0
  39. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_commit_bypass.py +0 -0
  40. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_coverage.py +0 -0
  41. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_db.py +0 -0
  42. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_decision.py +0 -0
  43. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_doctor_cmd.py +0 -0
  44. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_embedded_platform.py +0 -0
  45. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_event_reasons.py +0 -0
  46. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_feedback.py +0 -0
  47. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_fingerprint_invalidation.py +0 -0
  48. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_hook_timeout.py +0 -0
  49. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_init_cmd.py +0 -0
  50. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_static.py +0 -0
  51. {guard_cli-0.1.1 → guard_cli-0.1.3}/tests/test_thin_slice.py +0 -0
@@ -1,8 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: guard-cli
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Graph-powered safety layer for AI coding agents built on FalkorDB
5
5
  License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/thisisvaishnav/guard
7
+ Project-URL: Repository, https://github.com/thisisvaishnav/guard
8
+ Project-URL: Bug Tracker, https://github.com/thisisvaishnav/guard/issues
9
+ Project-URL: Changelog, https://github.com/thisisvaishnav/guard/blob/main/CHANGELOG.md
6
10
  Requires-Python: >=3.10
7
11
  Description-Content-Type: text/markdown
8
12
  License-File: LICENSE
@@ -176,7 +180,13 @@ $$\text{Trust} = 0.5 \cdot \text{EvidenceRatio} + 0.3 \cdot \text{Freshness} + 0
176
180
 
177
181
  ## Claude Code & Agent Integration
178
182
 
179
- Guard integrates seamlessly into autonomous coding workflows:
183
+ > **Experimental**: Both the Claude Code Stop hook and the Antigravity integration are
184
+ > labeled **experimental**. The Antigravity `hooks.json` schema was verified against
185
+ > [Antigravity's official hook documentation](https://antigravity.google/docs/hooks), but
186
+ > both integrations have only been validated with simulated stdin/stdout input. A real
187
+ > live session (Claude Code, Antigravity) has not yet confirmed end-to-end behavior.
188
+
189
+ Guard integrates into autonomous coding workflows:
180
190
 
181
191
  - **Claude Code Stop Hook**: Installed via `guard hook install` into `.claude/settings.local.json`. Whenever Claude attempts to conclude a task, the Stop hook evaluates the diff. If tests fail, it blocks completion (Exit 2) and injects a concise $\le$ 30 line failure report.
182
192
  - **Idle-Turn Optimization**: Caches working-tree SHA-256 fingerprints and skips unchanged turns and non-Python diffs.
@@ -228,11 +238,11 @@ guard check [--repo /path/to/repo] [--json]
228
238
  ```
229
239
 
230
240
  ### `guard hook`
231
- Manages Claude Code and agent hook configurations:
241
+ Manages Claude Code and agent hook configurations (experimental — see note above):
232
242
  ```bash
233
- guard hook install [--shared]
243
+ guard hook install [--shared] [--antigravity]
234
244
  guard hook status
235
- guard hook uninstall
245
+ guard hook uninstall [--antigravity]
236
246
  ```
237
247
 
238
248
  ### `guard feedback`
@@ -155,7 +155,13 @@ $$\text{Trust} = 0.5 \cdot \text{EvidenceRatio} + 0.3 \cdot \text{Freshness} + 0
155
155
 
156
156
  ## Claude Code & Agent Integration
157
157
 
158
- Guard integrates seamlessly into autonomous coding workflows:
158
+ > **Experimental**: Both the Claude Code Stop hook and the Antigravity integration are
159
+ > labeled **experimental**. The Antigravity `hooks.json` schema was verified against
160
+ > [Antigravity's official hook documentation](https://antigravity.google/docs/hooks), but
161
+ > both integrations have only been validated with simulated stdin/stdout input. A real
162
+ > live session (Claude Code, Antigravity) has not yet confirmed end-to-end behavior.
163
+
164
+ Guard integrates into autonomous coding workflows:
159
165
 
160
166
  - **Claude Code Stop Hook**: Installed via `guard hook install` into `.claude/settings.local.json`. Whenever Claude attempts to conclude a task, the Stop hook evaluates the diff. If tests fail, it blocks completion (Exit 2) and injects a concise $\le$ 30 line failure report.
161
167
  - **Idle-Turn Optimization**: Caches working-tree SHA-256 fingerprints and skips unchanged turns and non-Python diffs.
@@ -207,11 +213,11 @@ guard check [--repo /path/to/repo] [--json]
207
213
  ```
208
214
 
209
215
  ### `guard hook`
210
- Manages Claude Code and agent hook configurations:
216
+ Manages Claude Code and agent hook configurations (experimental — see note above):
211
217
  ```bash
212
- guard hook install [--shared]
218
+ guard hook install [--shared] [--antigravity]
213
219
  guard hook status
214
- guard hook uninstall
220
+ guard hook uninstall [--antigravity]
215
221
  ```
216
222
 
217
223
  ### `guard feedback`
@@ -4,4 +4,4 @@ from importlib.metadata import version, PackageNotFoundError
4
4
  try:
5
5
  __version__ = version("guard-cli")
6
6
  except PackageNotFoundError:
7
- __version__ = "0.1.1"
7
+ __version__ = "0.1.2"
@@ -227,6 +227,49 @@ def run(
227
227
  "fail_open": True,
228
228
  }, 0
229
229
 
230
+ # Fail-open short-circuit: if check() could not evaluate the diff (database/graph
231
+ # unavailable), do not attempt the full-suite fallback. Running the whole suite on an
232
+ # unknown diff is itself unreliable (e.g. Guard's own tests require FalkorDB), so we
233
+ # allow the stop with a loud warning and record a fail_open event, matching Guard's
234
+ # documented fail-open behavior instead of blocking the agent.
235
+ if check_dict.get("fail_open") or check_dict.get("policy") == "fail_open":
236
+ reason = (
237
+ check_dict.get("fallback_reason")
238
+ or check_dict.get("error")
239
+ or "Guard database/graph unavailable"
240
+ )
241
+ if strict:
242
+ raise RuntimeError(f"Guard fail-open suppressed by strict mode: {reason}")
243
+ sys.stderr.write(
244
+ f"Guard warning (fail-open): {reason}. "
245
+ "Skipping safety check and allowing stop without running tests.\n"
246
+ )
247
+ log_event(
248
+ event="fail_open",
249
+ session_id=session_id,
250
+ repo_path=repo,
251
+ details={"op": "run", "error": reason},
252
+ )
253
+ return {
254
+ "version": SCHEMA_VERSION,
255
+ "status": "passed",
256
+ "policy": "fail_open",
257
+ "trust": 0.0,
258
+ "overrides_triggered": check_dict.get("overrides_triggered", ["fail_open_error"]),
259
+ "selected_tests": [],
260
+ "failures": [],
261
+ "reasons": check_dict.get("reasons", {}),
262
+ "tests_run": [],
263
+ "tests_run_count": 0,
264
+ "tests_passed": True,
265
+ "forced_full": False,
266
+ "fallback_reason": reason,
267
+ "duration_s": 0.0,
268
+ "exit_code": 0,
269
+ "output_summary": f"Guard bypassed fail-open: {reason}",
270
+ "fail_open": True,
271
+ }, 0
272
+
230
273
  forced_full = check_dict.get("is_fallback", False) or (check_dict.get("status") == "run-all")
231
274
  if forced_full:
232
275
  tests_to_run = ["tests"]
@@ -171,23 +171,42 @@ def cmd_hook_stop(args) -> int:
171
171
  )
172
172
 
173
173
 
174
+ def cmd_hook_session_start(args) -> int:
175
+ from guard_cli.hook import run_session_start_hook
176
+ return run_session_start_hook(repo_override=args.repo)
177
+
178
+
174
179
  def cmd_hook_install(args) -> int:
175
- from guard_cli.integrations import claude
176
- ok, msg = claude.install(repo_path=args.repo, shared=args.shared)
180
+ if getattr(args, "antigravity", False):
181
+ from guard_cli.integrations import antigravity
182
+ ok, msg = antigravity.install(repo_path=args.repo)
183
+ else:
184
+ from guard_cli.integrations import claude
185
+ ok, msg = claude.install(repo_path=args.repo, shared=args.shared)
177
186
  print(msg)
178
187
  return 0 if ok else 1
179
188
 
180
189
 
181
190
  def cmd_hook_uninstall(args) -> int:
182
- from guard_cli.integrations import claude
183
- ok, msg = claude.uninstall(repo_path=args.repo, shared=args.shared)
191
+ if getattr(args, "antigravity", False):
192
+ from guard_cli.integrations import antigravity
193
+ ok, msg = antigravity.uninstall(repo_path=args.repo)
194
+ else:
195
+ from guard_cli.integrations import claude
196
+ ok, msg = claude.uninstall(repo_path=args.repo, shared=args.shared)
184
197
  print(msg)
185
198
  return 0 if ok else 1
186
199
 
187
200
 
188
201
  def cmd_hook_status(args) -> int:
189
- from guard_cli.integrations import claude
190
- stat = claude.status(repo_path=args.repo, shared=args.shared)
202
+ from guard_cli.integrations import claude, antigravity
203
+ if getattr(args, "antigravity", False):
204
+ stat = {"antigravity": antigravity.status(repo_path=args.repo)}
205
+ else:
206
+ stat = {
207
+ "claude": claude.status(repo_path=args.repo, shared=getattr(args, "shared", False)),
208
+ "antigravity": antigravity.status(repo_path=args.repo),
209
+ }
191
210
  print(json.dumps(stat, indent=2))
192
211
  return 0
193
212
 
@@ -260,19 +279,26 @@ def main(argv=None) -> int:
260
279
  hk_stop.add_argument("--strict", action="store_true", help="fail closed on error")
261
280
  hk_stop.set_defaults(fn=cmd_hook_stop)
262
281
 
263
- hk_inst = hk_sub.add_parser("install", help="Install Stop hook into Claude Code settings")
282
+ hk_start = hk_sub.add_parser("session-start", help="Record baseline SHA at session start (Claude Code SessionStart / Antigravity PreInvocation)")
283
+ hk_start.add_argument("--repo", default=None, help="path to repo root")
284
+ hk_start.set_defaults(fn=cmd_hook_session_start)
285
+
286
+ hk_inst = hk_sub.add_parser("install", help="Install Stop hook into agent settings (Claude Code by default, Antigravity with --antigravity)")
264
287
  hk_inst.add_argument("--repo", default=None, help="path to repo root")
265
288
  hk_inst.add_argument("--shared", action="store_true", help="install to settings.json instead of settings.local.json")
289
+ hk_inst.add_argument("--antigravity", action="store_true", help="install to Antigravity .agents/hooks.json instead of Claude Code settings")
266
290
  hk_inst.set_defaults(fn=cmd_hook_install)
267
291
 
268
- hk_uninst = hk_sub.add_parser("uninstall", help="Uninstall Stop hook from Claude Code settings")
292
+ hk_uninst = hk_sub.add_parser("uninstall", help="Uninstall Stop hook from agent settings (Claude Code by default, Antigravity with --antigravity)")
269
293
  hk_uninst.add_argument("--repo", default=None, help="path to repo root")
270
294
  hk_uninst.add_argument("--shared", action="store_true", help="uninstall from settings.json instead of settings.local.json")
295
+ hk_uninst.add_argument("--antigravity", action="store_true", help="uninstall from Antigravity .agents/hooks.json instead of Claude Code settings")
271
296
  hk_uninst.set_defaults(fn=cmd_hook_uninstall)
272
297
 
273
- hk_stat = hk_sub.add_parser("status", help="Check Claude Code hook installation status")
298
+ hk_stat = hk_sub.add_parser("status", help="Check hook installation status (Claude Code by default, Antigravity with --antigravity)")
274
299
  hk_stat.add_argument("--repo", default=None, help="path to repo root")
275
300
  hk_stat.add_argument("--shared", action="store_true", help="check settings.json instead of settings.local.json")
301
+ hk_stat.add_argument("--antigravity", action="store_true", help="check Antigravity .agents/hooks.json instead of Claude Code settings")
276
302
  hk_stat.set_defaults(fn=cmd_hook_status)
277
303
 
278
304
  fb = sub.add_parser("feedback")
@@ -313,6 +339,9 @@ def main(argv=None) -> int:
313
339
  cov.set_defaults(fn=cmd_covering_tests)
314
340
 
315
341
  args = p.parse_args(argv)
342
+ if not hasattr(args, "fn"):
343
+ p.print_help()
344
+ return 1
316
345
  return args.fn(args)
317
346
 
318
347
 
@@ -316,6 +316,14 @@ def check_integrations(repo_path: Path) -> CheckResult:
316
316
  agy_info = antigravity.status(repo_path)
317
317
  if not claude_info["installed"] and not agy_info["installed"]:
318
318
  missing.append("hook in .agents/hooks.json or .claude/settings.json")
319
+ elif agy_info.get("installed") and not agy_info.get("executable_exists", True):
320
+ exe = agy_info.get("executable_path")
321
+ return CheckResult(
322
+ name="integrations",
323
+ status="fail",
324
+ message=f"Guard executable configured in .agents/hooks.json does not exist: {exe}.",
325
+ fix="Run 'guard init' or 'guard hook install --antigravity' to update executable path.",
326
+ )
319
327
 
320
328
  # 3. Check AGENTS.md or CLAUDE.md marked block
321
329
  agents_doc = repo_path / "AGENTS.md"