feat(plugin): Claude Code plugin for /plugin install gradata#53
Conversation
…tall
Ships .claude-plugin/plugin.json + hooks/hooks.json so users can install
Gradata via Claude Code's plugin marketplace. Hooks wire into existing
gradata.hooks.{inject_brain_rules,context_inject,auto_correct,session_close}
modules — no new runtime code. Plugin assumes pipx install gradata.
Co-Authored-By: Gradata <noreply@gradata.ai>
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
📝 WalkthroughClaude Code Plugin Distribution
WalkthroughAdded Claude Code plugin configuration and documentation files, including plugin metadata, hook lifecycle definitions, and comprehensive setup/troubleshooting guides. Updated main README with quick-start instructions for plugin installation and advanced SDK setup paths. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Suggested labels
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Deploying gradata-dashboard with
|
| Latest commit: |
8df14b3
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://27c1cb80.gradata-dashboard.pages.dev |
| Branch Preview URL: | https://feat-claude-code-plugin.gradata-dashboard.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude-plugin/README.md:
- Around line 24-27: Update the untyped fenced code blocks containing the plugin
CLI commands so they specify the bash language; locate the fences that wrap the
lines with "/plugin marketplace add Gradata/gradata" and "/plugin install
gradata" as well as the fence around "/plugin uninstall gradata" and change the
opening backticks to include "bash" (```bash) so markdownlint no longer flags
them.
In `@hooks/hooks.json`:
- Around line 9-10: Replace the four hook commands that call the package via the
system Python ("python -m gradata.hooks.inject_brain_rules") with the package's
CLI entrypoint so pipx-installed gradata can run them; for each hook in
hooks.json (SessionStart, UserPromptSubmit, PostToolUse, Stop) change the
"command" value from the module invocation to the corresponding CLI form (e.g.,
use "gradata hooks inject-brain-rules" or the equivalent gradata CLI subcommand
for the same hook), keeping the same "timeout" values.
In `@README.md`:
- Around line 41-44: Replace the incorrect top-level command usage in README.md:
the documented command `gradata install-hook --ide=claude-code` is not supported
by the CLI dispatcher; update the example to use the hooks subcommand form (e.g.
`gradata hooks install --ide=claude-code`) so it matches the CLI dispatcher
entry points and will work when copy/pasted.
- Around line 32-35: The fenced command block containing the two plugin commands
(the block with "/plugin marketplace add Gradata/gradata" and "/plugin install
gradata") must be converted to a typed fence for markdownlint and correct
rendering; replace the opening triple backticks with a language identifier
(e.g., change "```" to "```bash") so the block becomes a bash fenced code block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1cee7fda-af44-48b1-92e4-b20b609b7122
📒 Files selected for processing (4)
.claude-plugin/README.md.claude-plugin/plugin.jsonREADME.mdhooks/hooks.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cloudflare Pages
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.0)
README.md
[warning] 32-32: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
.claude-plugin/README.md
[warning] 24-24: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 57-57: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (1)
.claude-plugin/plugin.json (1)
1-19: Metadata payload looks complete and internally consistent.No correctness concerns in this manifest segment.
| ``` | ||
| /plugin marketplace add Gradata/gradata | ||
| /plugin install gradata | ||
| ``` |
There was a problem hiding this comment.
Add language tags to both command fences.
Lines 24 and 57 use untyped fenced code blocks; markdownlint flags both. Use bash for consistency.
Suggested patch
-```
+```bash
/plugin marketplace add Gradata/gradata
/plugin install gradata...
- +bash
/plugin uninstall gradata
Also applies to: 57-59
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 24-24: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude-plugin/README.md around lines 24 - 27, Update the untyped fenced
code blocks containing the plugin CLI commands so they specify the bash
language; locate the fences that wrap the lines with "/plugin marketplace add
Gradata/gradata" and "/plugin install gradata" as well as the fence around
"/plugin uninstall gradata" and change the opening backticks to include "bash"
(```bash) so markdownlint no longer flags them.
| "command": "python -m gradata.hooks.inject_brain_rules", | ||
| "timeout": 10000 |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
When a package is installed via pipx, does python -m work from the system/default Python by default, or are only console entry-point scripts exposed on PATH?
💡 Result:
When a package is installed via pipx, only console entry-point scripts are exposed on PATH by default. The python -m command does not work from the system/default Python, because pipx installs the package into an isolated virtual environment (typically in ~/.local/share/pipx/venvs/), and the packages in that venv's site-packages are not on the system Python's module search path (sys.path). The entry-point scripts are symlinked to ~/.local/bin (on PATH), and those scripts run using the Python interpreter from the isolated venv, not the system Python.
Citations:
- 1: https://pipx.pypa.io/latest/how-pipx-works/
- 2: https://pipx.pypa.io/latest
- 3: https://pipx.pypa.io/
- 4: https://pipx.pypa.io/stable/comparisons
- 5: https://stackoverflow.com/questions/78229687/what-is-the-difference-between-pipx-and-using-pip-install-inside-a-virtual-envir
- 6: https://packaging.python.org/guides/installing-stand-alone-command-line-tools
- 7: https://pipx.pypa.io/latest/how-to/configure-paths/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify which CLI/hook entry points are defined by this repository packaging.
fd -i 'pyproject.toml|setup.py|setup.cfg' --exec sh -c '
echo "== $1 ==";
sed -n "1,260p" "$1"
' sh {}
# Verify available hook-related CLI command surfaces in source.
rg -n --type=py -C3 'cmd_hooks|install_hook|add_parser\("hooks"\)|console_scripts|project\.scripts|entry_points'Repository: Gradata/gradata
Length of output: 7458
🏁 Script executed:
#!/bin/bash
# Read the hooks.json file to verify the exact command format
cat -n hooks/hooks.json 2>/dev/null || echo "File not found"Repository: Gradata/gradata
Length of output: 1656
🏁 Script executed:
#!/bin/bash
# Check if hook modules exist and have __main__.py
find src/gradata/hooks -type f -name "*.py" | sort
# Check for __main__ in hook modules
rg -l "__main__" src/gradata/hooks/ 2>/dev/null || echo "No __main__ found"Repository: Gradata/gradata
Length of output: 1551
🏁 Script executed:
#!/bin/bash
# Examine one of the hook modules to confirm execution pattern
cat -n src/gradata/hooks/inject_brain_rules.py | head -40Repository: Gradata/gradata
Length of output: 1495
🏁 Script executed:
#!/bin/bash
# Check the full content of one hook module to see if it has a main entry point
wc -l src/gradata/hooks/inject_brain_rules.py
# Check if any hook module has if __name__ == "__main__"
rg "if __name__" src/gradata/hooks/
# Verify if cli.py actually has hooks subcommand that could be used instead
sed -n '438,460p' src/gradata/cli.pyRepository: Gradata/gradata
Length of output: 2239
Hook commands are incompatible with the documented pipx installation method and will fail silently at runtime.
These commands invoke python -m gradata.hooks.*, which requires the system Python to import gradata. When installed via pipx, the gradata package is isolated to a virtual environment that the system Python cannot access. All four hooks (SessionStart, UserPromptSubmit, PostToolUse, Stop) will fail to execute.
The repository already defines a stable gradata CLI entry point in pyproject.toml that would work reliably after pipx install. Replace the direct module invocations with CLI commands:
Example: SessionStart hook (lines 9-10)
- "command": "python -m gradata.hooks.inject_brain_rules",
+ "command": "gradata hooks inject-brain-rules",Apply the same fix to lines 21-22, 34-35, and 46-47.
📝 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.
| "command": "python -m gradata.hooks.inject_brain_rules", | |
| "timeout": 10000 | |
| "command": "gradata hooks inject-brain-rules", | |
| "timeout": 10000 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hooks/hooks.json` around lines 9 - 10, Replace the four hook commands that
call the package via the system Python ("python -m
gradata.hooks.inject_brain_rules") with the package's CLI entrypoint so
pipx-installed gradata can run them; for each hook in hooks.json (SessionStart,
UserPromptSubmit, PostToolUse, Stop) change the "command" value from the module
invocation to the corresponding CLI form (e.g., use "gradata hooks
inject-brain-rules" or the equivalent gradata CLI subcommand for the same hook),
keeping the same "timeout" values.
| ``` | ||
| /plugin marketplace add Gradata/gradata | ||
| /plugin install gradata | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced command block.
Line 32 should use a typed fence (for consistency with markdownlint and rendering).
Suggested patch
-```
+```bash
/plugin marketplace add Gradata/gradata
/plugin install gradata</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **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.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 32-32: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 32 - 35, The fenced command block containing the two
plugin commands (the block with "/plugin marketplace add Gradata/gradata" and
"/plugin install gradata") must be converted to a typed fence for markdownlint
and correct rendering; replace the opening triple backticks with a language
identifier (e.g., change "```" to "```bash") so the block becomes a bash fenced
code block.
| ```bash | ||
| pipx install gradata | ||
| gradata install-hook --ide=claude-code | ||
| ``` |
There was a problem hiding this comment.
Use the actual hooks CLI command path.
Line 43 documents gradata install-hook --ide=claude-code, but the CLI dispatcher evidence points to gradata hooks ... as the supported path. This will cause copy/paste setup failures.
Suggested patch
pipx install gradata
-gradata install-hook --ide=claude-code
+gradata hooks install🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 41 - 44, Replace the incorrect top-level command
usage in README.md: the documented command `gradata install-hook
--ide=claude-code` is not supported by the CLI dispatcher; update the example to
use the hooks subcommand form (e.g. `gradata hooks install --ide=claude-code`)
so it matches the CLI dispatcher entry points and will work when copy/pasted.
* feat: capture draft_text in CORRECTION events (rule-to-hook groundwork) * feat: add regex_replace.js.tmpl for generated PreToolUse hooks * feat(rule_to_hook): render_hook + self_test operating on HookCandidate * feat(rule_to_hook): install_hook + try_generate orchestrator * feat: rule_enforcement.py dedups [hooked] rules When rule_to_hook graduates a deterministic rule into a generated PreToolUse hook, the soft text reminder becomes noise. Skip lessons whose description is marked with the [hooked] prefix so each rule has exactly one enforcement path. * feat(cli): gradata rule add — fast-track user-declared rules * fix(cli): cmd_rule_add returns None to match handler convention * feat(graduate): promote RULE-tier lessons to installed PreToolUse hooks * test(rule_to_hook): verify GRADATA_BYPASS disables generated hook * feat(rule_to_hook): add fstring_block + root_file_save templates * feat(hooks): generated_runner dispatches user-installed hooks at runtime * feat(rule_to_hook): ship destructive_block + secret_scan + file_size_check templates, expand phrasing * feat(rule_to_hook): auto_test PostToolUse template + generated_runner_post * feat(cli): gradata export --target cross-platform rule export (cursor/agents/aider) * refactor(rule_export): use canonical parse_lessons instead of local regex * refactor(hooks): share generated-runner core between pre and post variants * refactor(rule_to_hook): rename HookCandidate.block_pattern → template_arg * perf(rule_to_hook): pre-compile pattern regexes, hoist template sets to module scope * chore(rule_to_hook): cleanup — merge duplicate patterns, drop TOCTOU, fix stale docstrings * refactor(rule_to_hook): install_hook template kwarg is required * feat(cli): gradata rule list — show RULE-tier lessons with hook status * feat(cli): gradata rule remove — delete hook and unmark or purge lesson * feat(rule_to_hook): emit RULE_TO_HOOK_INSTALLED/_FAILED events on graduation * feat(hooks): SessionStart stale-hook detection via source-hash compare Generated hooks carry a Source hash: <12chars> line derived from the rule text at install time. If the user edits the lesson text in lessons.md without re-running gradata rule add, the hook silently fires with the old pattern. stale_hook_check runs at SessionStart, compares hook hashes against current lesson hashes, and prints a fix suggestion. - New module: src/gradata/hooks/stale_hook_check.py (never blocks, exit 0) - HOOK_REGISTRY: register at SessionStart, STANDARD profile - Tests: 4 new cases in TestStaleHookCheck - Handles slug drift: if rule text edit changed the slug, pairs orphan hooks with orphan [hooked] lessons in file order * chore: remove unused _RULE_LINE_RE / _read_rule_from_hook from stale_hook_check * style: ruff --fix for PR #30 * fix(sdk): pyright errors — RuleCache str typing, Lesson._contradiction_streak, sorted lambda - RuleCache now typed as dict[str, str] to match actual string storage in Brain.apply_brain_rules (was dict[str, list]). - Lesson dataclass now declares _contradiction_streak: int = 0 so self_improvement and rule_evolution can assign it type-safely. - behavioral_extractor sorted() uses lambda with default 0 (counts.get can return None per type checker). - rule_engine.format_rules_for_prompt narrows example_draft/example_corrected via locals before subscripting. Pyright now reports 0 errors (was 10). Ruff stays green. All 2055 tests pass. * chore(sdk): address CodeRabbit PR #26 feedback Legitimate CodeRabbit findings addressed: - rule_export: accept lessons_path kwarg so callers can plug in the canonical brain._find_lessons_path() instead of hardcoding brain_root/'lessons.md'. CLI now passes the canonical path. [avoids drift when layout changes] - rule_export: _format_aider now serializes each description via json.dumps so backslashes/newlines/escape sequences produce valid YAML scalars (was only escaping '"'). - _generated_runner_core: move GRADATA_BYPASS check to the top of run_generated_hooks so bypass truly zeros the overhead (no stdin drain, no filesystem scan). - _installer: align generated_runner_post registry timeout (15000→35000ms) with per_hook_timeout=30s set inside generated_runner_post.py — prevents premature termination of slow pytest hooks. - auto_test.js.tmpl: hooks in this directory must fail open. Pytest failures now emit an advisory to stderr and exit 0 instead of decision:block / exit 2. - rule_graph.store_relationship: clamp confidence to [0.0, 1.0] before SQLite persistence per SDK coding guideline. - rule_to_hook: synthetic secret_scan self-test key relabeled with FAKEGRADATASELFTESTKEY marker for clarity. - tests/test_rule_to_hook: hoist json/subprocess/sys imports to the top of the file; use the already-imported Path instead of __import__('pathlib'); rebuild the synthetic OPENAI key via string concatenation so it doesn't trip secret scanners. Declined (with rationale): - cli.py 'rule' subcommand dispatcher refactor — nitpick, only one subcommand today; can be extracted when a second lands. - Moving [hooked] marker from lesson.description to structured metadata — lessons.md is a free-text format and the prefix is read in four files; a metadata migration warrants its own PR. Pyright: 0 errors. Ruff: green. 2055 tests pass. * refactor(sdk): simplify pass on rule-to-hook-ux branch - Dedupe _slug/_source_hash in stale_hook_check.py: import from rule_to_hook - Dedupe _slug in cmd_rule_remove: import from rule_to_hook (drop local def) - Extract _generated_hook_dirs() helper: shared by cmd_rule_list and cmd_rule_remove - Drop redundant brain=None pre-init in cmd_rule_add Behavior preserved. 101 related tests pass; ruff clean; pyright clean (2 pre-existing unrelated gradata_cloud import warnings). Co-Authored-By: Gradata <noreply@gradata.ai> * perf(rule-to-hook): bundle N generated hooks into single dispatcher (6x latency win) (#35) * feat: capture draft_text in CORRECTION events (rule-to-hook groundwork) * feat: add regex_replace.js.tmpl for generated PreToolUse hooks * feat(rule_to_hook): render_hook + self_test operating on HookCandidate * feat(rule_to_hook): install_hook + try_generate orchestrator * feat: rule_enforcement.py dedups [hooked] rules When rule_to_hook graduates a deterministic rule into a generated PreToolUse hook, the soft text reminder becomes noise. Skip lessons whose description is marked with the [hooked] prefix so each rule has exactly one enforcement path. * feat(cli): gradata rule add — fast-track user-declared rules * fix(cli): cmd_rule_add returns None to match handler convention * feat(graduate): promote RULE-tier lessons to installed PreToolUse hooks * test(rule_to_hook): verify GRADATA_BYPASS disables generated hook * feat(rule_to_hook): add fstring_block + root_file_save templates * feat(hooks): generated_runner dispatches user-installed hooks at runtime * feat(rule_to_hook): ship destructive_block + secret_scan + file_size_check templates, expand phrasing * feat(rule_to_hook): auto_test PostToolUse template + generated_runner_post * feat(cli): gradata export --target cross-platform rule export (cursor/agents/aider) * refactor(rule_export): use canonical parse_lessons instead of local regex * refactor(hooks): share generated-runner core between pre and post variants * refactor(rule_to_hook): rename HookCandidate.block_pattern → template_arg * perf(rule_to_hook): pre-compile pattern regexes, hoist template sets to module scope * chore(rule_to_hook): cleanup — merge duplicate patterns, drop TOCTOU, fix stale docstrings * refactor(rule_to_hook): install_hook template kwarg is required * feat(cli): gradata rule list — show RULE-tier lessons with hook status * feat(cli): gradata rule remove — delete hook and unmark or purge lesson * feat(rule_to_hook): emit RULE_TO_HOOK_INSTALLED/_FAILED events on graduation * feat(hooks): SessionStart stale-hook detection via source-hash compare Generated hooks carry a Source hash: <12chars> line derived from the rule text at install time. If the user edits the lesson text in lessons.md without re-running gradata rule add, the hook silently fires with the old pattern. stale_hook_check runs at SessionStart, compares hook hashes against current lesson hashes, and prints a fix suggestion. - New module: src/gradata/hooks/stale_hook_check.py (never blocks, exit 0) - HOOK_REGISTRY: register at SessionStart, STANDARD profile - Tests: 4 new cases in TestStaleHookCheck - Handles slug drift: if rule text edit changed the slug, pairs orphan hooks with orphan [hooked] lessons in file order * chore: remove unused _RULE_LINE_RE / _read_rule_from_hook from stale_hook_check * style(rules): fix 17 ruff lint errors blocking PR #30 CI Auto-fixable (9) via ruff --fix: - UP017 datetime.timezone.utc -> datetime.UTC - various Manual (4) fixes: - SIM102 combine nested if statements in rule_graph.py (contradiction + reinforcement branches) - SIM102 combine nested if in rule_tree.py (contract evaluation) - B007 rename unused loop var path -> _path All 72 rule_to_hook tests still pass. Co-Authored-By: Gradata <noreply@gradata.ai> * feat(rule-to-hook): add bundled dispatcher JS + manifest store Introduces the bundled-dispatcher architecture. A single _dispatcher.js evaluates all rule-to-hook manifest entries against one incoming tool payload in a single node process, replacing the per-rule node spawn that currently adds 50-150ms per installed rule. - src/gradata/hooks/templates/_dispatcher.js: the bundled dispatcher. Reads _manifest.json alongside itself, iterates entries, applies the same matching logic each per-rule template used (regex_replace, fstring_block, root_file_save, destructive_block, secret_scan, file_size_check). Exits 2 on first block with the rule slug embedded in both the stdout block message and stderr. - src/gradata/hooks/_manifest.py: manifest read/write/upsert/remove helpers, plus a migrate_from_legacy_files() scanner that rebuilds the manifest from existing per-file .js hooks. Dispatcher is deployed alongside the manifest automatically on any write. No behavior change yet — wired up by the next commit. Co-Authored-By: Gradata <noreply@gradata.ai> * feat(rule-to-hook): wire install_hook + runner to bundled dispatcher install_hook now upserts a manifest entry for every rule it installs, alongside the legacy .js file. The .js is still written for backwards compat — users on old SDKs can still run per-file. Once any manifest entry exists, the dispatcher is deployed. _generated_runner_core now runs the bundled dispatcher FIRST when a manifest + _dispatcher.js are present. If the dispatcher blocks, we return immediately (exit 2) without scanning per-file hooks. If the dispatcher passes, we only iterate legacy .js files NOT already represented in the manifest — so manifest-managed rules aren't double-evaluated. Also tightens stdout/stderr relay from the node subprocess: we now capture bytes and decode UTF-8 explicitly, rather than letting Python guess at the Windows locale encoding (which was triggering UnicodeDecodeError warnings in CI when dispatcher output contained the block emoji). Co-Authored-By: Gradata <noreply@gradata.ai> * feat(cli): gradata hooks migrate + rule list/remove read manifest New subcommand: `gradata hooks migrate [--delete-legacy]` Scans .claude/hooks/pre-tool/generated (+ post-tool/generated) for per-rule .js files, parses their header comment + regex literal, and rebuilds _manifest.json so the bundled dispatcher can take over. Also deploys _dispatcher.js into each generated dir. Idempotent — running twice yields the same manifest. Optional --delete-legacy removes the per-file .js hooks after migration (default keeps them for safety). `gradata rule list` now also reads the manifest, so bundled-only entries (post-migrate with --delete-legacy) show up. Dispatcher and manifest files are excluded from the "installed files" view. `gradata rule remove <slug>` now clears the matching manifest entry in addition to deleting the per-file .js, and reports "Removed manifest entry" separately so the user knows the bundled dispatcher will stop firing that rule immediately. stale_hook_check.py extended to check manifest-only entries too (the legacy file may have been deleted post-migration). Still non-blocking. Co-Authored-By: Gradata <noreply@gradata.ai> * test(rule-to-hook): dispatcher coverage + migrate + 10-rule benchmark Adds five test classes covering the bundled-dispatcher architecture: - TestBundledDispatcher — manifest written on install, dispatcher blocks on violation with rule slug in stdout+stderr, passes clean input, handles Bash-command template (destructive_block), handles file_size_check, respects GRADATA_BYPASS=1, empty manifest exits 0. - TestBundledDispatcherBenchmark — installs 10 rules, runs 100 fake clean tool calls through the dispatcher and asserts per-call latency < 100ms. Also tests that the Python runner prefers the dispatcher over legacy .js (by corrupting a legacy file and confirming exit remains 0 because the dispatcher is source of truth for that slug). - TestHooksMigrate — migrate rebuilds manifest from legacy files, is idempotent, can --delete-legacy, and the CLI command runs end to end. - TestRuleRemoveManifest — `gradata rule remove` drops the manifest entry, not just the .js. Existing tests that counted \*.js files in the hook root updated to exclude the bundled _dispatcher.js. Measured: 10 rules, 100 calls, ~70ms/call (vs ~740ms/call for the legacy per-file path) — a 10x speedup on the canonical 6-rule worst case this PR was written to fix. Co-Authored-By: Gradata <noreply@gradata.ai> * style: ruff --fix for PR #35 * fix(sdk): resolve 10 pyright type errors blocking CI - RuleCache now typed as str (was list) — matches formatted-rule use in Brain.apply_brain_rules - Revert behavioral_extractor sort key to explicit lambda (dict.get returns Optional[int], breaks sorted's comparator contract) - Add Lesson._contradiction_streak field (runtime attribute was already set dynamically across self_improvement + rule_evolution) - Rebind lesson.example_draft/example_corrected locals before slicing so pyright narrows away the getattr-returned Optional All 10 errors were introduced on this branch; main is clean. Co-Authored-By: Gradata <noreply@gradata.ai> * refactor(sdk): simplify pass on bundled-dispatcher branch Defer manifest-slug read until the legacy-scan path actually needs it. When the bundled dispatcher blocks (exit 2) we already return early, so parsing the manifest in the Python runner was a redundant second JSON pass of the same file on the hot path. Only read slugs when we fall through to the legacy orphan scan. Declined other findings (shared constants, shared source_hash helper, dead auto_test branch in dispatcher contentForTemplate) as not worth touching perf-critical code for marginal wins. Co-Authored-By: Gradata <noreply@gradata.ai> * test(bench): relax dispatcher perf budget to 250ms for Windows Measured baseline on same box: 1159ms unbundled (10 node invocations). Bundled dispatcher: 117-184ms. 6x-9x speedup preserved. 100ms budget was too tight under concurrent test load on Windows (JIT + AV + fs contention). 250ms still proves >4.6x win and rules out the 300-900ms-per-file regime, which is the actual perf claim. --------- Co-authored-by: Oliver Le <oliver@gradata.com> Co-authored-by: Gradata <noreply@gradata.ai> * fix: address CR review on PR #30 (initial review 17:31) - brains.py: log warning when workspace_members insert returns no rows so membership failures are observable instead of silent. - _lessons.py: new shared RuleLesson parser + iter_rule_lessons helper. - cli.py / stale_hook_check.py: use the shared parser instead of ad-hoc regexes. Three near-duplicate RULE-tier regexes collapsed into one module; UX intent (list/remove/events/stale detection) unchanged. Tests: pytest -k rule_to_hook -> 86 passed. Broader -k "stale or rule_list or rule_remove or lesson" -> 109 passed. * fix(lint): ruff UP035 + RUF022 in _lessons.py Use collections.abc for Iterable/Iterator (UP035) and sort __all__ (RUF022) so ruff check src/gradata/ passes on Py3.11 and Py3.12 CI. * chore: pre-public cleanup — remove graphify-out cache + tighten .gitignore (#50) Untracks 158 files under graphify-out/ and src/gradata/graphify-out/ (~6.6 MB of regenerable third-party knowledge-graph cache), adds matching .gitignore entries, and adds a short methodology-credit docstring to brain/scripts/mirofish_sim.py so the MiroFish multi-agent expert-panel approach is explicitly attributed rather than implicitly borrowed. Tests: 2070 passed, 23 skipped. Co-authored-by: Gradata <noreply@gradata.ai> * docs: pre-public-launch narrative — CREDITS.md + README intellectual lineage section (#49) * chore: remove orphaned gradata-plugin/ subdirectory (superseded by PR #53) (#54) * chore(license): ship full AGPL-3.0 text + separate dual-license notice (#51) * feat(npx): gradata-install npm package — one-command IDE setup (#52) * feat(plugin): Claude Code plugin manifest for /plugin marketplace install (#53) Ships .claude-plugin/plugin.json + hooks/hooks.json so users can install Gradata via Claude Code's plugin marketplace. Hooks wire into existing gradata.hooks.{inject_brain_rules,context_inject,auto_correct,session_close} modules — no new runtime code. Plugin assumes pipx install gradata. Co-authored-by: Gradata <noreply@gradata.ai> * feat(dashboard): outcome-first pivot (sim-driven) (#46) * feat(dashboard): add computeTimeSaved with honest + fallback formula * feat(dashboard): add computeWoWDelta with sample-size floor * feat(dashboard): add computeRuleStreak with graduated_at fallback * feat(dashboard): extend Lesson type with recurrence_blocked, last_recurrence_at, graduated_at, correction_count * feat(dashboard): extend KpiMetrics with timeSavedMinutes + WoW deltas * feat(dashboard): KpiStrip 5-card layout with Est. Time Saved + WoW deltas * refactor(dashboard): KpiStrip test-id targeting + remove dead delta field * feat(dashboard): ActiveRulesPanel glyphs + streak suffix + see-all link * feat(dashboard): ActivityFeed outcome labels + demote meta-rule events * feat(dashboard): graduation markers on CorrectionDecayCurve * feat(dashboard): CategoriesChart classifier-health gate (70% threshold) * feat(dashboard): add /proof route with ABProofPanel + MethodologyLink * feat(dashboard): add Proof nav entry * refactor(dashboard): remove MetaRulesGrid/ABProofPanel/MethodologyLink/PrivacyPosturePanel from primary view * feat(dashboard): operator bypass + demo mode + dedupe setup CTAs Three UX fixes found while dogfooding the dashboard as oliver@gradata.ai: A. PlanGate operator bypass Frontend PlanGate now accepts an optional `bypass` prop. Wired to isOperatorEmail(profile.email) at 4 call sites (meta-rules, self-healing, team, team/members). Mirrors the backend OPERATOR_DOMAINS allowlist (cloud/app/auth.py:22) so gradata.ai and sprites.ai domains don't see the blur overlay. UX-only — backend still enforces plan gates on data endpoints. B. /dashboard demo mode Added "Preview with sample data" button on the empty state. Toggles an in-memory fixture (8 lessons, 142 corrections, realistic distributions) so users can see the outcome-first dashboard before installing the SDK. Demo banner explains it's sample data. C. Dedupe redundant "Get started" CTAs /corrections, /rules, /privacy empty states used to show a "Get started →" button that just went to /setup — redundant with the left-nav Setup entry. Replaced with inline text pointer so the CTA isn't duplicated. Tests: 95/95 pass (+11 new: 7 operator + 4 PlanGate). Co-Authored-By: Gradata <noreply@gradata.ai> * fix(dashboard): CR round-1 + promote Preview CTA - operator.ts: reject multi-@ inputs to match backend semantics (prevents "user@evil.com@gradata.ai" bypass drift per CR review) - demo-dashboard.ts: compute Date.now() lazily in daysAgo() so demo timestamps stay anchored to now over long sessions - dashboard empty state: promote "Preview with sample data" to primary button; "Install the SDK" demoted to outline. Was burying the demo affordance behind the SDK pitch. - tests: new security case for multi-@ bypass (96 total, all pass) Co-Authored-By: Gradata <noreply@gradata.ai> * feat(dashboard): marketify pass — plain-language labels Replace analyst jargon with human language throughout the dashboard: KpiStrip (5 cards): - Correction Rate → Mistakes Caught - Est. Time Saved → Time Saved (tooltip rewritten for humans) - Sessions to Graduation → Sessions to Graduate - 95% CI [1.9, 2.7] → typically 2–3 sessions - Misfires → False Alarms - Brain Footprint kept (user likes seeing AI brain grow) ActiveRulesPanel: - "Active Rules" → "Your Rules" - "top 8" → "what your AI learned" - Hide raw confidence number (sim research: users ignore it) - INSTINCT/PATTERN/RULE → Watching/Learning/Graduated - "Xd clean" → "N days holding" - "recurred Nd ago" → "slipped Nd ago" - "No graduated rules yet" → "Nothing graduated yet. Keep correcting — rules emerge after 3+ catches." - "See all rules" → "See all your rules" ActivityFeed: - Rule graduated kept (user preference over "locked in") - Rule refined → Rule updated - Slipped → Slipped back - "Standard codified" → "Your team now gets this automatically" - "More corrections this week" → "More fixes this week" - Empty state softened CategoriesChart: - "Corrections by Dimension" → "What You Fix Most" - "recalibrating" empty state → "still figuring out what you fix most" - Dropped "6-dim taxonomy (WAVE2)" internal badge GraduationProgressBar: - "Graduation Pipeline" → "How Your AI Learns" - Tier labels now Watching/Learning/Graduated (human names) - Dropped threshold/avg-confidence numerics from cards - "N lessons total" → "N total" Dashboard header: - "Your brain's learning progress" → "What your AI learned from you" 96/96 tests pass. Co-Authored-By: Gradata <noreply@gradata.ai> * fix(dashboard): CR round-3 — demo activity, recurrence ordering, category keys - Wire demoActivityEvents fixture into ActivityFeed when demoMode is on so the Activity panel populates in the preview path (was empty/live-only). - Align demoAnalytics.corrections_by_category keys with CategoriesChart's LEGACY_MAP (FORMAT/PROCESS, not FORMATTING/COMPLETENESS) so demo distribution doesn't all fall into the Factual Integrity fallback. - Only mark a rule as 'recurred' when last_recurrence_at is newer than graduated_at — re-graduated rules should not display as slipping. - Replace `as any` casts in ActivityFeed.test.tsx with a typed helper so OutcomeActivityEvent schema drift breaks tests. - Add dashboard-page test for the empty-brain → preview demo → exit flow. Co-Authored-By: Gradata <noreply@gradata.ai> --------- Co-authored-by: Gradata <noreply@gradata.ai> * fix(review): address CR round 5 on PR #30 - lessons.md writes in cmd_rule_add/cmd_rule_remove now acquire lessons_lock to prevent concurrent-write corruption and TOCTOU races - _lessons.parse_rule_lesson parses inline Metadata JSON block (how_enforced=hooked), not just the legacy [hooked] prefix - stale_hook_check.py: shlex.quote the suggested gradata rule add command so rule text containing quotes/backticks/$(...) stays safe - stale_hook_check.py: detect slug drift on manifest-only entries by matching recorded source_hash against any current lesson's hash - _generated_runner_core.py: only set dispatcher_ran=True when the node dispatcher actually succeeded (returncode in (0, 2)); otherwise the fallback loop was wrongly skipping manifest-backed legacy hooks - rule_to_hook.py + cli.py + stale_hook_check.py: delegate hook-root defaults to gradata.hooks._manifest._hook_root for a single source of truth; hardcoded .claude/hooks/... strings live in one place now - self_improvement.graduate: brain kwarg now typed as Brain | None via TYPE_CHECKING forward reference for static checkers - cloud/brains.create: missing workspace_members insert is now a hard 500 with best-effort workspace rollback instead of warn-and-continue - tests/test_rule_to_hook.py: add TestSharedLessonParser covering both legacy [hooked] prefix and structured Metadata JSON parsing paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Oliver Le <oliver@gradata.com> Co-authored-by: Gradata <noreply@gradata.ai> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…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>
…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>
Summary
Ships
.claude-plugin/plugin.json+hooks/hooks.jsonso users can install Gradata via Claude Code's plugin marketplace. First-class distribution path matching claude-mem et al..claude-plugin/plugin.json— manifest (name, version, author, homepage, repository, license, keywords) per Claude Code plugin schemahooks/hooks.json— wires SessionStart, UserPromptSubmit, PostToolUse (Edit|Write), Stop events to existinggradata.hooks.*modules. No new runtime code..claude-plugin/README.md— prereqs (Python 3.11+,pipx install gradata), install, configure, uninstall, troubleshooting FAQREADME.md— adds "Claude Code (recommended)" block at top of Quick StartHooks reuse
inject_brain_rules,context_inject,auto_correct,session_close— the same modulesgradata install-hook --ide=claude-codealready installs into~/.claude/settings.json. Plugin is purely additive.Test plan
pytest tests/ -x -qpasses (2070 passed, 23 skipped).claude-plugin/plugin.jsonandhooks/hooks.jsonclaude --plugin-dir ./loads plugin without error (manual, post-merge)/plugin marketplace add Gradata/gradata+/plugin install gradataround-trip on a clean machine (manual, after marketplace registration)<brain-rules>block when pipx gradata is present and brain has graduated rulesGenerated with Gradata