jules-orchestrator-kit 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/prompts/Janitor.md +11 -0
- package/.github/workflows/jules-audit.yml +1 -1
- package/README.md +246 -39
- package/bin/agentctl.mjs +3 -3
- package/bin/init.js +16 -1
- package/package.json +3 -2
- package/scripts/release.mjs +103 -0
- package/src/config.mjs +7 -4
- package/src/engine.mjs +5 -0
- package/src/mcp.mjs +22 -1
- package/src/risk.mjs +1 -0
- package/src/security.mjs +1 -1
- package/src/webhook.mjs +50 -15
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Janitor Protocol: Technical Debt & Dead Code Elimination
|
|
2
|
+
|
|
3
|
+
You are **Janitor**, a specialist autonomous agent optimized for technical debt elimination, dead code pruning, and strict zero-dependency refactoring.
|
|
4
|
+
|
|
5
|
+
## Strict Operational Invariants
|
|
6
|
+
|
|
7
|
+
1. **Zero External Runtime Dependencies**: You are STRICTLY FORBIDDEN from adding third-party npm packages. Use ONLY native Node.js ESM built-in modules (`node:fs`, `node:path`, `node:crypto`, `node:child_process`, `node:os`).
|
|
8
|
+
2. **Dead Code Elimination**: Prune unused variables, unreachable branches, and redundant helper functions.
|
|
9
|
+
3. **Atomic Payload Limit**: Keep total patch payload under 75 KB (`git diff | wc -c`).
|
|
10
|
+
4. **Verification Requirement**: Execute `npm test` and `npm run lint` to ensure 100% of tests pass with 0 lint errors before completing work.
|
|
11
|
+
5. **No Assert Weakening**: Never weak or remove test assertions to make a test pass.
|
package/README.md
CHANGED
|
@@ -5,47 +5,68 @@
|
|
|
5
5
|
[](https://github.com/FullThrottle83/jules-orchestrator-kit/actions/workflows/jules-audit.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/jules-orchestrator-kit)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org)
|
|
8
9
|
|
|
9
|
-
> **
|
|
10
|
-
>
|
|
10
|
+
> **High-Volume Autonomous Orchestration Engine for Google Jules**
|
|
11
|
+
> Built specifically to execute 300+ daily agent sessions and parallel swarms safely. Zero external runtime dependencies. Built strictly on native Node.js 20+ ESM.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+

|
|
11
16
|
|
|
12
17
|
---
|
|
13
18
|
|
|
14
19
|
## ⚡ 2-Minute Quickstart
|
|
15
20
|
|
|
16
|
-
Get up and running in under 2 minutes:
|
|
21
|
+
Get up and running in under 2 minutes with zero complex configuration:
|
|
17
22
|
|
|
18
23
|
```bash
|
|
19
|
-
# 1.
|
|
24
|
+
# 1. Initialize orchestrator structure in your target codebase
|
|
20
25
|
npx jules-orchestrator-kit init
|
|
21
26
|
|
|
22
|
-
# 2. Dispatch
|
|
27
|
+
# 2. Dispatch an autonomous task (Dry-Run mode for local simulation)
|
|
23
28
|
JULES_DRY_RUN=1 npx agentctl dispatch \
|
|
24
29
|
--title "Add JWT Validator" \
|
|
25
30
|
--prompt "Implement JWT validation middleware with unit tests"
|
|
26
31
|
|
|
27
|
-
# 3. Run security & verification gatekeeper
|
|
32
|
+
# 3. Run the 4-phase security & verification gatekeeper
|
|
28
33
|
npx agentctl gate
|
|
29
34
|
|
|
30
|
-
# 4.
|
|
35
|
+
# 4. Connect as a native stdio MCP server (for Claude Code, Cursor, or Antigravity)
|
|
31
36
|
npx agentctl mcp
|
|
32
37
|
```
|
|
33
38
|
|
|
39
|
+
> 📖 **Looking for production deployment patterns?**
|
|
40
|
+
> Check out [**EXAMPLES.md**](./EXAMPLES.md) for 6 real-world recipes (Nightly TODO Scanner, Composite CI Action, Multi-Worktree Swarms, OODA Auto-Fix, MCP IDE setup, and Specialist Rosters).
|
|
41
|
+
|
|
34
42
|
---
|
|
35
43
|
|
|
36
|
-
##
|
|
44
|
+
## 🎯 Why jules-orchestrator-kit?
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
Whether you are dispatching your first automated coding task or managing high-throughput CI/CD swarms across large engineering teams, `jules-orchestrator-kit` provides total operational safety:
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
| Feature | 🐣 For Rookies & Beginners | 🛠️ For Senior Developers & Infrastructure Engineers |
|
|
49
|
+
| :--- | :--- | :--- |
|
|
50
|
+
| **Safety First** | Never breaks `main` branch or pushes failing code. | 4-Phase Safety Gatekeeper fails closed on scope drift, high entropy secrets, or test regressions. |
|
|
51
|
+
| **Token Budget Protection** | Prevents runaway loops from burning API quotas. | Sliding-window OODA thrash detector ($A \rightarrow B \rightarrow A \rightarrow B$) halts non-convergent repair cycles automatically. |
|
|
52
|
+
| **Zero Setup Hassle** | Works out of the box with standard `npm test`. | **Zero External Runtime Dependencies** (`node:fs`, `node:path`, `node:crypto`, `node:child_process`). |
|
|
53
|
+
| **Multi-Agent Swarms** | Run multiple tasks simultaneously without conflict. | Deterministic VFS mutex and 3-way structural merge engine resolve parallel worktree changes cleanly. |
|
|
54
|
+
| **IDE & Tooling** | Seamlessly connects to your favorite editor. | Native Model Context Protocol (MCP) server over memory-bounded stdio streams. |
|
|
43
55
|
|
|
44
56
|
---
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
<details open>
|
|
59
|
+
<summary><b>⚙️ Control Plane Architecture & Deep Dive</b></summary>
|
|
60
|
+
|
|
61
|
+
<br/>
|
|
62
|
+
|
|
63
|
+

|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
<br/>
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
### Engine System Highlights
|
|
49
70
|
|
|
50
71
|
- **Linearizable VFS Mutex (`src/state.mjs`)**: Kernel-level directory mutex (`withVfsMutex`) guaranteeing serial linearizability for SHA-256 hash-chained session ledgers under high concurrency.
|
|
51
72
|
- **PID Recycling & Stale Lock Protection (`src/state.mjs`)**: Linux `/proc/<pid>/stat` launch-time validation prevents false-positive lock reaps from recycled OS process IDs.
|
|
@@ -54,46 +75,232 @@ Built strictly on native Node.js 18+ ESM with **Zero External Runtime Dependenci
|
|
|
54
75
|
- **TOCTOU & Symlink Defense (`src/security.mjs`)**: `safeAtomicWrite()` uses `O_CREAT | O_EXCL | O_WRONLY` temp files with `fsyncSync` + `renameSync` and `lstatSync`/`realpathSync` symlink checks.
|
|
55
76
|
- **3-Way Structural AST/JSON Merge (`scripts/jules-merge-swarm.mjs`)**: Pure Node `deepMerge3Way()` algorithm for recursive object and array merges executed in isolated temporary directories (`os.tmpdir()`).
|
|
56
77
|
|
|
57
|
-
|
|
78
|
+
</details>
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary><b>🛡️ Zero-Trust Security Gatekeeper</b></summary>
|
|
82
|
+
|
|
83
|
+
<br/>
|
|
84
|
+
|
|
85
|
+

|
|
86
|
+
|
|
87
|
+
### The 4-Phase Safety Audit (`agentctl gate`)
|
|
88
|
+
|
|
89
|
+
1. **Scope Fencing (`forbidden_paths`)**: Ensures agents cannot modify protected files (`package.json`, `.github/`, deployment keys) without explicit overrides.
|
|
90
|
+
2. **Diff Payload Governor**: Rejects oversized diffs (> 75 KB) to prevent truncation and hidden payload injections.
|
|
91
|
+
3. **Secret Entropy Scanner**: Scans diffs for high-confidence secrets (AWS keys, Stripe keys, GitHub tokens, SSH private keys) using Shannon Entropy analysis (> 3.6 bits).
|
|
92
|
+
4. **Trusted Verification Suite**: Executes auto-detected unit tests and linters (`npm test`) to guarantee zero regressions before merging.
|
|
93
|
+
|
|
94
|
+
> [!NOTE]
|
|
95
|
+
> All security rules are fetched strictly from `origin/main` (never untrusted PR branches) to prevent prompt-injection attacks from altering security rules.
|
|
96
|
+
|
|
97
|
+
</details>
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary><b>🔌 Model Context Protocol (MCP) & IDE Integration</b></summary>
|
|
101
|
+
|
|
102
|
+
<br/>
|
|
103
|
+
|
|
104
|
+

|
|
105
|
+
|
|
106
|
+
### Connecting to Claude Desktop, Cursor, or Antigravity
|
|
107
|
+
|
|
108
|
+
Start the native stdio MCP server:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx agentctl mcp
|
|
112
|
+
```
|
|
58
113
|
|
|
59
|
-
|
|
114
|
+
#### MCP Tool Registry Exposed:
|
|
115
|
+
- `dispatch_jules_task`: Dispatch autonomous coding tasks directly from your LLM prompt.
|
|
116
|
+
- `audit_jules_gate`: Execute the 4-phase safety gate against the workspace.
|
|
117
|
+
- `check_risk_tier`: Classify workspace changes into Risk Tiers (R0 Cosmetic to R3 Restricted).
|
|
118
|
+
- `get_jules_status`: Fetch real-time status of active, pending, and completed tasks.
|
|
60
119
|
|
|
61
|
-
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary><b>🤖 Specialist Agent Prompt Presets (.agent/prompts/)</b></summary>
|
|
124
|
+
|
|
125
|
+
<br/>
|
|
126
|
+
|
|
127
|
+
Specialized prompt presets enforcement payload limits (< 75 KB) and domain guardrails out of the box:
|
|
128
|
+
|
|
129
|
+
| Preset | Role & Domain | Primary Focus |
|
|
62
130
|
| :--- | :--- | :--- |
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
| `agentctl merge-swarm` | `agentctl merge-swarm` | Performs 3-way structural merge on completed swarm PRs |
|
|
68
|
-
| `agentctl mcp` | `agentctl mcp` | Starts stdio Model Context Protocol (MCP) server |
|
|
69
|
-
| `agentctl doctor` | `agentctl doctor` | Verifies stack configuration, environment, and budget |
|
|
131
|
+
| **`Overseer.md`** | **Architect & Supervisor** | System-wide refactoring, linearizable state, and structural integrity. |
|
|
132
|
+
| **`Bolt.md`** | **Performance Engineer** | Bottleneck elimination, streaming optimization, and low-latency execution. |
|
|
133
|
+
| **`Sentinel.md`** | **Security Auditor** | Vulnerability patching, secret sanitization, and TOCTOU defense. |
|
|
134
|
+
| **`Janitor.md`** | **Technical Debt & Cleanup** | Dead code elimination, unused import pruning, and zero-dependency compliance. |
|
|
70
135
|
|
|
71
|
-
|
|
136
|
+
```bash
|
|
137
|
+
# Example: Dispatch a cleanup task using the Janitor preset
|
|
138
|
+
npx agentctl dispatch \
|
|
139
|
+
--prompt "$(cat .agent/prompts/Janitor.md) Prune unused helper methods in src/utils.mjs"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
</details>
|
|
143
|
+
|
|
144
|
+
<details>
|
|
145
|
+
<summary><b>⚡ GitHub Actions Composite Action (.github/actions/setup-jules)</b></summary>
|
|
146
|
+
|
|
147
|
+
<br/>
|
|
148
|
+
|
|
149
|
+
Integrate `jules-orchestrator-kit` into any GitHub Actions workflow with 3 lines of YAML:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
steps:
|
|
153
|
+
- uses: actions/checkout@v4
|
|
154
|
+
- uses: FullThrottle83/jules-orchestrator-kit/.github/actions/setup-jules@main
|
|
155
|
+
with:
|
|
156
|
+
action: 'gate'
|
|
157
|
+
base_branch: 'main'
|
|
158
|
+
tier: 'ultra'
|
|
159
|
+
env:
|
|
160
|
+
JULES_API_KEY: ${{ secrets.JULES_API_KEY }}
|
|
161
|
+
```
|
|
72
162
|
|
|
73
|
-
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
<details>
|
|
166
|
+
<summary><b>🛠️ CLI Command Reference (`agentctl`)</b></summary>
|
|
167
|
+
|
|
168
|
+
<br/>
|
|
169
|
+
|
|
170
|
+
| Command | Usage Example | Description |
|
|
171
|
+
| :--- | :--- | :--- |
|
|
172
|
+
| **`init`** | `npx agentctl init` | Initializes `.agent/` configuration, workflows, and task queue directory |
|
|
173
|
+
| **`dispatch`** | `agentctl dispatch --title "Fix Bug" --prompt "..."` | Dispatches an autonomous task to Google Jules |
|
|
174
|
+
| **`gate`** | `agentctl gate [--fix] [--base main]` | Runs 4-phase safety gatekeeper audit against workspace |
|
|
175
|
+
| **`queue`** | `agentctl queue` | Processes pending task queue sequentially from `.agent/jules-queue/` |
|
|
176
|
+
| **`swarm`** | `agentctl swarm` | Launches parallel multi-agent swarm in isolated git worktrees |
|
|
177
|
+
| **`merge-swarm`** | `agentctl merge-swarm` | Performs 3-way structural merge on completed swarm PRs |
|
|
178
|
+
| **`mcp`** | `agentctl mcp` | Starts stdio Model Context Protocol (MCP) JSON-RPC 2.0 server |
|
|
179
|
+
| **`doctor`** | `agentctl doctor` | Verifies stack configuration, environment keys, and daily token budget |
|
|
180
|
+
| **`scan`** | `agentctl scan` | Scans codebase for `TODO` and `FIXME` comments and generates task queue |
|
|
181
|
+
| **`cleanup`** | `agentctl cleanup` | Audits and cleans up stale git worktrees and temporary state files |
|
|
182
|
+
|
|
183
|
+
</details>
|
|
184
|
+
|
|
185
|
+
<details>
|
|
186
|
+
<summary><b>💳 Subscription Tier Presets (Free / Pro / Ultra)</b></summary>
|
|
187
|
+
|
|
188
|
+
<br/>
|
|
189
|
+
|
|
190
|
+

|
|
191
|
+
|
|
192
|
+
<br/>
|
|
193
|
+
|
|
194
|
+
Tailor session limits and rate-limiting behavior to your Google Jules API subscription tier:
|
|
195
|
+
|
|
196
|
+
| Tier | `dailyTasks` | `repairAttempts` | `concurrency` | `staggerMs` | Target Usage |
|
|
197
|
+
| :--- | :---: | :---: | :---: | :---: | :--- |
|
|
198
|
+
| **`free`** | `15` | `1` | `1` | `3000 ms` | **Hobby / Free Tier:** Conserves quota, prevents HTTP 429 rate limits. |
|
|
199
|
+
| **`pro`** | `100` | `2` | `2` | `1500 ms` | **Developer Pro:** Balanced throughput for everyday work. |
|
|
200
|
+
| **`ultra`** *(default)* | `300` | `3` | `3` | `1000 ms` | **Swarm / Enterprise:** Maximum parallel throughput & CI/CD. |
|
|
201
|
+
|
|
202
|
+
**How to activate:**
|
|
203
|
+
- **Environment Variable:** `export JULES_TIER=free` (or set in `.env`)
|
|
204
|
+
- **Config File (`.agent/jules.yml`):** Set `tier: free`
|
|
205
|
+
|
|
206
|
+
</details>
|
|
207
|
+
|
|
208
|
+
<details>
|
|
209
|
+
<summary><b>📝 Configuration Reference (`.agent/jules.yml`)</b></summary>
|
|
210
|
+
|
|
211
|
+
<br/>
|
|
212
|
+
|
|
213
|
+
Auto-generated by `agentctl init` at the root of your project:
|
|
214
|
+
|
|
215
|
+
```yaml
|
|
216
|
+
version: 2
|
|
217
|
+
tier: "pro" # Options: free, pro, ultra (default: ultra)
|
|
218
|
+
test_cmd: "npm test"
|
|
219
|
+
build_cmd: "npm run build"
|
|
220
|
+
forbidden_paths:
|
|
221
|
+
- ".github/"
|
|
222
|
+
- "package.json"
|
|
223
|
+
- ".agent/jules.yml"
|
|
224
|
+
allow_paths: []
|
|
225
|
+
limits:
|
|
226
|
+
dailyTasks: 300
|
|
227
|
+
repairAttempts: 3
|
|
228
|
+
diffKb: 75
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
</details>
|
|
232
|
+
|
|
233
|
+
<details>
|
|
234
|
+
<summary><b>🚦 Exit Code Registry & Troubleshooting</b></summary>
|
|
235
|
+
|
|
236
|
+
<br/>
|
|
74
237
|
|
|
75
238
|
Standardized exit codes enforced across all CLI utilities:
|
|
76
239
|
|
|
77
|
-
| Code | Status | Description |
|
|
240
|
+
| Exit Code | Status | Description & Immediate Action |
|
|
78
241
|
| :---: | :--- | :--- |
|
|
79
|
-
| `0` | **Success** | Task completed cleanly; verification passed
|
|
80
|
-
| `1` | **
|
|
81
|
-
| `2` | **API / Network Failure** | Jules API rate-limit (429), `FAILED_PRECONDITION
|
|
82
|
-
| `3` | **Scope Violation** | Attempted modification of
|
|
83
|
-
| `4` | **OODA Exhausted /
|
|
84
|
-
| `5` | **Diff Payload
|
|
85
|
-
| `6` | **Secret Detected** | High-confidence secret or
|
|
242
|
+
| `0` | **Success** | Task completed cleanly; PR opened or verification passed. |
|
|
243
|
+
| `1` | **Pre-Dispatch / Arg Error** | Invalid arguments, prompt > 50 KB, or pre-dispatch validation error. |
|
|
244
|
+
| `2` | **API / Network Failure** | Jules API rate-limit (HTTP 429), `FAILED_PRECONDITION` quota, or timeout. |
|
|
245
|
+
| `3` | **Scope Violation** | Attempted modification of restricted files (`.github/`, command files, agent rules). |
|
|
246
|
+
| `4` | **OODA Exhausted / Thrash** | Verification suite failed after 3 repair attempts or hit deterministic regression. |
|
|
247
|
+
| `5` | **Diff Payload Limit** | Post-change git diff exceeds payload budget (`limits.diffKb`, default 75 KB). |
|
|
248
|
+
| `6` | **Secret Detected** | High-confidence secret or private key detected in patch diff. |
|
|
86
249
|
| `7` | **Budget Exhausted** | Daily task session quota limit reached (`limits.dailyTasks`, default 300). |
|
|
87
250
|
|
|
251
|
+
</details>
|
|
252
|
+
|
|
253
|
+
<details>
|
|
254
|
+
<summary><b>🔐 Environment Variables Reference</b></summary>
|
|
255
|
+
|
|
256
|
+
<br/>
|
|
257
|
+
|
|
258
|
+
| Variable | Description | Default |
|
|
259
|
+
| :--- | :--- | :--- |
|
|
260
|
+
| `JULES_API_KEY` | Google Jules REST API key | *(none)* |
|
|
261
|
+
| `JULES_REPO` | Target GitHub Repository (`owner/repo`) | Auto-detected from `git remote` |
|
|
262
|
+
| `JULES_TIER` | Subscription tier preset (`free`, `pro`, `ultra`) | `ultra` |
|
|
263
|
+
| `JULES_DRY_RUN` | Set to `1` or `true` for dry-run simulation mode | `false` |
|
|
264
|
+
| `JULES_DAILY_BUDGET` | Custom daily session budget limit | `300` |
|
|
265
|
+
| `JULES_MAX_DIFF_KB` | Custom git diff payload limit in KB | `75` |
|
|
266
|
+
| `JULES_ALLOW_COMMAND_FILE_CHANGES` | Allow PR changes to command files (`package.json`, etc.) | `false` |
|
|
267
|
+
| `JULES_ALLOW_AGENT_RULE_CHANGES` | Allow PR changes to agent rule files (`AGENTS.md`, etc.) | `false` |
|
|
268
|
+
| `BASE_BRANCH` | Base branch for PR Audits & Merge-Base checks | `main` |
|
|
269
|
+
| `NO_COLOR` | Set to `true` to disable ANSI color output | `false` |
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary><b>🌐 Supported Tech Stacks & Auto-Detection</b></summary>
|
|
275
|
+
|
|
276
|
+
<br/>
|
|
277
|
+
|
|
278
|
+
The orchestrator automatically infers verification and build commands across ecosystems:
|
|
279
|
+
|
|
280
|
+
| Stack / Ecosystem | Manifest File | Inferred Test Command | Inferred Build Command |
|
|
281
|
+
| :--- | :--- | :--- | :--- |
|
|
282
|
+
| **Turborepo** | `turbo.json` | `npx turbo run test` | `npx turbo run build` |
|
|
283
|
+
| **pnpm Workspace** | `pnpm-workspace.yaml` | `pnpm test` | `pnpm build` |
|
|
284
|
+
| **Nx Workspace** | `nx.json` | `npx nx run-many -t test` | `npx nx run-many -t build` |
|
|
285
|
+
| **JavaScript / TypeScript** | `package.json` | `npm test` | `npm run build` |
|
|
286
|
+
| **Rust** | `Cargo.toml` | `cargo test --workspace` | `cargo build` |
|
|
287
|
+
| **Go** | `go.mod` | `go test ./...` | `go build ./...` |
|
|
288
|
+
| **Python** | `pyproject.toml` | `pytest` | *(none)* |
|
|
289
|
+
| **Bun / Deno** | `bunfig.toml` / `deno.json` | `bun test` / `deno test` | `bun run build` |
|
|
290
|
+
| **Elixir / Ruby** | `mix.exs` / `Gemfile` | `mix test` / `rake test` | *(standard build)* |
|
|
291
|
+
| **Java / C / C++** | `pom.xml` / `Makefile` | `mvn test` / `make test` | `mvn compile` / `make` |
|
|
292
|
+
|
|
293
|
+
</details>
|
|
294
|
+
|
|
88
295
|
---
|
|
89
296
|
|
|
90
|
-
## 🤝
|
|
297
|
+
## 🤝 Contributing & Standards
|
|
91
298
|
|
|
92
|
-
We
|
|
299
|
+
We welcome community contributions! Please adhere to our core engineering invariants:
|
|
93
300
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
301
|
+
1. **Zero External Runtime Dependencies**: Use ONLY native Node.js ESM built-in modules (`node:fs`, `node:path`, `node:crypto`, `node:child_process`, `node:os`).
|
|
302
|
+
2. **100% Verification Suite**: All test suites must pass cleanly with 0 errors.
|
|
303
|
+
3. **Cross-Platform Compatibility**: Always normalize Windows backslashes (`\`) to POSIX slashes (`/`).
|
|
97
304
|
|
|
98
305
|
### Running Tests Locally
|
|
99
306
|
|
package/bin/agentctl.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const command = args[0];
|
|
|
13
13
|
|
|
14
14
|
function printHelp() {
|
|
15
15
|
console.log(`
|
|
16
|
-
🚀 agentctl v0.
|
|
16
|
+
🚀 agentctl v0.22.0 — Universal Agent Orchestrator & Safety Gatekeeper
|
|
17
17
|
|
|
18
18
|
Usage: agentctl <command> [options]
|
|
19
19
|
|
|
@@ -43,7 +43,7 @@ async function main() {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
46
|
-
console.log("agentctl v0.
|
|
46
|
+
console.log("agentctl v0.22.0");
|
|
47
47
|
process.exit(0);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -223,7 +223,7 @@ async function main() {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
case "doctor": {
|
|
226
|
-
console.log(`\n🔍 agentctl System Diagnostics (v0.
|
|
226
|
+
console.log(`\n🔍 agentctl System Diagnostics (v0.22.0)`);
|
|
227
227
|
console.log(`--------------------------------------------------`);
|
|
228
228
|
console.log(` Project Root : ${root}`);
|
|
229
229
|
console.log(` Config File : ${config._file || "None (Using defaults)"}`);
|
package/bin/init.js
CHANGED
|
@@ -145,11 +145,26 @@ const rulesDir = path.join(agentDir, "rules");
|
|
|
145
145
|
const queueDir = path.join(agentDir, "jules-queue");
|
|
146
146
|
const completedQueueDir = path.join(queueDir, "completed");
|
|
147
147
|
const workflowsDir = path.join(agentDir, "workflows");
|
|
148
|
+
const promptsDir = path.join(agentDir, "prompts");
|
|
148
149
|
|
|
149
|
-
[agentDir, rulesDir, queueDir, completedQueueDir, workflowsDir].forEach((d) => {
|
|
150
|
+
[agentDir, rulesDir, queueDir, completedQueueDir, workflowsDir, promptsDir].forEach((d) => {
|
|
150
151
|
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
151
152
|
});
|
|
152
153
|
|
|
154
|
+
// Scaffold .agent/prompts files
|
|
155
|
+
const sourcePromptsDir = path.join(kitRoot, ".agent/prompts");
|
|
156
|
+
if (fs.existsSync(sourcePromptsDir)) {
|
|
157
|
+
const promptFiles = fs.readdirSync(sourcePromptsDir);
|
|
158
|
+
promptFiles.forEach((file) => {
|
|
159
|
+
const srcPrompt = path.join(sourcePromptsDir, file);
|
|
160
|
+
const destPrompt = path.join(promptsDir, file);
|
|
161
|
+
if (!fs.existsSync(destPrompt) || isForce) {
|
|
162
|
+
fs.copyFileSync(srcPrompt, destPrompt);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
console.log("✅ Created: .agent/prompts presets (Overseer, Bolt, Sentinel, Janitor, Task_Template)");
|
|
166
|
+
}
|
|
167
|
+
|
|
153
168
|
// Scaffold .agent/jules.yml
|
|
154
169
|
const yamlConfigPath = path.join(agentDir, "jules.yml");
|
|
155
170
|
if (!fs.existsSync(yamlConfigPath) || isForce) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Orchestration kit for running Google Jules autonomous agents.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -58,10 +58,11 @@
|
|
|
58
58
|
"jules:check-asset-integrity": "node scripts/asset-integrity-check.mjs",
|
|
59
59
|
"jules:risk-tier": "node scripts/risk-tier.mjs",
|
|
60
60
|
"jules:rules-lint": "node scripts/rules-lint.mjs",
|
|
61
|
+
"release": "node scripts/release.mjs",
|
|
61
62
|
"lint": "eslint ."
|
|
62
63
|
},
|
|
63
64
|
"engines": {
|
|
64
|
-
"node": ">=
|
|
65
|
+
"node": ">=20.0.0"
|
|
65
66
|
},
|
|
66
67
|
"keywords": [
|
|
67
68
|
"jules",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Automated Release Orchestrator script for jules-orchestrator-kit.
|
|
5
|
+
* Verifies test suite, updates git tags, and creates GitHub Release via gh CLI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
import { resolveRoot } from "../src/config.mjs";
|
|
12
|
+
|
|
13
|
+
const root = resolveRoot();
|
|
14
|
+
const pkgPath = join(root, "package.json");
|
|
15
|
+
const changelogPath = join(root, "CHANGELOG.md");
|
|
16
|
+
|
|
17
|
+
if (!existsSync(pkgPath)) {
|
|
18
|
+
console.error("❌ Error: package.json not found.");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
23
|
+
const version = pkg.version;
|
|
24
|
+
const tagName = `v${version}`;
|
|
25
|
+
|
|
26
|
+
console.log(`🚀 Automated Release Pipeline for ${pkg.name} (${tagName})`);
|
|
27
|
+
console.log("-------------------------------------------------------");
|
|
28
|
+
|
|
29
|
+
// 1. Verify test suite
|
|
30
|
+
console.log("1. Running unit test verification suite...");
|
|
31
|
+
try {
|
|
32
|
+
execSync("npm test", { cwd: root, stdio: "inherit" });
|
|
33
|
+
console.log(" ✅ Test suite passed cleanly.\n");
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error("❌ Release Aborted: Test suite failed.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Extract release notes from CHANGELOG.md
|
|
40
|
+
console.log(`2. Extracting release notes for ${tagName} from CHANGELOG.md...`);
|
|
41
|
+
let notes = "";
|
|
42
|
+
if (existsSync(changelogPath)) {
|
|
43
|
+
const changelog = readFileSync(changelogPath, "utf-8");
|
|
44
|
+
const regex = new RegExp(`## \\[${version}\\][^\\n]*\\n([\\s\\S]*?)(?=\\n## \\[|$)`, "i");
|
|
45
|
+
const match = changelog.match(regex);
|
|
46
|
+
if (match && match[1]) {
|
|
47
|
+
notes = match[1].trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!notes) {
|
|
52
|
+
notes = `Release ${tagName}`;
|
|
53
|
+
console.log(" ⚠️ Warning: Release entry not found in CHANGELOG.md, using default title.");
|
|
54
|
+
} else {
|
|
55
|
+
console.log(" ✅ Extracted release notes from CHANGELOG.md.\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. Create Git Tag if not exists
|
|
59
|
+
console.log(`3. Checking Git tag ${tagName}...`);
|
|
60
|
+
try {
|
|
61
|
+
const tags = execSync("git tag -l", { cwd: root, encoding: "utf-8" });
|
|
62
|
+
if (tags.split("\n").includes(tagName)) {
|
|
63
|
+
console.log(` ℹ️ Git tag ${tagName} already exists.`);
|
|
64
|
+
} else {
|
|
65
|
+
execSync(`git tag -a ${tagName} -m "${pkg.name} ${tagName}"`, { cwd: root, stdio: "inherit" });
|
|
66
|
+
console.log(` ✅ Created Git tag ${tagName}.`);
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`❌ Tagging failed: ${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 4. Push git commits and tag to origin
|
|
74
|
+
console.log(`4. Pushing main and tag ${tagName} to origin...`);
|
|
75
|
+
try {
|
|
76
|
+
execSync("git push origin main", { cwd: root, stdio: "inherit" });
|
|
77
|
+
execSync(`git push origin ${tagName}`, { cwd: root, stdio: "inherit" });
|
|
78
|
+
console.log(" ✅ Pushed commits and tag to origin.\n");
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(`❌ Push failed: ${err.message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 5. Create GitHub Release via gh CLI
|
|
85
|
+
console.log(`5. Creating GitHub Release via gh CLI...`);
|
|
86
|
+
try {
|
|
87
|
+
const notesFile = join(root, ".agent", "temp_release_notes.txt");
|
|
88
|
+
const fs = await import("node:fs");
|
|
89
|
+
fs.writeFileSync(notesFile, notes, "utf-8");
|
|
90
|
+
|
|
91
|
+
execSync(`gh release create ${tagName} --title "${tagName}" --notes-file "${notesFile}"`, {
|
|
92
|
+
cwd: root,
|
|
93
|
+
stdio: "inherit",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (fs.existsSync(notesFile)) fs.unlinkSync(notesFile);
|
|
97
|
+
console.log(` 🎉 GitHub Release ${tagName} created successfully!`);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.log(` ℹ️ Note: gh release creation finished or already exists.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log("-------------------------------------------------------");
|
|
103
|
+
console.log(`✅ Release ${tagName} Completed Successfully!\n`);
|
package/src/config.mjs
CHANGED
|
@@ -306,6 +306,9 @@ export const TIER_PRESETS = {
|
|
|
306
306
|
* Loads and validates configuration from .agent/config.yml or .agent/jules.yml.
|
|
307
307
|
*/
|
|
308
308
|
export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
309
|
+
if (root === null || root === undefined) {
|
|
310
|
+
root = resolveRoot();
|
|
311
|
+
}
|
|
309
312
|
const candidates = explicitPath
|
|
310
313
|
? [explicitPath]
|
|
311
314
|
: [join(root, ".agent/config.yml"), join(root, ".agent/jules.yml")];
|
|
@@ -328,8 +331,8 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
328
331
|
const activeTier = String(process.env.JULES_TIER || parsed.tier || "ultra").toLowerCase();
|
|
329
332
|
const tierLimits = TIER_PRESETS[activeTier] || TIER_PRESETS.ultra;
|
|
330
333
|
|
|
331
|
-
const envDailyTasks = process.env.JULES_DAILY_BUDGET ? Number(process.env.JULES_DAILY_BUDGET) : null;
|
|
332
|
-
const envDiffKb = process.env.JULES_MAX_DIFF_KB ? Number(process.env.JULES_MAX_DIFF_KB) : null;
|
|
334
|
+
const envDailyTasks = process.env.JULES_DAILY_BUDGET !== undefined ? Number(process.env.JULES_DAILY_BUDGET) : null;
|
|
335
|
+
const envDiffKb = process.env.JULES_MAX_DIFF_KB !== undefined ? Number(process.env.JULES_MAX_DIFF_KB) : null;
|
|
333
336
|
|
|
334
337
|
const config = {
|
|
335
338
|
version: parsed.version || DEFAULTS.version,
|
|
@@ -344,8 +347,8 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
344
347
|
...DEFAULTS.limits,
|
|
345
348
|
...tierLimits,
|
|
346
349
|
...(parsed.limits || {}),
|
|
347
|
-
...(envDailyTasks && !isNaN(envDailyTasks) ? { dailyTasks: envDailyTasks } : {}),
|
|
348
|
-
...(envDiffKb && !isNaN(envDiffKb) ? { diffKb: envDiffKb } : {}),
|
|
350
|
+
...(envDailyTasks !== null && !isNaN(envDailyTasks) ? { dailyTasks: envDailyTasks } : {}),
|
|
351
|
+
...(envDiffKb !== null && !isNaN(envDiffKb) ? { diffKb: envDiffKb } : {}),
|
|
349
352
|
},
|
|
350
353
|
isolation: parsed.isolation || DEFAULTS.isolation,
|
|
351
354
|
runner: parsed.runner || DEFAULTS.runner,
|
package/src/engine.mjs
CHANGED
|
@@ -13,7 +13,12 @@ import { createHash } from "node:crypto";
|
|
|
13
13
|
export function fingerprintFailureState(failure = {}, root = process.cwd()) {
|
|
14
14
|
const rawStderr = failure.stderr || failure.stdout || failure.message || "Unknown Error";
|
|
15
15
|
const normalizedStderr = String(rawStderr)
|
|
16
|
+
.replace(/[\u001b\x1b]\[[0-9;]*[a-zA-Z]/g, "")
|
|
17
|
+
.replace(/\?[^\s"'\)\\]+/g, "<?>")
|
|
16
18
|
.replace(/:\d+:\d+/g, ":?:?")
|
|
19
|
+
.replace(/:\d+/g, ":?")
|
|
20
|
+
.replace(/\bline \d+/gi, "line ?")
|
|
21
|
+
.replace(/\bcol(umn)? \d+/gi, "col ?")
|
|
17
22
|
.replace(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z?\b/g, "<timestamp>")
|
|
18
23
|
.replace(/\b[0-9a-f]{7,40}\b/g, "<sha>")
|
|
19
24
|
.replace(/\/[\w\/-]+\//g, "/?/")
|
package/src/mcp.mjs
CHANGED
|
@@ -207,7 +207,28 @@ export async function handleMcpRequest(request, opts = {}) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
if (toolName === "check_risk_tier") {
|
|
210
|
-
|
|
210
|
+
if (!args || !args.files || !Array.isArray(args.files)) {
|
|
211
|
+
return {
|
|
212
|
+
jsonrpc: "2.0",
|
|
213
|
+
id,
|
|
214
|
+
error: { code: -32602, message: "Invalid parameters: 'files' must be an array" },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (args.files.some(f => typeof f !== "string")) {
|
|
218
|
+
return {
|
|
219
|
+
jsonrpc: "2.0",
|
|
220
|
+
id,
|
|
221
|
+
error: { code: -32602, message: "Invalid parameters: 'files' must be an array of strings" },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (args.diffLines !== undefined && typeof args.diffLines !== "number") {
|
|
225
|
+
return {
|
|
226
|
+
jsonrpc: "2.0",
|
|
227
|
+
id,
|
|
228
|
+
error: { code: -32602, message: "Invalid parameters: 'diffLines' must be a number" },
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const tierResult = classifyRiskTier(args.files, { diffLines: args.diffLines || 0 });
|
|
211
232
|
return {
|
|
212
233
|
jsonrpc: "2.0",
|
|
213
234
|
id,
|
package/src/risk.mjs
CHANGED
package/src/security.mjs
CHANGED
|
@@ -15,7 +15,7 @@ export const HIGH_CONFIDENCE_PATTERNS = [
|
|
|
15
15
|
/\bASIA[0-9A-Z]{16}\b/g,
|
|
16
16
|
/\baws_secret_access_key\s*=\s*['"]?[A-Za-z0-9\/+=]{40}['"]?/g,
|
|
17
17
|
|
|
18
|
-
/-----BEGIN (?:RSA|DSA|EC|OPENSSH|PRIVATE) KEY-----[\s\S]*?-----END (?:RSA|DSA|EC|OPENSSH|PRIVATE) KEY-----/g,
|
|
18
|
+
/-----BEGIN (?:RSA|DSA|EC|OPENSSH|PRIVATE)(?:\s+PRIVATE)? KEY-----[\s\S]*?-----END (?:RSA|DSA|EC|OPENSSH|PRIVATE)(?:\s+PRIVATE)? KEY-----/g,
|
|
19
19
|
/PuTTY-User-Key-File-[0-9]:[^\n]+/g,
|
|
20
20
|
|
|
21
21
|
/\bsk_live_[0-9a-zA-Z]{24,99}\b/g,
|
package/src/webhook.mjs
CHANGED
|
@@ -10,7 +10,12 @@ import { createServer } from "node:http";
|
|
|
10
10
|
*/
|
|
11
11
|
export function verifySignature(payload, signatureHeader, secret) {
|
|
12
12
|
if (!secret) return false; // Fail closed if secret is not configured
|
|
13
|
-
if (
|
|
13
|
+
if (typeof signatureHeader !== "string") return false;
|
|
14
|
+
if (!signatureHeader.startsWith("sha256=")) return false;
|
|
15
|
+
|
|
16
|
+
const hexSignature = signatureHeader.slice(7);
|
|
17
|
+
// Verify hexSignature consists strictly of 64 hex characters (0-9, a-f, A-F)
|
|
18
|
+
if (!/^[0-9a-fA-F]{64}$/.test(hexSignature)) return false;
|
|
14
19
|
|
|
15
20
|
const hmac = createHmac("sha256", secret);
|
|
16
21
|
const bodyBuf = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "utf-8");
|
|
@@ -30,13 +35,16 @@ export function verifySignature(payload, signatureHeader, secret) {
|
|
|
30
35
|
* @returns {object} Parsed JSON payload
|
|
31
36
|
*/
|
|
32
37
|
export function parseWebhookPayload(bodyBuf, contentType = "application/json") {
|
|
33
|
-
|
|
38
|
+
if (!bodyBuf || bodyBuf.length === 0) return {};
|
|
39
|
+
const str = bodyBuf.toString("utf-8").trim();
|
|
40
|
+
if (!str) return {};
|
|
41
|
+
|
|
34
42
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
35
43
|
const params = new URLSearchParams(str);
|
|
36
44
|
const payloadStr = params.get("payload");
|
|
37
45
|
return payloadStr ? JSON.parse(payloadStr) : {};
|
|
38
46
|
}
|
|
39
|
-
return
|
|
47
|
+
return JSON.parse(str);
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/**
|
|
@@ -47,37 +55,64 @@ export function parseWebhookPayload(bodyBuf, contentType = "application/json") {
|
|
|
47
55
|
* @returns {object} Response summary { handled: boolean, action: string, details: object }
|
|
48
56
|
*/
|
|
49
57
|
export function routeWebhookEvent(eventType, payload, handlers = {}) {
|
|
58
|
+
const safePayload = payload || {};
|
|
59
|
+
|
|
50
60
|
if (eventType === "ping") {
|
|
51
|
-
return { handled: true, action: "ping", details: { zen:
|
|
61
|
+
return { handled: true, action: "ping", details: { zen: safePayload.zen, hookId: safePayload.hook_id } };
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
if (eventType === "pull_request") {
|
|
55
|
-
const action =
|
|
56
|
-
const prNumber =
|
|
57
|
-
const repo =
|
|
58
|
-
const branch =
|
|
59
|
-
const merged =
|
|
65
|
+
const action = safePayload.action;
|
|
66
|
+
const prNumber = safePayload.number || safePayload.pull_request?.number;
|
|
67
|
+
const repo = safePayload.repository?.full_name;
|
|
68
|
+
const branch = safePayload.pull_request?.head?.ref;
|
|
69
|
+
const merged = safePayload.pull_request?.merged || false;
|
|
60
70
|
|
|
61
71
|
const details = { action, prNumber, repo, branch, merged };
|
|
62
72
|
if (typeof handlers.onPullRequest === "function") {
|
|
63
|
-
|
|
73
|
+
try {
|
|
74
|
+
handlers.onPullRequest(details, safePayload);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
// Safely ignore errors from handlers
|
|
77
|
+
}
|
|
64
78
|
}
|
|
65
79
|
return { handled: true, action: `pull_request:${action}`, details };
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
if (eventType === "workflow_run") {
|
|
69
|
-
const action =
|
|
70
|
-
const name =
|
|
71
|
-
const conclusion =
|
|
72
|
-
const repo =
|
|
83
|
+
const action = safePayload.action;
|
|
84
|
+
const name = safePayload.workflow?.name || safePayload.workflow_run?.name;
|
|
85
|
+
const conclusion = safePayload.workflow_run?.conclusion;
|
|
86
|
+
const repo = safePayload.repository?.full_name;
|
|
73
87
|
|
|
74
88
|
const details = { action, name, conclusion, repo };
|
|
75
89
|
if (typeof handlers.onWorkflowRun === "function") {
|
|
76
|
-
|
|
90
|
+
try {
|
|
91
|
+
handlers.onWorkflowRun(details, safePayload);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// Safely ignore errors from handlers
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
return { handled: true, action: `workflow_run:${action}`, details };
|
|
79
97
|
}
|
|
80
98
|
|
|
99
|
+
// Fallback status handler for unhandled GitHub event types
|
|
100
|
+
if (typeof handlers.onUnhandled === "function") {
|
|
101
|
+
try {
|
|
102
|
+
handlers.onUnhandled(eventType, safePayload);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
// Safely ignore or log error from fallback handler to prevent crashing
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof handlers.onFallback === "function") {
|
|
109
|
+
try {
|
|
110
|
+
handlers.onFallback(eventType, safePayload);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
// Safely ignore or log error from fallback handler to prevent crashing
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
81
116
|
return { handled: false, action: eventType, details: {} };
|
|
82
117
|
}
|
|
83
118
|
|