Skip to content

feat(distribution): npm @gradata/cli + Docker daemon image + tag-triggered publish workflows (Phase 4)#80

Merged
Gradata merged 5 commits intomainfrom
wt-phase4-distribution-npm-docker
Apr 15, 2026
Merged

feat(distribution): npm @gradata/cli + Docker daemon image + tag-triggered publish workflows (Phase 4)#80
Gradata merged 5 commits intomainfrom
wt-phase4-distribution-npm-docker

Conversation

@Gradata
Copy link
Copy Markdown
Owner

@Gradata Gradata commented Apr 15, 2026

Summary

Phase 4 distribution: ship the SDK in two new runtimes.

  • npm: @gradata/cli package — Node wrapper + JS correction client + ESM entry + TypeScript
  • Docker: multi-stage Dockerfile (uv + alpine + non-root) with smart entrypoint
  • CI: 2 new tag-triggered workflows mirroring sdk-publish.yml's pattern (no edits to existing publish workflow)

What ships

  • Dockerfile, .dockerignore, docker-compose.yml
  • packages/npm/{package.json, tsconfig.json, README.md}, packages/npm/src/{client.ts, client.test.ts}, packages/npm/bin/gradata.mjs
  • .github/workflows/{docker-publish.yml, npm-publish.yml}
  • scripts/publish-npm.sh
  • README sections (npm + Docker install)

Verified

  • tsc --noEmit strict-mode + exactOptionalPropertyTypes: pass
  • vitest: 4/4 (payload shape, health fallback, 4xx mapping, endpoint normalization)
  • node packages/npm/bin/gradata.mjs --version -> 0.1.0
  • npm pack --dry-run: 6.8 kB packed / 23.7 kB unpacked, 9 files
  • uv run pytest tests/ -q -> 2342 passed (no Python touched)

Caveat

Docker image NOT built locally — Docker Desktop daemon down on this machine. Dockerfile is syntactically sound; docker-publish.yml builds + runs --version smoke + prints size on every push. First CI run validates.

Plan divergences

  1. .gitignore was blocking the new files (Dockerfile, packages/, package.json, scripts/) — un-ignored as part of the relevant commits.
  2. Added .dockerignore (not in plan but required: pytest+docs+.claude would push image past 200MB).
  3. Dockerfile entrypoint is a smart wrapper: routes --version to a python one-liner (daemon.py has no native flag), everything else to python -m gradata.daemon.

Out of scope

Hermes skill export — separate next sub-task.

Commits (5 in plan order)

  • 167a041 feat(docker): multi-stage Dockerfile with uv + gradata daemon entrypoint
  • ae09431 feat(docker): GitHub Actions tag-triggered GHCR publish workflow
  • 2c174ab feat(npm): @gradata/cli package with Node wrapper and JS correction client
  • 75155cf feat(npm): tag-triggered npm-publish workflow + release script
  • 998c58b docs(readme): add npm and Docker install sections

Co-Authored-By: Gradata noreply@gradata.ai

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Warning

Rate limit exceeded

@Gradata has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 13 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 13 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3629eb2-77bb-46d3-98fd-360fe18f33ad

📥 Commits

Reviewing files that changed from the base of the PR and between 744c27b and 8e20c96.

📒 Files selected for processing (14)
  • .dockerignore
  • .github/workflows/docker-publish.yml
  • .github/workflows/npm-publish.yml
  • .gitignore
  • Dockerfile
  • README.md
  • docker-compose.yml
  • packages/npm/README.md
  • packages/npm/bin/gradata.mjs
  • packages/npm/package.json
  • packages/npm/src/client.test.ts
  • packages/npm/src/client.ts
  • packages/npm/tsconfig.json
  • scripts/publish-npm.sh
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wt-phase4-distribution-npm-docker

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 15, 2026

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8e20c96
Status: ✅  Deploy successful!
Preview URL: https://f5003542.gradata-dashboard.pages.dev
Branch Preview URL: https://wt-phase4-distribution-npm-d.gradata-dashboard.pages.dev

View logs

@Gradata Gradata force-pushed the wt-phase4-distribution-npm-docker branch from 998c58b to 3701088 Compare April 15, 2026 09:17
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Gradata and others added 5 commits April 15, 2026 02:49
Multi-stage build: python:3.12-alpine builder installs uv and builds a
venv with the base gradata SDK; runtime stage copies the prebuilt venv
into a fresh alpine image and runs as non-root. Entrypoint wrapper
routes --version / daemon / cli / positional args appropriately.

Also ships a docker-compose.yml for local dev (mounts ./brain, binds
8765, includes /health healthcheck) and unignores Dockerfile +
packages/ so distribution artifacts are tracked.

Co-Authored-By: Gradata <noreply@gradata.ai>
Validate-only on push/PR to main (build + --version smoke + size
check); publish to ghcr.io on docker-v* tags. Mirrors the structure
of sdk-publish.yml (tag parsing, prerelease detection, conditional
publish job). Pre-release tags (rc/a/b/dev/alpha/beta) skip the
:latest tag.

Co-Authored-By: Gradata <noreply@gradata.ai>
…lient

New npm package under packages/npm:
- src/client.ts: GradataClient typed over fetch; posts correction events
  to a local daemon at /correct, pings /health, maps daemon errors to
  typed exceptions.
- src/client.test.ts: vitest suite (4 tests) covering payload shape,
  health fallback on network error, 4xx error mapping, endpoint
  normalisation.
- bin/gradata.mjs: ESM CLI wrapper. `correct` subcommand talks to the
  daemon directly from Node; any other subcommand shells to
  `python -m gradata` with a helpful error if Python/SDK is missing.
- tsconfig.json: strict TS targeting ES2022 with declaration + maps.
- package.json: @gradata/cli, type=module, bin + exports, Node >=18.

.gitignore updated to unignore packages/**/package.json so the
manifests are tracked.

Typecheck, vitest, and `npm pack --dry-run` (6.8 kB tarball) all pass.

Co-Authored-By: Gradata <noreply@gradata.ai>
.github/workflows/npm-publish.yml mirrors the structure of
sdk-publish.yml: parse version from npm-v* tag, verify against
packages/npm/package.json, skip if already published, run
typecheck + vitest + build + pack, then publish with npm
provenance. Pre-release versions ship under dist-tag=next, final
versions under latest.

scripts/publish-npm.sh is the manual fallback (NPM_TOKEN gated,
--dry-run supported, cleans up npmrc auth on exit).

.gitignore narrowed from `scripts/` to `scripts/*` so
scripts/publish-npm.sh is tracked without opening the whole
directory.

Co-Authored-By: Gradata <noreply@gradata.ai>
Slot two blurbs into the existing install block (after the pip/npx
lines, before the "Works with any LLM" tagline): a JS/TS section
showing the @gradata/cli client against a local daemon, and a
Docker section showing `docker run` against the GHCR image plus a
pointer to docker-compose.yml. Rest of README untouched.

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata Gradata force-pushed the wt-phase4-distribution-npm-docker branch from 3701088 to 8e20c96 Compare April 15, 2026 09:49
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata Gradata merged commit 7cae473 into main Apr 15, 2026
13 checks passed
Gradata added a commit that referenced this pull request Apr 15, 2026
…ebase onto main

Rewrite TestSelfHealingE2E::test_inline_auto_heal_emits_rule_patched and
TestCorrectAutoHealIntegration::test_auto_heal_emits_patch_when_retroactive_test_passes
to exercise ONLY the inline `brain.correct(auto_heal=True)` path. Remove
the manual `review_rule_failures()` + `brain.patch_rule()` fallback so a
regression in the inline orchestration fails the test instead of being
masked by the fallback emitting RULE_PATCHED on its own.

Also address three smaller CR items:
- `_core.py`: forward `auto_heal` through the cloud branch by falling
  back to the local pipeline when the flag is set (cloud client does
  not yet carry the flag), so `auto_heal=True` is no longer silently
  dropped in cloud-connected brains.
- `_core.py`: route the auto-heal notice through `_log.warning` instead
  of `print(..., file=sys.stderr)` to keep the SDK logging pipeline
  consistent.
- `brain.py`: invalidate `_rule_cache` after `Brain.auto_heal()` applies
  patches, so later `apply_brain_rules()` calls see the patched prompt.

Rebased onto origin/main (includes #76 cloud split, #80 distribution,
#81 rule-to-hook auto-promotion). Rebase was conflict-free -- zero file
overlap between this branch and the three landed PRs.

Co-Authored-By: Gradata <noreply@gradata.ai>
Gradata added a commit that referenced this pull request Apr 15, 2026
…() (Phase 1) (#77)

* feat(self-healing): close the auto-heal loop

brain.auto_heal() reads recent RULE_FAILURE events, runs
review_rule_failures, gates each candidate through retroactive_test,
and applies the survivors via brain.patch_rule. Batches are
deduped by (category, original_description) and hard-capped at
max_patches (default 5) so a single session can't rewrite a rule
multiple times.

Before this commit, PR #21 emitted RULE_FAILURE but nothing
consumed it. The full flow required the caller to manually call
review_rule_failures + brain.patch_rule as the E2E test does.

Adds auto_heal_failures(brain, ...) orchestrator in self_healing.py
and Brain.auto_heal public method that delegates to it. 6 new
tests covering empty input, passing candidate, retro-test failure,
in-batch dedup, max_patches cap, and event-log read when no
events are passed.

* feat(self-healing): wire auto_heal into brain.correct() by default

When brain.correct() detects a RULE_FAILURE it now invokes
auto_heal_failures inline, capped at one patch per call so a
single correction can rewrite at most the rule it just failed.
Skipped in dry_run, approval_required, and renter modes; opt-out
via correct(auto_heal=False).

Adds auto_heal kwarg to brain.correct + brain_correct in _core.py
and surfaces the heal summary on event["auto_healed"] when a
patch lands. 3 new tests covering the default-on path, the
opt-out path, and the dry_run skip.

Closes the diagnose-then-do-nothing gap that PR #21 left open.

* fix(self-healing): default auto_heal=False per council; add PatchReceipt for visibility

Polyclaude council verdict (4/4 FALSE) on Phase 1: default auto_heal=True
is too hot. Ship False default plus a visible PatchReceipt so any auto-heal
that does fire is loud, not silent. Flip to True deferred until (a) a
pending_patches review queue and (b) ablation showing rule-quality lift on
a held-out broken-rule corpus.

- brain.correct() and _core.brain_correct() default auto_heal to False
- auto_heal_failures() returns PatchReceipt dicts: rule_id, old/new
  confidence, patch_diff, revert_command (legacy fields kept for BC)
- _core emits one stderr line per successful auto-heal patch so silent
  rule edits cannot sneak through
- tests: opt-in explicit auto_heal=True on existing integration tests,
  plus new test_default_off_no_patch and test_patch_receipt_shape

Co-Authored-By: Gradata <noreply@gradata.ai>

* fix(self-healing): address CodeRabbit review on #77 (docstring + type ann + test fixture)

- Document auto_heal parameter on Brain.correct (defaults to False post-council)
- Add forward-ref Brain type annotation on auto_heal_failures via TYPE_CHECKING
- Hoist brain_with_rule to module-scope fixture and remove 5 duplicated class-level copies
- Split weak test_auto_heal_triggers_on_rule_failure into two deterministic tests: one asserts rule_failure_detected only, the second drives auto_heal_failures with known-good delta words and asserts patched==1 plus auto_heal: reason prefix

Co-Authored-By: Gradata <noreply@gradata.ai>

* fix(self-healing): address CodeRabbit on inline auto-heal coverage; rebase onto main

Rewrite TestSelfHealingE2E::test_inline_auto_heal_emits_rule_patched and
TestCorrectAutoHealIntegration::test_auto_heal_emits_patch_when_retroactive_test_passes
to exercise ONLY the inline `brain.correct(auto_heal=True)` path. Remove
the manual `review_rule_failures()` + `brain.patch_rule()` fallback so a
regression in the inline orchestration fails the test instead of being
masked by the fallback emitting RULE_PATCHED on its own.

Also address three smaller CR items:
- `_core.py`: forward `auto_heal` through the cloud branch by falling
  back to the local pipeline when the flag is set (cloud client does
  not yet carry the flag), so `auto_heal=True` is no longer silently
  dropped in cloud-connected brains.
- `_core.py`: route the auto-heal notice through `_log.warning` instead
  of `print(..., file=sys.stderr)` to keep the SDK logging pipeline
  consistent.
- `brain.py`: invalidate `_rule_cache` after `Brain.auto_heal()` applies
  patches, so later `apply_brain_rules()` calls see the patched prompt.

Rebased onto origin/main (includes #76 cloud split, #80 distribution,
#81 rule-to-hook auto-promotion). Rebase was conflict-free -- zero file
overlap between this branch and the three landed PRs.

Co-Authored-By: Gradata <noreply@gradata.ai>

* fix(self-healing): use contextlib.suppress for cache invalidation (Ruff SIM105)

CodeRabbit's 3rd review on PR #77 + CI Ruff job both flagged the
try/except/pass block at brain.py:548-552 as SIM105 violation. Replaced
with contextlib.suppress(Exception) (contextlib already imported at
line 51). Behavior unchanged.

Co-Authored-By: Gradata <noreply@gradata.ai>

---------

Co-authored-by: Gradata <noreply@gradata.ai>
Gradata added a commit that referenced this pull request Apr 15, 2026
…gin/

README:
- Lead with problem framing ("Your AI keeps making the same mistakes")
- 30-second pitch table moved up with vs Mem0 / vs fine-tuning / vs system prompts
- Kept ablation v4 data + Min 2022 random-label control
- Merged new JS/TS (@gradata/cli) and Docker install paths from #80
- Preserved Inspection & Transparency API section from #56
- Tightened features into scannable single-line blocks
- Repo layout + intellectual lineage kept but demoted below primary CTA

.gitignore:
- Add gradata-plugin/ (orphaned clone of separate plugin repo, superseded by
  .claude-plugin/ in this repo via #53; can't be rm'd due to OneDrive lock)

Co-Authored-By: Gradata <noreply@gradata.ai>
Gradata added a commit that referenced this pull request Apr 15, 2026
…gin/ (#84)

README:
- Lead with problem framing ("Your AI keeps making the same mistakes")
- 30-second pitch table moved up with vs Mem0 / vs fine-tuning / vs system prompts
- Kept ablation v4 data + Min 2022 random-label control
- Merged new JS/TS (@gradata/cli) and Docker install paths from #80
- Preserved Inspection & Transparency API section from #56
- Tightened features into scannable single-line blocks
- Repo layout + intellectual lineage kept but demoted below primary CTA

.gitignore:
- Add gradata-plugin/ (orphaned clone of separate plugin repo, superseded by
  .claude-plugin/ in this repo via #53; can't be rm'd due to OneDrive lock)

Co-authored-by: Gradata <noreply@gradata.ai>
@Gradata Gradata deleted the wt-phase4-distribution-npm-docker branch April 17, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant