Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Gradata/scripts/*
!Gradata/scripts/publish-npm.sh
!Gradata/scripts/cloud/
!Gradata/scripts/migrate_legacy_scopes.py
!Gradata/scripts/smoke_quickstart.py

# npm sub-package build outputs (source tracked, outputs ignored)
Gradata/packages/npm/node_modules/
Expand Down
8 changes: 8 additions & 0 deletions Gradata/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ gradata install --agent claude-code --brain ./my-brain
gradata audit
```

Want to prove the SDK/CLI path first, without Cloud sync, API keys, or a daemon? From a checkout, run:

```bash
python3 scripts/smoke_quickstart.py
```

The smoke script creates a temporary local brain, records one correction, recalls rules, and renders a manifest using only stdlib + the local package.

Supported agent targets:

```bash
Expand Down
10 changes: 10 additions & 0 deletions Gradata/docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

Get a brain learning from your corrections in under 5 minutes.

## 0. Optional: prove the local quickstart works offline

Before connecting Cloud sync, agent hooks, or API keys, run the repo smoke test:

```bash
python3 scripts/smoke_quickstart.py
```

It creates a temporary local brain, records one correction, recalls rules, renders a manifest, and exits without network credentials or a daemon. This is the fastest pre-Hacker News proof that the SDK/CLI path works on your machine.

## 1. Create a Brain

```python
Expand Down
137 changes: 137 additions & 0 deletions Gradata/scripts/smoke_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Offline quickstart smoke test for Show HN/readme proof.

This intentionally uses only the local SDK/CLI path:
- no Gradata Cloud key
- no daemon requirement
- no network calls
- no LLM/provider credentials

Run from a checkout:
python3 scripts/smoke_quickstart.py
"""

from __future__ import annotations

import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src"


def _run(args: list[str], *, cwd: Path, env: dict[str, str]) -> subprocess.CompletedProcess[str]:
result = subprocess.run(
args,
cwd=cwd,
env=env,
text=True,
capture_output=True,
timeout=30,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make command timeout configurable to reduce flaky failures.

A fixed 30s timeout is brittle for slower CI/dev hosts; one slow command will fail the full smoke run.

Proposed fix
-        timeout=30,
+        timeout=int(env.get("GRADATA_SMOKE_TIMEOUT_SEC", "60")),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Gradata/scripts/smoke_quickstart.py` at line 35, The hard-coded timeout=30 in
smoke_quickstart.py makes the smoke run flaky on slow CI; make the timeout
configurable by replacing the literal with a variable (e.g., cmd_timeout) that
is populated from a config source such as an environment variable
(SMOKE_CMD_TIMEOUT) or a CLI argument with a default of 30, parse it to int, and
use that variable wherever timeout=30 is currently passed (search for the exact
token "timeout=30" in smoke_quickstart.py and update the surrounding function
that runs subprocesses or commands to accept/consume the configurable timeout).

check=False,
)
if result.returncode != 0:
cmd = " ".join(args)
raise RuntimeError(
f"command failed ({result.returncode}): {cmd}\n"
f"stdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}\n"
)
return result


def smoke(tmp_root: Path) -> dict[str, object]:
brain_dir = tmp_root / "show-hn-brain"
home_dir = tmp_root / "home"
home_dir.mkdir()

env = os.environ.copy()
env.update(
{
"HOME": str(home_dir),
"PYTHONPATH": str(SRC),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Preserve existing PYTHONPATH instead of overwriting it.

Overwriting can break environments that already depend on PYTHONPATH. Prepend SRC and keep existing entries.

Proposed fix
-            "PYTHONPATH": str(SRC),
+            "PYTHONPATH": (
+                f"{SRC}{os.pathsep}{env['PYTHONPATH']}"
+                if env.get("PYTHONPATH")
+                else str(SRC)
+            ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"PYTHONPATH": str(SRC),
"PYTHONPATH": (
f"{SRC}{os.pathsep}{env['PYTHONPATH']}"
if env.get("PYTHONPATH")
else str(SRC)
),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Gradata/scripts/smoke_quickstart.py` at line 57, The env construction
currently overwrites PYTHONPATH by setting "PYTHONPATH": str(SRC); instead
prepend SRC to any existing PYTHONPATH instead of replacing it: read the current
value via os.environ.get("PYTHONPATH", ""), join SRC and the existing value with
os.pathsep (handling empty existing value so you don't add a trailing
separator), and set that combined string as the "PYTHONPATH" entry in the env
dict where SRC is referenced.

"GRADATA_TELEMETRY": "0",
"GRADATA_DISABLE_WRITE_THROUGH": "1",
"GRADATA_BRAIN": str(brain_dir),
}
)
env.pop("GRADATA_API_KEY", None)
env.pop("ANTHROPIC_API_KEY", None)
env.pop("OPENAI_API_KEY", None)
env.pop("GOOGLE_API_KEY", None)

py = sys.executable
commands = [
[
py,
"-m",
"gradata.cli",
"init",
str(brain_dir),
"--domain",
"Sales",
"--name",
"Show HN Smoke Brain",
"--no-interactive",
],
[
py,
"-m",
"gradata.cli",
"--brain-dir",
str(brain_dir),
"correct",
"--draft",
"We are pleased to inform you of our new product offering.",
"--final",
"Hey, check out what we just shipped.",
"--category",
"tone",
"--session",
"1",
],
[py, "-m", "gradata.cli", "--brain-dir", str(brain_dir), "recall", "draft a launch email"],
[py, "-m", "gradata.cli", "--brain-dir", str(brain_dir), "manifest", "--json"],
[py, "-m", "gradata.cli", "--brain-dir", str(brain_dir), "stats"],
]

outputs: list[str] = []
for cmd in commands:
result = _run(cmd, cwd=ROOT, env=env)
outputs.append(result.stdout.strip())

manifest = json.loads(outputs[3])
system_db = brain_dir / "system.db"
if not system_db.exists():
raise AssertionError(f"expected local brain database at {system_db}")

return {
"brain_dir": str(brain_dir),
"database_created": system_db.exists(),
"sessions_trained": manifest.get("metadata", {}).get("sessions_trained"),
"commands": [" ".join(cmd) for cmd in commands],
}


def main() -> int:
keep = os.environ.get("GRADATA_SMOKE_KEEP_TMP") == "1"
tmp_root = Path(tempfile.mkdtemp(prefix="gradata-quickstart-smoke-"))
try:
result = smoke(tmp_root)
print("Gradata offline quickstart smoke: PASS")
print(json.dumps(result, indent=2))
return 0
finally:
if keep:
print(f"kept temp dir: {tmp_root}")
else:
shutil.rmtree(tmp_root, ignore_errors=True)


if __name__ == "__main__":
raise SystemExit(main())
50 changes: 32 additions & 18 deletions Gradata/src/gradata/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,24 +514,38 @@ def _cmd_install_agent(args) -> None:
from gradata import Brain

verification_marker = f"gradata-install-verify-{name}-{os.urandom(4).hex()}"
with tempfile.TemporaryDirectory(prefix="gradata-verify-") as verification_tmp:
verification_dir = Path(verification_tmp) / "brain"
Brain.init(verification_dir)
verification_brain = Brain(verification_dir)
correction = verification_brain.correct(
draft=f"test draft for {name} install verification {verification_marker}",
final=f"test final for {name} install verification {verification_marker}",
dry_run=False,
)
results = verification_brain.search(verification_marker, mode="rules", top_k=3)
marker_found = any(
verification_marker in (r.get("text") or "").lower() for r in results
)
if not marker_found:
print(f" ⚠ verify failed: test rule written but not readable for {name}")
had_failure = True
previous_disable_write_through = os.environ.get("GRADATA_DISABLE_WRITE_THROUGH")
os.environ["GRADATA_DISABLE_WRITE_THROUGH"] = "1"
try:
with tempfile.TemporaryDirectory(prefix="gradata-verify-") as verification_tmp:
verification_dir = Path(verification_tmp) / "brain"
Brain.init(
verification_dir,
name="Gradata install verification",
domain="General",
interactive=False,
)
verification_brain = Brain(verification_dir)
verification_brain.correct(
draft=f"test draft for {name} install verification {verification_marker}",
final=f"test final for {name} install verification {verification_marker}",
dry_run=False,
)
events = verification_brain.query_events(event_type="CORRECTION", limit=10)
marker_found = any(
verification_marker in json.dumps(event.get("data", {})).lower()
for event in events
)
finally:
if previous_disable_write_through is None:
os.environ.pop("GRADATA_DISABLE_WRITE_THROUGH", None)
else:
print(f" ✓ verify: {name} install confirmed (write+read)")
os.environ["GRADATA_DISABLE_WRITE_THROUGH"] = previous_disable_write_through
if not marker_found:
print(f" ⚠ verify failed: test correction written but not readable for {name}")
had_failure = True
else:
print(f" ✓ verify: {name} install confirmed (write+read)")
except Exception as exc:
print(f" ✗ verify failed for {name}: {exc}")
had_failure = True
Expand Down Expand Up @@ -868,7 +882,7 @@ def cmd_prove(args):
xs = list(range(n))
mean_x = sum(xs) / n
mean_y = sum(counts) / n
num = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, counts))
num = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, counts, strict=False))
den = sum((x - mean_x) ** 2 for x in xs) or 1.0
slope = num / den

Expand Down
16 changes: 16 additions & 0 deletions Gradata/tests/test_quickstart_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

from pathlib import Path
from typing import cast

from scripts.smoke_quickstart import smoke


def test_offline_quickstart_smoke(tmp_path: Path) -> None:
result = smoke(tmp_path)
commands = cast("list[str]", result["commands"])

assert result["database_created"] is True
assert result["sessions_trained"] is not None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen sessions_trained assertion to catch no-op runs.

is not None still passes for 0; this can miss regressions where training doesn’t happen.

Proposed fix
-    assert result["sessions_trained"] is not None
+    assert isinstance(result["sessions_trained"], int)
+    assert result["sessions_trained"] >= 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert result["sessions_trained"] is not None
assert isinstance(result["sessions_trained"], int)
assert result["sessions_trained"] >= 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Gradata/tests/test_quickstart_smoke.py` at line 14, The assertion for
result["sessions_trained"] only checks for not None and would accept 0 (a no-op
run); change the check on the test that contains assert
result["sessions_trained"] is not None to assert that result["sessions_trained"]
is a positive integer (e.g., assert isinstance(result["sessions_trained"], int)
and result["sessions_trained"] > 0 or simply assert result["sessions_trained"]
>= 1) so the test fails when no training sessions were actually run.

assert any("gradata.cli init" in cmd for cmd in commands)
assert any("gradata.cli --brain-dir" in cmd and " correct " in cmd for cmd in commands)
Loading