diff --git a/.devkit.yml b/.devkit.yml
index 43ad064..4a1f4ce 100644
--- a/.devkit.yml
+++ b/.devkit.yml
@@ -1,4 +1,4 @@
-plugin_dir: usr/plugins/diff_visualizer
+plugin_dir: .
display_name: DiffVisualizer
# If the plugin has agent-driven behaviour needing an e2e seam, enable it here:
# e2e_pod_env:
diff --git a/.github/workflows/plugin-e2e.yml b/.github/workflows/plugin-e2e.yml
index 587c7c9..27851ab 100644
--- a/.github/workflows/plugin-e2e.yml
+++ b/.github/workflows/plugin-e2e.yml
@@ -26,6 +26,10 @@ name: plugin-e2e
on:
pull_request:
branches: ["main", "master"]
+ # push on main: proves the shipped code, and gives the README badge a status
+ # (PR-only runs leave branch=main with zero runs โ "no status").
+ push:
+ branches: ["main", "master"]
workflow_dispatch:
inputs:
capture_all_traces:
@@ -35,8 +39,8 @@ on:
permissions:
contents: read
- pull-requests: write # merge-guard drafts a PR whose plugin-e2e is red
- actions: write # DEC-074 count-based artifact retention: delete superseded e2e artifacts
+ pull-requests: write # merge-guard drafts a PR whose plugin-e2e is red
+ actions: write # DEC-074 count-based artifact retention: delete superseded e2e artifacts
jobs:
e2e:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4d51cb5..319869e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,10 +9,10 @@ Thanks for helping improve visual diffs in Agent Zero chat.
`docs/spec/behaviour-spec.md` (BEH-n) and lands with a traceable `.feature` scenario in
`tests/e2e/features/` plus step definitions in `tests/e2e/steps/`.
- **Red-first**: write the failing test before the fix. The e2e harness runs a seam-off
- *red-proof* โ scenarios that pass without the plugin installed fail the gate.
+ _red-proof_ โ scenarios that pass without the plugin installed fail the gate.
- **End-state assertions only** in e2e specs (rendered containers, overlay counts) โ
never transient markers like `data-diff-processed`.
-- **Versioning**: bump `version` in BOTH `usr/plugins/diff_visualizer/plugin.yaml` and
+- **Versioning**: bump `version` in BOTH `plugin.yaml` and
`meta.yaml` (CI checks they match) and in `pyproject.toml`.
## Dev setup
@@ -20,24 +20,24 @@ Thanks for helping improve visual diffs in Agent Zero chat.
```bash
git clone https://github.com/agent-zero-plugins/agent-zero-plugin-diff-visualizer
cd agent-zero-plugin-diff-visualizer
-git submodule update --init --recursive # devkit (private) + nested .agent-zero
+git submodule update --init --recursive # devkit + nested .agent-zero
```
## Verify loop (fastest first)
-| Step | Command | Gate |
-|---|---|---|
-| 1 | `make verify` | bdd-lint: feature-purity, honesty, traceability |
-| 2 | `/opt/venv/bin/python -m pytest tests/ -v` | L1 component suite, 0 skipped |
-| 3 | `make package` | builds `dist/diff_visualizer.zip` |
-| 4 | `make e2e` | seam-off red-proof + full BDD run in the nested-A0 harness |
+| Step | Command | Gate |
+| ---- | ------------------------------------------ | ---------------------------------------------------------- |
+| 1 | `make verify` | bdd-lint: feature-purity, honesty, traceability |
+| 2 | `/opt/venv/bin/python -m pytest tests/ -v` | L1 component suite, 0 skipped |
+| 3 | `make package` | builds `dist/diff_visualizer.zip` |
+| 4 | `make e2e` | seam-off red-proof + full BDD run in the nested-A0 harness |
A pre-commit hook (installed by the devkit adopter) runs bdd-lint on every commit.
## Renderer changes
The entire UI lives in one file:
-`usr/plugins/diff_visualizer/extensions/webui/sidebar-end/diff-renderer.html`.
+`extensions/webui/sidebar-end/diff-renderer.html`.
Keep the graceful-fallback invariants: never swap a readable block unless diff2html
produced at least one parsed file (`d2h-file-wrapper`), and always clear the
`data-diff-processed` marker on failure so the block stays retry-able.
diff --git a/Makefile b/Makefile
index 5c23e4d..2190acc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,3 @@
-PLUGIN_DIR := usr/plugins/diff_visualizer
+PLUGIN_DIR := .
PLUGIN_DISPLAY_NAME := DiffVisualizer
-include tests/_testkit/e2e/Makefile.devkit
diff --git a/README.md b/README.md
index 325fe6f..b6717d9 100644
--- a/README.md
+++ b/README.md
@@ -15,52 +15,41 @@ whenever it proposes edits, shows uncommitted changes, or compares before/after.
Reviewing a proposed change in prose is slow; reviewing it as a rendered diff is
instant. That's the whole plugin.
-## What it looks like
+## ๐ธ What it looks like
Inline render in chat (side-by-side, per-file):
-
+
+
+
Maximized fullscreen review (Esc / backdrop / โ to close):
-
+
+
+
-## Features
+## โจ Features
-| Feature | Detail |
-|---|---|
-| Auto-render | Any ` ```diff ` fence in chat becomes a visual diff (MutationObserver, debounced) |
-| Side-by-side view | diff2html `side-by-side` output, line-matched |
-| Multi-file fences | One fence containing several `diff --git` sections renders per-file |
-| Maximize | Fullscreen overlay re-render at larger type; single-overlay invariant |
-| Copy raw source | Copies the original diff text (never the rendered HTML) |
+| Feature | Detail |
+| ----------------- | -------------------------------------------------------------------------------------------------------- |
+| Auto-render | Any ` ```diff ` fence in chat becomes a visual diff (MutationObserver, debounced) |
+| Side-by-side view | diff2html `side-by-side` output, line-matched |
+| Multi-file fences | One fence containing several `diff --git` sections renders per-file |
+| Maximize | Fullscreen overlay re-render at larger type; single-overlay invariant |
+| Copy raw source | Copies the original diff text (never the rendered HTML) |
| Graceful fallback | CDN unreachable / malformed diff / empty block โ the raw code block stays readable, nothing is swallowed |
-| Theme-aware | Consumes A0's `--color-*` custom properties with sane fallbacks |
-| Agent skill | `skills/diff/SKILL.md` โ unified-diff anatomy, capture recipes, pitfalls |
-| Behaviour nudge | System-prompt extension steering the agent toward diff output |
-
-## Architecture
-
-```mermaid
-flowchart TD
- subgraph Agent side
- N[system_prompt extension\n_15_diff_nudge.py] -->|nudges| A[Agent emits ```diff fence]
- P[prompt_fragments publisher\n_50_publish_diff_nudge.py] -->|enumerable nudge| H[non-native harnesses]
- S[skills/diff/SKILL.md] -->|syntax reference| A
- end
- subgraph Chat UI - sidebar-end extension
- A -->|markdown renders| C[pre > code.language-diff]
- O[MutationObserver + 150ms debounce] --> C
- C --> G{parsed โฅ1 file?\nd2h-file-wrapper}
- G -->|yes| R[.diff-visualizer-container\nside-by-side diff2html render]
- G -->|no / CDN down| F[raw block left readable]
- R --> M[maximize overlay]
- R --> Y[copy raw source]
- end
- D[(diff2html 3.4.51\njsDelivr CDN, lazy + memoised)] --> R
-```
+| Theme-aware | Consumes A0's `--color-*` custom properties with sane fallbacks |
+| Agent skill | `skills/diff/SKILL.md` โ unified-diff anatomy, capture recipes, pitfalls |
+| Behaviour nudge | System-prompt extension steering the agent toward diff output |
+
+## โ๏ธ Architecture
+
+
+
+
-## Usage
+## ๐ฌ Usage
Every behaviour below is covered by a BDD scenario in [`tests/e2e/features/`](tests/e2e/features/),
and [`docs/BEHAVIOUR.md`](docs/BEHAVIOUR.md) shows each one as a screenshot captured from a passing
@@ -98,10 +87,10 @@ Both are asserted scenarios rather than intentions.
---
-## Install
+## ๐ฆ Install
-**Plugin Hub (recommended):** open *Settings โ Plugins* in Agent Zero, find
-**DiffVisualizer**, click *Install*.
+**Plugin Hub (recommended):** open _Settings โ Plugins_ in Agent Zero, find
+**DiffVisualizer**, click _Install_.
**Manual zip:**
@@ -114,7 +103,7 @@ make package # โ dist/diff_visualizer.zip
Enable the plugin after installing (it is store-gated, not always-enabled).
-## Configuration
+## ๐ง Configuration
None. The plugin has no configurable options, no secrets, and no environment
variables (`default_config.yaml` is intentionally empty; `meta.yaml` declares
@@ -124,34 +113,35 @@ variables (`default_config.yaml` is intentionally empty; `meta.yaml` declares
> CDN is unreachable (offline / strict CSP), diff blocks simply stay as readable plain
> code โ no crash, no data loss.
-## Development
+## ๐ ๏ธ Development
-Repo layout follows the org-canonical devkit standard: plugin source under
-`usr/plugins/diff_visualizer/`, devkit vendored at `tests/_testkit/` (private submodule).
+Repo layout follows the org-canonical devkit standard: the repo root IS the plugin
+(root layout โ manifests, `extensions/`, `skills/`, `webui/` at top level), devkit
+vendored at `tests/_testkit/` (public submodule).
```bash
git submodule update --init --recursive # devkit + nested .agent-zero
make verify # Tier-1 static BDD gates (bdd-lint)
make package # build dist/diff_visualizer.zip
-/opt/venv/bin/python -m pytest tests/ -v # L1 component suite (7 tests)
+python -m pytest tests/ -v # L1 component suite (10 tests)
make e2e # full BDD e2e in the nested-A0 harness
```
The e2e harness boots a **nested Agent Zero** via rootless podman, installs the built
-zip, runs a seam-off *red-proof* (the suite must fail without the plugin), then runs the
+zip, runs a seam-off _red-proof_ (the suite must fail without the plugin), then runs the
BDD features in `tests/e2e/features/` (`playwright-bdd`). Specs assert **end state**
(rendered containers, overlay presence/absence), never transient markers.
Behaviour truth lives in `docs/spec/behaviour-spec.md` (BEH-1โฆ9) โ feature scenarios
trace to those IDs.
-## Tests
+## ๐งช Tests
-| Layer | Where | What |
-|---|---|---|
-| L1 component | `tests/test_diff_visualizer.py`, `tests/test_smoke.py`, `tests/test_publish_nudge.py` | surface validity, stray folders, dead hooks, manifest sanity, nudge publisher |
-| L3 BDD e2e | `tests/e2e/features/*.feature` + `tests/e2e/steps/` | render, toolbar, maximize, 3 close paths, copy, multi-file, malformed fallback, non-diff negative |
+| Layer | Where | What |
+| ------------ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
+| L1 component | `tests/test_diff_visualizer.py`, `tests/test_smoke.py`, `tests/test_publish_nudge.py` | surface validity, stray folders, dead hooks, manifest sanity, nudge publisher |
+| L3 BDD e2e | `tests/e2e/features/*.feature` + `tests/e2e/steps/` | render, toolbar, maximize, 3 close paths, copy, multi-file, malformed fallback, non-diff negative |
-## License
+## โ๏ธ License
-Apache-2.0 โ see [LICENSE](LICENSE).
\ No newline at end of file
+Apache-2.0 โ see [LICENSE](LICENSE).
diff --git a/usr/plugins/diff_visualizer/__init__.py b/__init__.py
similarity index 100%
rename from usr/plugins/diff_visualizer/__init__.py
rename to __init__.py
diff --git a/usr/plugins/diff_visualizer/default_config.yaml b/default_config.yaml
similarity index 100%
rename from usr/plugins/diff_visualizer/default_config.yaml
rename to default_config.yaml
diff --git a/docs/architecture.png b/docs/architecture.png
new file mode 100644
index 0000000..ff5cde5
Binary files /dev/null and b/docs/architecture.png differ
diff --git a/docs/diff-inline.png b/docs/diff-inline.png
index c062ae9..c856edb 100644
Binary files a/docs/diff-inline.png and b/docs/diff-inline.png differ
diff --git a/docs/diff-inline0.png b/docs/diff-inline0.png
new file mode 100644
index 0000000..c062ae9
Binary files /dev/null and b/docs/diff-inline0.png differ
diff --git a/docs/marketplace-draft.md b/docs/marketplace-draft.md
index 39f0328..7578f6d 100644
--- a/docs/marketplace-draft.md
+++ b/docs/marketplace-draft.md
@@ -34,7 +34,7 @@ screenshots:
## Thumbnail
-Copy `usr/plugins/diff_visualizer/webui/thumbnail.png` (256ร256 square, ~1.5 KB โ well
+Copy `webui/thumbnail.png` (256ร256 square, ~1.5 KB โ well
under the 20 KB limit) into the index folder as `thumbnail.png`.
## Pre-submission checklist (flip-time)
diff --git a/usr/plugins/diff_visualizer/extensions/python/_functions/helpers/prompt_fragments/get_prompt_fragments/end/_50_publish_diff_nudge.py b/extensions/python/_functions/helpers/prompt_fragments/get_prompt_fragments/end/_50_publish_diff_nudge.py
similarity index 100%
rename from usr/plugins/diff_visualizer/extensions/python/_functions/helpers/prompt_fragments/get_prompt_fragments/end/_50_publish_diff_nudge.py
rename to extensions/python/_functions/helpers/prompt_fragments/get_prompt_fragments/end/_50_publish_diff_nudge.py
diff --git a/usr/plugins/diff_visualizer/extensions/python/system_prompt/_15_diff_nudge.py b/extensions/python/system_prompt/_15_diff_nudge.py
similarity index 100%
rename from usr/plugins/diff_visualizer/extensions/python/system_prompt/_15_diff_nudge.py
rename to extensions/python/system_prompt/_15_diff_nudge.py
diff --git a/usr/plugins/diff_visualizer/extensions/webui/sidebar-end/diff-renderer.html b/extensions/webui/sidebar-end/diff-renderer.html
similarity index 100%
rename from usr/plugins/diff_visualizer/extensions/webui/sidebar-end/diff-renderer.html
rename to extensions/webui/sidebar-end/diff-renderer.html
diff --git a/usr/plugins/diff_visualizer/meta.yaml b/meta.yaml
similarity index 100%
rename from usr/plugins/diff_visualizer/meta.yaml
rename to meta.yaml
diff --git a/usr/plugins/diff_visualizer/plugin.yaml b/plugin.yaml
similarity index 100%
rename from usr/plugins/diff_visualizer/plugin.yaml
rename to plugin.yaml
diff --git a/usr/plugins/diff_visualizer/skills/diff/SKILL.md b/skills/diff/SKILL.md
similarity index 100%
rename from usr/plugins/diff_visualizer/skills/diff/SKILL.md
rename to skills/diff/SKILL.md
diff --git a/tests/_testkit b/tests/_testkit
index c77ed9b..2f3e8ca 160000
--- a/tests/_testkit
+++ b/tests/_testkit
@@ -1 +1 @@
-Subproject commit c77ed9bd7ccb675b656caffabcf5c1e7aad77c91
+Subproject commit 2f3e8ca6f5f2a69e4a247beb27c1a6912980b9ef
diff --git a/tests/conftest.py b/tests/conftest.py
index fcc5b93..6ae21bb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,20 +5,20 @@
"""
import os
+import sys
from pathlib import Path
import pytest
+# Root layout: the repo root IS the plugin dir โ stop Python from littering it
+# with __pycache__, which the static validator flags.
+sys.dont_write_bytecode = True
+
@pytest.fixture(scope="session")
def plugin_dir() -> Path:
- """Path to this plugin's source directory (canonical devkit layout)."""
- return (
- Path(__file__).resolve().parent.parent
- / "usr"
- / "plugins"
- / "diff_visualizer"
- )
+ """Path to this plugin's source directory (root layout: the repo root)."""
+ return Path(__file__).resolve().parent.parent
@pytest.fixture(scope="session")
@@ -27,12 +27,17 @@ def a0_root() -> Path | None:
The testkit validates webui surface names against A0's real surface list,
which requires the A0 source. Resolution order: ``$A0_ROOT``, then the
- conventional ``/a0`` install path. Returns ``None`` if neither is present
- so surface-validation tests can skip cleanly in a bare CI without A0.
+ devkit submodule's nested ``tests/_testkit/.agent-zero`` clone (present
+ after ``git submodule update --init --recursive``), then the conventional
+ ``/a0`` install path. Returns ``None`` if none is present so
+ surface-validation tests can skip cleanly in a bare CI without A0.
"""
env = os.environ.get("A0_ROOT")
if env and (Path(env) / "agent.py").is_file():
return Path(env)
+ vendored = Path(__file__).resolve().parent / "_testkit" / ".agent-zero"
+ if (vendored / "agent.py").is_file():
+ return vendored
default = Path("/a0")
if (default / "agent.py").is_file():
return default
diff --git a/tests/test_publish_nudge.py b/tests/test_publish_nudge.py
index 7b7dbe4..bd31aaa 100644
--- a/tests/test_publish_nudge.py
+++ b/tests/test_publish_nudge.py
@@ -14,7 +14,7 @@
_HOOK = (
pathlib.Path(__file__).resolve().parents[1]
- / "usr" / "plugins" / "diff_visualizer" / "extensions" / "python" / "_functions" / "helpers"
+ / "extensions" / "python" / "_functions" / "helpers"
/ "prompt_fragments" / "get_prompt_fragments" / "end" / "_50_publish_diff_nudge.py"
)
diff --git a/usr/plugins/diff_visualizer/webui/.gitkeep b/webui/.gitkeep
similarity index 100%
rename from usr/plugins/diff_visualizer/webui/.gitkeep
rename to webui/.gitkeep
diff --git a/usr/plugins/diff_visualizer/webui/thumbnail.png b/webui/thumbnail.png
similarity index 100%
rename from usr/plugins/diff_visualizer/webui/thumbnail.png
rename to webui/thumbnail.png