You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: rewrite in Node.js for cross-platform support (v1.1.0) (#6)
* feat: rewrite in Node.js for cross-platform support (v1.1.0)
Complete rewrite of the Watchdog plugin from Bash + jq + POSIX
coreutils to Node.js. Zero shell dependencies — runs natively on
Linux, macOS, and native Windows (PowerShell / cmd) with only
`node` 18+ required. No more WSL2 requirement for Windows users,
no more jq install, no more "bash in PATH" gotcha.
## What changed
- **Three thin entry points** (`hooks/stop-hook.js`,
`scripts/setup-watchdog.js`, `scripts/stop-watchdog.js`) that all
consume shared logic from six `lib/` modules:
- `lib/constants.js` — state path pattern, marker tokens, prompt templates
- `lib/log.js` — stderr diagnostics
- `lib/stdin.js` — cross-platform sync stdin reader
- `lib/state.js` — atomic state file lifecycle
- `lib/transcript.js` — JSONL parser + current-turn tool extraction
- `lib/judge.js` — headless Haiku subprocess + verdict parser
- **738 lines** of production JavaScript replacing the combined
287 + 140 + 36 = 463 lines of bash — the extra lines buy full
modularization, cross-platform support, and a real test suite.
- **53 automated tests** using Node's built-in `node:test` runner.
No external test dependencies. The suite exercises:
- JSONL transcript parsing (real vs tool_result user turn boundaries,
tool_use extraction, malformed-line resilience)
- State file lifecycle (atomic writes, merge updates, validation,
per-session path keying, parent dir creation)
- Verdict parser (FILE_CHANGES substring trap, ambiguous, empty,
multi-token)
- End-to-end setup-watchdog.js subprocess invocations
- End-to-end stop-watchdog.js subprocess invocations
- End-to-end stop-hook.js including the owner_session_id recursion
guard, ownership claim, max iterations cap, missing transcript,
and pure-text turn branches
- **CI rewritten** to drop shellcheck (no shell scripts left) and run
`node --test` on a 3x3 matrix: `ubuntu-latest`, `macos-latest`,
`windows-latest` × Node 18 / 20 / 22. Also dropped `jq` from the
jsonlint job in favor of `node -e JSON.parse`.
- **All 7 READMEs updated** (English + zh / ja / ko / es / vi / pt)
via 6 parallel subagent translations. New Requirements section
drops bash/jq, adds node 18+. New Install dependencies subsection
has native Windows install commands. Platform support matrix now
shows Linux / macOS / Windows all ✅ tested in CI.
- **NOTICE updated** with the Node.js rewrite as a new modification
entry and the new file list.
- **CONTRIBUTING updated** to replace shellcheck references with
`node --test` instructions and a description of the test suite.
- **plugin.json + marketplace.json bumped to 1.1.0**.
Same semantics as 1.0.x: Haiku-classifier convergence detection,
tool-call exit precondition, owner_session_id recursion guard,
per-session state keyed by TERM_SESSION_ID. The state file format
and slash command surface are unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ci,docs): use explicit file list for node --test (Node 18/20 glob)
Node's --test runner only gained glob pattern support in v21, so
on Node 18 and 20 'test/*.test.js' is treated as a literal filename
and CI fails with "Could not find 'test/*.test.js'". Fix the CI
workflow to list each test file explicitly.
Also update all 7 READMEs + CONTRIBUTING.md to show both forms —
the Node 22+ glob syntax and the Node 18/20 unquoted (shell-
expanded) form. Plus fix a stale "shellcheck → node --test"
transition comment in the Plugin Layout tree across all languages
to describe the actual CI jobs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
"description": "Watchdog — watches the agent and re-feeds the same prompt until the task genuinely stops producing file edits. Derived from Anthropic's ralph-loop, Apache 2.0.",
3
+
"name": "claude-code-watchdog-nodejs",
4
+
"description": "Watchdog (Node.js rewrite) — cross-platform self-referential Claude Code loop with zero bash/jq dependencies. Runs natively on Linux, macOS, and Windows. Apache 2.0, derived from ralph-loop.",
5
5
"owner": {
6
6
"name": "Jonyan Dunh",
7
7
"email": "jonyandunh@outlook.com"
8
8
},
9
9
"plugins": [
10
10
{
11
11
"name": "watchdog",
12
-
"description": "A self-referential loop for Claude Code that re-feeds the user's prompt until the task truly stops producing file edits. Uses a headless Haiku classifier to judge convergence, requires the agent to actually call tools before exit (no pure-text 'done' claims), and is hidden from the agent so it cannot cheat. Per-session state keyed by TERM_SESSION_ID. Apache 2.0, derived from ralph-loop.",
13
-
"version": "1.0.3",
12
+
"description": "Self-referential loop for Claude Code that re-feeds the user's prompt until the task truly stops producing file edits. Cross-platform Node.js implementation with no bash / jq dependencies. Uses a headless Haiku classifier to judge convergence, requires the agent to actually call tools before exit (no pure-text 'done' claims), and is hidden from the agent so it cannot cheat. Per-session state keyed by TERM_SESSION_ID. Apache 2.0, derived from ralph-loop.",
Copy file name to clipboardExpand all lines: .claude-plugin/plugin.json
+5-3Lines changed: 5 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
{
2
2
"name": "watchdog",
3
-
"version": "1.0.3",
4
-
"description": "Continuous self-referential AI watchdog for interactive iterative development. Runs Claude in a while-true loop with the same prompt until the task stops producing file edits.",
3
+
"version": "1.1.0",
4
+
"description": "Self-referential loop for Claude Code. Re-feeds the same prompt after every turn until files actually stop changing. Cross-platform Node.js implementation — no bash / jq dependencies, runs natively on Linux, macOS, and Windows.",
├── test/ # node:test unit + integration tests with fixtures
48
50
├── LICENSE # Apache 2.0
49
51
└── NOTICE # Attribution to ralph-loop (required by Apache 2.0 § 4)
50
52
```
51
53
52
-
The heart of the plugin is `hooks/stop-hook.sh`. Read that first before changing anything.
54
+
The heart of the plugin is `hooks/stop-hook.js`. Read that first before changing anything — it consumes everything under `lib/`.
53
55
54
56
## Before you open a PR
55
57
56
-
1.**Shell scripts must pass `shellcheck`.** CI runs it on every `.sh` file. Install locally (`apt install shellcheck`, `brew install shellcheck`) and fix warnings before pushing.
57
-
2.**Line endings must be LF.** The `.gitattributes` file enforces this — do not override.
58
-
3.**Do not skip the `NOTICE` file when making derivative changes.** If your change affects how we differ from `ralph-loop`, update the "modifications" list in `NOTICE`.
59
-
4.**Do not commit debug logging.** Remove any `WATCHDOG_DEBUG_LOG=...` scaffolding before the PR.
60
-
5.**One logical change per PR.** Mixing a bugfix with a refactor makes review harder.
58
+
1.**All JavaScript must parse under `node --check`.** CI runs it on every `.js` file. Broken syntax will fail the build.
59
+
2.**All tests must pass.** Run `node --test 'test/*.test.js'` locally before pushing. CI runs the full suite on `ubuntu-latest`, `macos-latest`, and `windows-latest` across Node 18 / 20 / 22 — any regression on any matrix entry blocks merge.
60
+
3.**Line endings must be LF.** The `.gitattributes` file enforces this — do not override.
61
+
4.**Do not skip the `NOTICE` file when making derivative changes.** If your change affects how we differ from `ralph-loop`, update the "modifications" list in `NOTICE`.
62
+
5.**Do not commit debug logging.** Remove any `console.log` scaffolding before the PR — `lib/log.js` is the only sanctioned stderr output surface.
63
+
6.**One logical change per PR.** Mixing a bugfix with a refactor makes review harder.
61
64
62
65
## Commit messages
63
66
@@ -78,15 +81,40 @@ Common types:
78
81
79
82
## Testing your change
80
83
81
-
There is no automated test suite (the project is small and the interesting behavior requires a live Claude Code session). Manual verification steps:
84
+
Watchdog ships with 53 automated tests using Node's built-in `node:test` runner — no external test dependencies. Run them from the repo root:
85
+
86
+
```bash
87
+
# Node 22+: glob pattern
88
+
node --test 'test/*.test.js'
89
+
90
+
# Node 18/20: shell expansion (quotes removed) or explicit file list
91
+
node --test test/*.test.js
92
+
```
93
+
94
+
Target an individual file:
95
+
96
+
```bash
97
+
node --test test/transcript.test.js
98
+
```
99
+
100
+
The suite covers:
101
+
102
+
-**`test/transcript.test.js`** — JSONL parser, real-vs-tool_result user turn detection, tool_use extraction
-**`test/setup.test.js`** — E2E subprocess tests for `scripts/setup-watchdog.js`
106
+
-**`test/stop-watchdog.test.js`** — E2E subprocess tests for `scripts/stop-watchdog.js`
107
+
-**`test/stop-hook.test.js`** — E2E subprocess tests for `hooks/stop-hook.js` including the recursion guard, ownership claim, max iterations cap, missing transcript, and pure-text turn branches
108
+
109
+
In addition to the unit/integration suite, you should **also** manually verify your change in a live Claude Code session:
82
110
83
111
1. Clear any stale state: `rm -f .claude/watchdog.*.local.json`
84
112
2. Load the plugin locally (see Quick start above)
85
113
3. Run a small iterative task:
86
114
```
87
115
/watchdog:start "Create tmp/test.md with 'hello world'" --max-iterations 5
88
116
```
89
-
4. Verify the loop converges (file created, Haiku judges NO_FILE_CHANGES on the next turn, loop exits)
117
+
4. Verify the loop converges (file created, Haiku judges `NO_FILE_CHANGES` on the next turn, loop exits)
90
118
5. Verify `/watchdog:stop` cleanly removes the state file
0 commit comments