jules-orchestrator-kit 0.2.8 → 0.3.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.
@@ -36,12 +36,12 @@ Dispatch tasks to Jules when ALL of the following apply:
36
36
 
37
37
  Jules automatically infers test and build verification commands via `scripts/command-resolver.mjs`:
38
38
  - `.agent/jules.yml` -> Custom user commands (`test_cmd`, `build_cmd`)
39
- - `package.json` -> `npm test` (or `npm run lint && npm test` if lint script exists)
40
- - `Cargo.toml` -> `cargo test --workspace && cargo build`
41
- - `go.mod` -> `go test ./... && go build ./...`
42
- - `pyproject.toml` -> `pytest`
43
- - `pom.xml` -> `mvn test`
44
- - `build.gradle` -> `./gradlew test`
39
+ - `package.json` -> `testCmd: "npm test"` (or `"npm run lint && npm test"`), `buildCmd: "npm run build"`
40
+ - `Cargo.toml` -> `testCmd: "cargo test --workspace"`, `buildCmd: "cargo build"`
41
+ - `go.mod` -> `testCmd: "go test ./..."`, `buildCmd: "go build ./..."`
42
+ - `pyproject.toml` -> `testCmd: "pytest"`, `buildCmd: ""`
43
+ - `pom.xml` -> `testCmd: "mvn test"`, `buildCmd: "mvn compile"`
44
+ - `build.gradle` -> `testCmd: "./gradlew test"`, `buildCmd: "./gradlew assemble"`
45
45
  - Workspace graphs (`turbo.json`, `pnpm-workspace.yaml`, `nx.json`) -> targeted affected package filters
46
46
 
47
47
  ---
package/README.md CHANGED
@@ -54,8 +54,7 @@ Instead of blindly trusting AI code mutations, the Orchestrator acts as a strict
54
54
  2. **Secret & Path Defense:** Hides passwords, API keys (`entropy > 3.6`), and blocks path traversal (`../`).
55
55
  3. **Jules Proposes Code:** Google Jules mutates code in an isolated environment.
56
56
  4. **Tiered Verification:** Enforces scope bounds (`git diff`), runs fast-fail linters/type-checks, then executes full test/build suites.
57
- 5. **Self-Correction (OODA Loop):** If tests fail, stderr traces are fed back to Jules to self-correct (up to 4 attempts).
58
- 6. **Clean Pull Request:** Once verified green, commits & pushes clean code to GitHub.
57
+ 5. **Self-Healing PR Gatekeeper (OODA Feedback):** Parses execution stderr traces, logs diagnostic telemetry to `.agent/history/metrics.jsonl`, and enforces strict exit code 1 to block bad code from merging.
59
58
 
60
59
  <details>
61
60
  <summary><b>šŸ” View System Architecture Diagram (For Power Users)</b></summary>
@@ -75,33 +74,22 @@ sequenceDiagram
75
74
  Orc->>Git: Provision Worktree (`git worktree add`)
76
75
  Orc->>Jules: Dispatch Context, Invariants & Target Scope
77
76
 
78
- loop Max Retries (Attempts < 4)
79
- Jules->>Git: Propose Code Mutations
80
-
81
- note over Orc,Git: Phase 2: Tiered Verification
82
- Orc->>Git: Scope Audit (`git diff --name-only` vs forbidden_paths)
83
- alt Scope Breach
84
- Git-->>Orc: Scope Violation Error
85
- else Scope OK
86
- Orc->>Git: Run Fast-Fail Checks (Lint / Typecheck)
87
- opt Pass Static Checks
88
- Orc->>Git: Run Heavy Suite (`build_cmd` & `test_cmd`)
89
- end
90
- end
91
-
92
- alt Verification Gates Pass
93
- Orc->>Git: Commit, Push & Cleanup Worktree
94
- Orc->>CLI: Return Success + Metrics (.agent/history/metrics.jsonl)
95
- note over Jules,Git: Exit Loop
96
- else Verification Gates Fail (Attempts < 4)
97
- Git-->>Orc: Execution Trace (stdout/stderr / Diff)
98
- Orc->>Jules: Inject OODA Feedback & Error Context
99
- end
77
+ Jules->>Git: Propose Code Mutations
78
+
79
+ note over Orc,Git: Phase 2: Tiered Verification Gatekeeper
80
+ Orc->>Git: Scope Audit (`git diff -z --name-only` vs forbidden_paths)
81
+ alt Scope Breach
82
+ Git-->>Orc: Scope Violation Error (Exit 1)
83
+ else Scope OK
84
+ Orc->>Git: Run Verification Suite (`test_cmd` & `build_cmd`)
100
85
  end
101
86
 
102
- opt Verification Gates Fail (Attempts >= 4)
103
- Orc->>Git: Abort & Rollback Worktree (`git worktree remove --force`)
104
- Orc->>CLI: Return Terminal Failure (.agent/history/errors.jsonl)
87
+ alt Verification Gates Pass
88
+ Orc->>Git: Commit, Push & Log Telemetry (.agent/history/metrics.jsonl)
89
+ Orc->>CLI: Return Success (Exit 0)
90
+ else Verification Gates Fail
91
+ Git-->>Orc: Execution Trace (stdout/stderr / Diff)
92
+ Orc->>CLI: Log OODA Diagnostic Telemetry & Block PR (Exit 1)
105
93
  end
106
94
  ```
107
95
 
@@ -151,28 +139,29 @@ allow_paths: []
151
139
 
152
140
  > šŸ›”ļø **Zero-Trust Security Model**: `allow_paths` and `forbidden_paths` rules are read **strictly from the target base branch** (`origin/main`), never from untrusted PR branches. Even if an AI agent hallucinates and tries to modify its own security rules in a PR branch, the Orchestrator enforces the immutable rules defined on `main`.
153
141
  > ā„¹ļø Setting `build_cmd: ""` explicitly skips the build verification step (useful for pure test suites or scripts). Note that `.agent/jules.yml` uses a zero-dependency parser that supports flow (`[...]`) and block (`- item`) list subsets.
142
+ > šŸ“¦ `.github/` is intentionally included in `package.json`'s `files` array so that `npx jules-init` can automatically scaffold `.github/workflows/jules-audit.yml` into target repositories.
154
143
 
155
144
  ---
156
145
 
157
146
  <details>
158
147
  <summary><b>šŸ› ļø Supported Language Manifests & Workspace Graphs (15+ Tech Stacks)</b></summary>
159
148
 
160
- | Stack / Ecosystem | Manifest / Workspace File | Default Verification Command |
161
- |---|---|---|
162
- | **Turborepo** | `turbo.json` | `npx turbo run test --filter=<pkg>...` |
163
- | **pnpm Workspace** | `pnpm-workspace.yaml` | `pnpm --filter=...<pkg> test` |
164
- | **Nx Workspace** | `nx.json` | `npx nx run-many -t test -p <pkg> --with-deps` |
165
- | **Bun** | `bunfig.toml` / `bun.lockb` | `bun test && bun run build` |
166
- | **Deno** | `deno.json` / `deno.jsonc` | `deno test && deno task build` |
167
- | **JavaScript / TypeScript** | `package.json` | `npm run lint && npm test` (or `npm run check:all`) |
168
- | **Rust** | `Cargo.toml` | `cargo test -p <pkg>` / `cargo test --workspace` |
169
- | **Go** | `go.mod` | `go test ./... && go build ./...` |
170
- | **Python** | `pyproject.toml` / `requirements.txt` | `pytest` |
171
- | **Elixir** | `mix.exs` | `mix test && mix compile` |
172
- | **Ruby** | `Gemfile` | `bundle exec rake test` |
173
- | **Swift** | `Package.swift` | `swift test && swift build` |
174
- | **Java (Maven/Gradle)** | `pom.xml` / `build.gradle` | `mvn test` / `./gradlew test` |
175
- | **C / C++** | `Makefile` | `make test && make build` |
149
+ | Stack / Ecosystem | Manifest / Workspace File | Test Command (`testCmd`) | Build Command (`buildCmd`) |
150
+ |---|---|---|---|
151
+ | **Turborepo** | `turbo.json` | `npx turbo run test --filter=<pkg>...` | `npx turbo run build --filter=<pkg>...` |
152
+ | **pnpm Workspace** | `pnpm-workspace.yaml` | `pnpm --filter=...<pkg> test` | `pnpm --filter=...<pkg> build` |
153
+ | **Nx Workspace** | `nx.json` | `npx nx run-many -t test -p <pkg> --with-deps` | `npx nx run-many -t build -p <pkg> --with-deps` |
154
+ | **Bun** | `bunfig.toml` / `bun.lockb` | `bun test` | `bun run build` |
155
+ | **Deno** | `deno.json` / `deno.jsonc` | `deno test` | `deno task build` |
156
+ | **JavaScript / TypeScript** | `package.json` | `npm run lint && npm test` | `npm run build` |
157
+ | **Rust** | `Cargo.toml` | `cargo test --workspace` | `cargo build` |
158
+ | **Go** | `go.mod` | `go test ./...` | `go build ./...` |
159
+ | **Python** | `pyproject.toml` / `requirements.txt` | `pytest` | *(none)* |
160
+ | **Elixir** | `mix.exs` | `mix test` | `mix compile` |
161
+ | **Ruby** | `Gemfile` | `bundle exec rake test` | *(none)* |
162
+ | **Swift** | `Package.swift` | `swift test` | `swift build` |
163
+ | **Java (Maven/Gradle)** | `pom.xml` / `build.gradle` | `mvn test` / `./gradlew test` | `mvn compile` / `./gradlew assemble` |
164
+ | **C / C++** | `Makefile` | `make test` | `make build` |
176
165
 
177
166
  </details>
178
167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/FullThrottle83/jules-orchestrator-kit.git"
@@ -17,7 +17,10 @@
17
17
  "files": [
18
18
  "bin/",
19
19
  "scripts/",
20
- ".agent/",
20
+ ".agent/jules.yml",
21
+ ".agent/rules/",
22
+ ".agent/workflows/",
23
+ ".agent/jules-queue/README.md",
21
24
  ".github/",
22
25
  ".env.example",
23
26
  "JULES_RULES_TEMPLATE.md",
@@ -28,7 +28,7 @@ function loadEnv() {
28
28
  if (!trimmed || trimmed.startsWith("#")) continue;
29
29
  const eqIdx = trimmed.indexOf("=");
30
30
  if (eqIdx > 0) {
31
- const key = trimmed.slice(0, eqIdx).trim();
31
+ const key = trimmed.slice(0, eqIdx).trim().replace(/^export\s+/, "");
32
32
  let val = trimmed.slice(eqIdx + 1).trim();
33
33
  if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
34
34
  val = val.slice(1, -1);
@@ -162,9 +162,23 @@ const repo = process.env.JULES_REPO || "";
162
162
 
163
163
  if (isMainModule) {
164
164
  const possiblePath = path.resolve(process.cwd(), taskPromptArg);
165
- if (fs.existsSync(possiblePath) && fs.statSync(possiblePath).isFile()) {
166
- assertPathWithinWorkspace(possiblePath);
167
- rawPrompt = fs.readFileSync(possiblePath, "utf-8");
165
+ if (fs.existsSync(possiblePath)) {
166
+ try {
167
+ const verifiedPath = assertPathWithinWorkspace(possiblePath);
168
+ const fd = fs.openSync(verifiedPath, "r");
169
+ try {
170
+ const stat = fs.fstatSync(fd);
171
+ if (stat.isFile()) {
172
+ rawPrompt = fs.readFileSync(fd, "utf-8");
173
+ } else {
174
+ rawPrompt = taskPromptArg;
175
+ }
176
+ } finally {
177
+ fs.closeSync(fd);
178
+ }
179
+ } catch (_) {
180
+ rawPrompt = taskPromptArg;
181
+ }
168
182
  } else {
169
183
  rawPrompt = taskPromptArg;
170
184
  }
@@ -210,7 +224,9 @@ function cleanupTmp() {
210
224
  if (fs.existsSync(tmpDir)) {
211
225
  fs.rmSync(tmpDir, { recursive: true, force: true });
212
226
  }
213
- } catch (_) {}
227
+ } catch (err) {
228
+ console.warn(`āš ļø Failed to remove temporary payload directory at ${tmpDir}:`, err.message);
229
+ }
214
230
  }
215
231
 
216
232
  async function executeDispatch() {
@@ -110,7 +110,13 @@ function main() {
110
110
  }
111
111
 
112
112
  logNightlyHistory(results, isDryRun);
113
- console.log("\nāœ… Nightly Jules audit suite execution completed cleanly.");
113
+ const hasFailures = results.some((r) => r.status && (r.status.includes("Failed") || r.status.includes("error")));
114
+ if (hasFailures) {
115
+ console.error("\nāŒ Nightly Jules audit suite completed with task errors.");
116
+ process.exitCode = 1;
117
+ } else {
118
+ console.log("\nāœ… Nightly Jules audit suite execution completed cleanly.");
119
+ }
114
120
  }
115
121
 
116
122
  main();
@@ -11,6 +11,8 @@ const queueDir = path.resolve(process.cwd(), ".agent/jules-queue");
11
11
  const completedDir = path.resolve(process.cwd(), ".agent/jules-queue/completed");
12
12
  const queueLogFile = path.join(queueDir, "queue.jsonl");
13
13
 
14
+ const processingDir = path.resolve(process.cwd(), ".agent/jules-queue/.processing");
15
+
14
16
  if (!fs.existsSync(queueDir)) {
15
17
  console.error(`āŒ Queue directory not found: ${queueDir}`);
16
18
  process.exit(1);
@@ -20,6 +22,10 @@ if (!fs.existsSync(completedDir)) {
20
22
  fs.mkdirSync(completedDir, { recursive: true });
21
23
  }
22
24
 
25
+ if (!fs.existsSync(processingDir)) {
26
+ fs.mkdirSync(processingDir, { recursive: true });
27
+ }
28
+
23
29
  function logQueueState(file, status, error = null) {
24
30
  const entry = JSON.stringify({
25
31
  timestamp: new Date().toISOString(),
@@ -43,6 +49,16 @@ console.log(`šŸ Found ${files.length} tasks in the queue. Processing...`);
43
49
 
44
50
  files.forEach((file, index) => {
45
51
  const filePath = path.join(queueDir, file);
52
+ const processingPath = path.join(processingDir, file);
53
+
54
+ // Atomic claim: move from queueDir to processingDir before dispatching
55
+ try {
56
+ fs.renameSync(filePath, processingPath);
57
+ } catch (claimErr) {
58
+ // Another runner already claimed this task file
59
+ return;
60
+ }
61
+
46
62
  // Generate title from filename (e.g. "TASK-001-auth-spec.md" -> "TASK 001 auth spec")
47
63
  const title = file.replace(/\.md$/, "").replace(/-/g, " ");
48
64
 
@@ -51,13 +67,13 @@ files.forEach((file, index) => {
51
67
  logQueueState(file, "RUNNING");
52
68
 
53
69
  try {
54
- execFileSync("node", [dispatchScript, title, filePath], {
70
+ execFileSync("node", [dispatchScript, title, processingPath], {
55
71
  stdio: "inherit",
56
72
  });
57
73
 
58
74
  // Move to completed
59
75
  const destPath = path.join(completedDir, file);
60
- fs.renameSync(filePath, destPath);
76
+ fs.renameSync(processingPath, destPath);
61
77
  logQueueState(file, "COMPLETED");
62
78
  console.log(`āœ… Moved ${file} to completed/`);
63
79
  } catch (error) {
@@ -17,7 +17,9 @@ function runCommand(cmd, ignoreError = false) {
17
17
 
18
18
  export function matchGlob(filepath, globPattern) {
19
19
  const cleanPath = filepath.replace(/\\/g, "/").replace(/^\.\//, "");
20
- const segments = globPattern.split("/");
20
+ // Normalize consecutive glob wildcards (e.g., "**/**" -> "**")
21
+ const normalizedGlob = globPattern.replace(/\\/g, "/").replace(/(?:\*\*\/)+/g, "**/");
22
+ const segments = normalizedGlob.split("/");
21
23
  const regexParts = [];
22
24
  for (let i = 0; i < segments.length; i++) {
23
25
  const seg = segments[i];
@@ -33,12 +35,14 @@ export function matchGlob(filepath, globPattern) {
33
35
  }
34
36
  }
35
37
  let patternStr = regexParts.join("/");
36
- patternStr = patternStr.replace(/(^|\/)\*\*(\/|$)/g, (m, p1, p2) => {
37
- if (p1 === "/" && p2 === "/") return "(?:/|/.*/)";
38
- if (p1 === "/") return "(?:/.*)?";
39
- if (p2 === "/") return "(?:.*/)?";
38
+ patternStr = patternStr.replace(/(?:^|\/)\*\*(?:\/|$)/g, (m) => {
39
+ if (m === "**") return ".*";
40
+ if (m === "/**/") return "(?:/|/.*/)";
41
+ if (m === "/**") return "(?:/.*)?";
42
+ if (m === "**/") return "(?:.*/)?";
40
43
  return ".*";
41
44
  });
45
+ patternStr = patternStr.replace(/\*\*/g, ".*");
42
46
  return new RegExp(`^${patternStr}$`, "i").test(cleanPath);
43
47
  }
44
48
 
@@ -166,9 +170,9 @@ export function runSelfAudit() {
166
170
 
167
171
  console.log(`šŸ”— Merge-Base Hash: ${mergeBase}`);
168
172
 
169
- const rawDiffFiles = runCommand(`git diff --name-only ${mergeBase}...HEAD`)
170
- .split("\n")
171
- .map((f) => f.trim())
173
+ const rawDiffFiles = runCommand(`git diff -z --name-only ${mergeBase}...HEAD`)
174
+ .split("\0")
175
+ .map((f) => f.trim().replace(/^["']|["']$/g, ""))
172
176
  .filter(Boolean);
173
177
 
174
178
  const isBloatFile = (file) => /(\.lock|package-lock\.json|yarn\.lock|pnpm-lock\.yaml|\.png|\.jpg|\.jpeg|\.pdf|\.min\.js|\.map)$/i.test(file);
@@ -38,31 +38,70 @@ process.env.JULES_PROJECT_ROOT = process.cwd();
38
38
 
39
39
  console.log(`šŸ Launching Jules Swarm Orchestrator (${tasks.length} tasks, Concurrency: ${MAX_CONCURRENT}, Worktrees: ${USE_WORKTREES ? "ENABLED" : "DISABLED"})...`);
40
40
 
41
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
41
+ const activeWorktrees = new Set();
42
+
43
+ function cleanupAllWorktreesSync() {
44
+ for (const item of Array.from(activeWorktrees)) {
45
+ if (item.wtDir && fs.existsSync(item.wtDir)) {
46
+ try {
47
+ fs.chmodSync(item.wtDir, 0o755);
48
+ } catch (_) {}
49
+ }
50
+ try {
51
+ execFileSync("git", ["worktree", "remove", "--force", item.wtDir], { stdio: "ignore" });
52
+ } catch (_) {}
53
+ if (item.branchName) {
54
+ try {
55
+ execFileSync("git", ["branch", "-D", item.branchName], { stdio: "ignore" });
56
+ } catch (_) {}
57
+ }
58
+ }
59
+ activeWorktrees.clear();
60
+ }
61
+
62
+ process.on("SIGINT", () => {
63
+ console.warn("\nāš ļø SIGINT received. Cleaning up active Git worktrees...");
64
+ cleanupAllWorktreesSync();
65
+ process.exit(130);
66
+ });
67
+
68
+ process.on("SIGTERM", () => {
69
+ console.warn("\nāš ļø SIGTERM received. Cleaning up active Git worktrees...");
70
+ cleanupAllWorktreesSync();
71
+ process.exit(143);
72
+ });
42
73
 
43
74
  async function createWorktree(taskId) {
44
75
  const slug = taskId.toLowerCase().replace(/[^a-z0-9]+/g, "-");
45
76
  const wtDir = path.resolve(process.cwd(), `.agent/worktrees/${slug}`);
46
77
  const branchName = `jules/${slug}`;
78
+ let item = null;
47
79
  try {
48
80
  if (!fs.existsSync(path.dirname(wtDir))) {
49
81
  fs.mkdirSync(path.dirname(wtDir), { recursive: true });
50
82
  }
51
83
  await execFileAsync("git", ["worktree", "add", "-b", branchName, wtDir, "HEAD"]);
52
- return { wtDir, branchName };
84
+ item = { wtDir, branchName };
53
85
  } catch (err) {
54
86
  try {
55
87
  await execFileAsync("git", ["worktree", "add", "--force", wtDir, "HEAD"]);
56
- return { wtDir, branchName: null };
88
+ item = { wtDir, branchName: null };
57
89
  } catch (fallbackErr) {
58
90
  console.warn(`āš ļø Failed to create worktree for ${taskId}:`, fallbackErr.message);
59
91
  return null;
60
92
  }
61
93
  }
94
+ if (item) activeWorktrees.add(item);
95
+ return item;
62
96
  }
63
97
 
64
98
  async function removeWorktree(wtDir, branchName) {
65
99
  if (!wtDir) return;
100
+ for (const item of Array.from(activeWorktrees)) {
101
+ if (item.wtDir === wtDir) {
102
+ activeWorktrees.delete(item);
103
+ }
104
+ }
66
105
  try {
67
106
  await execFileAsync("git", ["worktree", "remove", "--force", wtDir]);
68
107
  } catch (err) {
@@ -1,9 +0,0 @@
1
- ---
2
- type: jules_dispatch
3
- title: "Test Swarm Task"
4
- timestamp: "2026-07-27T09:25:41.107Z"
5
- ---
6
- # Jules Task Dispatch: Test Swarm Task
7
-
8
- ## Prompt
9
- Test Prompt Description