Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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())
9 changes: 5 additions & 4 deletions Gradata/src/gradata/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,17 +518,18 @@ def _cmd_install_agent(args) -> None:
verification_dir = Path(verification_tmp) / "brain"
Brain.init(verification_dir)
verification_brain = Brain(verification_dir)
correction = verification_brain.correct(
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)
events = verification_brain.query_events(event_type="CORRECTION", limit=10)
marker_found = any(
verification_marker in (r.get("text") or "").lower() for r in results
verification_marker in json.dumps(event.get("data", {})).lower()
for event in events
)
if not marker_found:
print(f" ⚠ verify failed: test rule written but not readable for {name}")
print(f" ⚠ verify failed: test correction written but not readable for {name}")
had_failure = True
else:
print(f" ✓ verify: {name} install confirmed (write+read)")
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