jules-orchestrator-kit 0.66.0 → 0.67.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/README.md CHANGED
@@ -207,7 +207,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
207
207
  * **Fail-Closed Security & Secret Redaction:** Evaluates explicit Deny rules before Allow rules against canonicalized, case-folded paths. Redacts high-entropy keys and base64-encoded credentials (such as Kubernetes `Secret` manifests).
208
208
  * **Complexity & Cost Router:** Zero-dependency heuristic classifier (`src/router.mjs`) routing mechanical tasks to lightweight models while reserving primary models for complex refactors, with a `node --check` syntax-verification gate that transparently escalates a FAST-tier result to the primary provider if it left broken JS on disk.
209
209
  * **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
210
- * **Verified Test Suite:** Tested with **1133 unit tests across 163 suites passing in < 15.0s**.
210
+ * **Verified Test Suite:** Tested with **1154 unit tests across 166 suites passing in < 15.0s**.
211
211
 
212
212
  <br/>
213
213
 
package/bin/agentctl.mjs CHANGED
@@ -425,7 +425,17 @@ async function main() {
425
425
  console.log(`-----------------------------------------------------`);
426
426
  for (const p of res.phases) {
427
427
  const status = p.ok ? "✅ PASS" : "❌ FAIL";
428
- console.log(` Phase [${p.phase.toUpperCase()}] : ${status}`);
428
+ // Test tampering shares this phase, and its exit code, with the
429
+ // secret scanner. The codes are frozen, but reporting an assertion
430
+ // deletion under a bare "SECRETS" heading sent operators looking for
431
+ // a credential to rotate. Name what actually failed.
432
+ const tamperOnly =
433
+ p.phase === "secrets" &&
434
+ !p.ok &&
435
+ (p.findings || []).length > 0 &&
436
+ (p.findings || []).every((f) => f.type !== "SECRET_DETECTED" && !/SECRET/i.test(f.type || ""));
437
+ const label = tamperOnly ? "SECRETS (test integrity — no secret found)" : p.phase.toUpperCase();
438
+ console.log(` Phase [${label}] : ${status}`);
429
439
  if (p.violations) {
430
440
  p.violations.forEach((v) => console.log(` - Violation: ${v.file} (Rule: ${v.rule})`));
431
441
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for autonomous coding agents — Google Jules, Claude Code, Codex and Gemini CLI.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,6 +56,7 @@ import {
56
56
  SCOPE_CANARIES,
57
57
  INNOCENT_EDITS,
58
58
  UNREADABLE_DIALECTS,
59
+ CROSS_FILE_CASES,
59
60
  } from "../src/guard-policy.mjs";
60
61
 
61
62
  /**
@@ -185,6 +186,29 @@ const canaryResults = new Map();
185
186
  );
186
187
  }
187
188
 
189
+ {
190
+ const wrong = [];
191
+ for (const c of CROSS_FILE_CASES) {
192
+ const lines = [];
193
+ for (const f of c.files) {
194
+ lines.push(`--- a/${f.file}`, `+++ b/${f.file}`, "@@ -1,20 +1,20 @@", ` ${c.context}`);
195
+ for (const l of f.removed) lines.push(`-${l}`);
196
+ for (const l of f.added) lines.push(`+${l}`);
197
+ lines.push(` ${c.context}`);
198
+ }
199
+ const got = (checkTestTampering(lines.join("\n")).violations || []).map((v) => v.type).sort();
200
+ const want = [...c.expect].sort();
201
+ if (JSON.stringify(got) !== JSON.stringify(want)) {
202
+ wrong.push(`${c.id}: got ${JSON.stringify(got)}, policy says ${JSON.stringify(want)} (${c.why})`);
203
+ }
204
+ }
205
+ add(
206
+ "a move across files is judged as a move",
207
+ wrong.length === 0,
208
+ wrong.length ? wrong.join("; ") : `${CROSS_FILE_CASES.length} two-file diffs judged as declared`
209
+ );
210
+ }
211
+
188
212
  // --- 4. Predicate mutants ---------------------------------------------------
189
213
  {
190
214
  const survivors = [];
@@ -1,11 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import { readdirSync } from "node:fs";
2
+ import { readdirSync, existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
5
  import { cpus } from "node:os";
6
6
 
7
7
  const root = process.cwd();
8
8
  const testDir = join(root, "test");
9
+
10
+ // The published package ships `scripts/` and not `test/`, so running
11
+ // `npm test` inside an installed copy crashed with a raw
12
+ // `ENOENT: no such file or directory, scandir '.../test'`. The suite is not
13
+ // missing; it was never part of the tarball. Say that, rather than leaving
14
+ // someone to work it out from a stack trace.
15
+ if (!existsSync(testDir)) {
16
+ console.error("No test/ directory here.");
17
+ console.error("");
18
+ console.error("If this is an installed copy of jules-orchestrator-kit, that is expected:");
19
+ console.error("the test suite is not part of the published package. Clone the repository");
20
+ console.error("to run it: git clone https://github.com/FullThrottle83/jules-orchestrator-kit");
21
+ console.error("");
22
+ console.error("To check an installed copy instead, run: npm run guard-reach");
23
+ process.exit(1);
24
+ }
25
+
9
26
  const testFiles = readdirSync(testDir)
10
27
  .filter((f) => f.endsWith(".test.mjs"))
11
28
  .sort()
@@ -235,6 +235,58 @@ export const TAMPER_CANARIES = [
235
235
  added: [" $this->markTestSkipped(\"later\");"],
236
236
  expect: "TEST_SKIP_INJECTION",
237
237
  },
238
+ // Skipping from inside the test body, which is how every one of these
239
+ // ecosystems actually does it. The decorator and annotation forms were
240
+ // covered; the standard library's own method was not, so a test could be
241
+ // silenced with the most ordinary call in the language.
242
+ {
243
+ id: "skip-injection/unittest-body",
244
+ file: "tests/test_app.py",
245
+ context: "# app",
246
+ removed: [],
247
+ added: [' self.skipTest("disabled")'],
248
+ expect: "TEST_SKIP_INJECTION",
249
+ },
250
+ {
251
+ id: "skip-injection/pytest-body",
252
+ file: "tests/test_app.py",
253
+ context: "# app",
254
+ removed: [],
255
+ added: [' pytest.skip("not now")'],
256
+ expect: "TEST_SKIP_INJECTION",
257
+ },
258
+ {
259
+ id: "skip-injection/raise-skiptest",
260
+ file: "tests/test_app.py",
261
+ context: "# app",
262
+ removed: [],
263
+ added: [' raise unittest.SkipTest("nope")'],
264
+ expect: "TEST_SKIP_INJECTION",
265
+ },
266
+ {
267
+ id: "skip-injection/mocha-body",
268
+ file: "test/app.test.js",
269
+ context: "// app",
270
+ removed: [],
271
+ added: [" this.skip();"],
272
+ expect: "TEST_SKIP_INJECTION",
273
+ },
274
+ {
275
+ id: "skip-injection/jest-todo",
276
+ file: "test/app.test.js",
277
+ context: "// app",
278
+ removed: [],
279
+ added: [' test.todo("adds two numbers");'],
280
+ expect: "TEST_SKIP_INJECTION",
281
+ },
282
+ {
283
+ id: "skip-injection/go-skipnow",
284
+ file: "internal/calc/calc_test.go",
285
+ context: "// calc",
286
+ removed: [],
287
+ added: ["\tt.SkipNow()"],
288
+ expect: "TEST_SKIP_INJECTION",
289
+ },
238
290
  {
239
291
  id: "skip-injection/xctest",
240
292
  file: "Tests/CalcTests/CalcTests.swift",
@@ -551,3 +603,57 @@ export const MULTILINE_CANARIES = [
551
603
  expect: "ASSERTION_EXPECTATION_CHANGED",
552
604
  },
553
605
  ];
606
+
607
+ /**
608
+ * Diffs that touch two files at once, where the verdict depends on both.
609
+ *
610
+ * Assertion tracking is per file, so moving a test between files — ordinary
611
+ * refactoring, done constantly — read as deleting every assertion in it and
612
+ * was reported as CRITICAL tampering. The assertion still exists and still
613
+ * runs. What the removal check is for is verification that *disappeared*.
614
+ *
615
+ * `expect: []` means the whole diff must produce no finding.
616
+ */
617
+ export const CROSS_FILE_CASES = [
618
+ {
619
+ id: "move-a-test-between-files",
620
+ context: "# ctx",
621
+ files: [
622
+ {
623
+ file: "test/test_app.py",
624
+ removed: [" def test_setattr(self):", " self.assertEqual(5, app.test)", " self.assertRaises(AttributeError, setattr, app, 't', 6)"],
625
+ added: [],
626
+ },
627
+ {
628
+ file: "test/test_html.py",
629
+ removed: [],
630
+ added: [" def test_setattr_moved(self):", " self.assertEqual(5, app.test)", " self.assertRaises(AttributeError, setattr, app, 't', 6)"],
631
+ },
632
+ ],
633
+ expect: [],
634
+ why: "a move is not a removal",
635
+ },
636
+ {
637
+ id: "delete-a-test-outright",
638
+ context: "# ctx",
639
+ files: [
640
+ {
641
+ file: "test/test_app.py",
642
+ removed: [" def test_setattr(self):", " self.assertEqual(5, app.test)", " self.assertRaises(AttributeError, setattr, app, 't', 6)"],
643
+ added: [],
644
+ },
645
+ ],
646
+ expect: ["ASSERTION_REMOVAL", "ASSERTION_REMOVAL"],
647
+ why: "nothing caught it on the way, so verification really did disappear",
648
+ },
649
+ {
650
+ id: "change-it-on-the-way-across",
651
+ context: "# ctx",
652
+ files: [
653
+ { file: "test/test_app.py", removed: [" self.assertEqual(5, app.test)"], added: [] },
654
+ { file: "test/test_html.py", removed: [], added: [" self.assertEqual(99, app.test)"] },
655
+ ],
656
+ expect: ["ASSERTION_REMOVAL"],
657
+ why: "what arrived is a different claim; only an exact arrival is a move",
658
+ },
659
+ ];
package/src/security.mjs CHANGED
@@ -2195,6 +2195,17 @@ export function checkTestTampering(diffOrText = "", options = {}) {
2195
2195
  { pattern: /\b(?:xit|xdescribe|xcontext|xspecify)\b\s*["\x27]/i, desc: "Injected RSpec disabled example (xit)" },
2196
2196
  { pattern: /,\s*skip:\s*(?:true|["\x27])/i, desc: "Injected RSpec skip metadata (skip:)" },
2197
2197
  { pattern: /^\s*(?:skip|pending)\s*(?:["\x27(]|$)/i, desc: "Injected Minitest/RSpec skip statement" },
2198
+ // Skipping from inside the body, which is how these ecosystems actually
2199
+ // do it. Only the decorator and annotation forms were covered, so a test
2200
+ // could be silenced with the standard library's own method and the guard
2201
+ // said nothing: measured silent on six of seven in-body forms.
2202
+ { pattern: /\bself\.skipTest\s*\(/i, desc: "Injected unittest skip (self.skipTest())" },
2203
+ { pattern: /\braise\s+(?:unittest\.)?SkipTest\b/i, desc: "Injected unittest skip (raise SkipTest)" },
2204
+ { pattern: /\bpytest\.skip\s*\(/i, desc: "Injected Pytest skip call (pytest.skip())" },
2205
+ { pattern: /\bpytest\.xfail\s*\(/i, desc: "Injected Pytest expected-failure (pytest.xfail())" },
2206
+ { pattern: /\bthis\.skip\s*\(/i, desc: "Injected Mocha skip (this.skip())" },
2207
+ { pattern: /\b(?:it|test|describe|context)\.todo\s*\(/i, desc: "Injected todo placeholder (test.todo())" },
2208
+ { pattern: /\bt\.Skip(?:Now|f)?\s*\(/, desc: "Injected Go test skip (t.Skip/t.Skipf/t.SkipNow)" },
2198
2209
  ];
2199
2210
 
2200
2211
  // `#` and `--` belong here for the same reason the dialects belong in
@@ -2369,6 +2380,26 @@ export function checkTestTampering(diffOrText = "", options = {}) {
2369
2380
  }
2370
2381
  }
2371
2382
 
2383
+ // Assertions added in some *other* file of this same diff.
2384
+ //
2385
+ // Tracking is strictly per file, so moving a test from one file to another
2386
+ // — ordinary refactoring — read as deleting three assertions and was
2387
+ // reported as CRITICAL tampering. The assertion still exists and still
2388
+ // runs; it is in a different file. What the removal check is for is
2389
+ // verification that *disappeared*, and this has not.
2390
+ //
2391
+ // Deliberately exact on the assertion text: something that changed on the
2392
+ // way across is not a move, and is judged normally.
2393
+ const movedIn = new Map();
2394
+ for (const [file, stats] of fileAssertions.entries()) {
2395
+ for (const t of stats.addedTexts || []) {
2396
+ const key = collapseWhitespace(String(t.text ?? t)).trim();
2397
+ if (!key) continue;
2398
+ if (!movedIn.has(key)) movedIn.set(key, []);
2399
+ movedIn.get(key).push(file);
2400
+ }
2401
+ }
2402
+
2372
2403
  for (const [file, stats] of fileAssertions.entries()) {
2373
2404
  // An expectation that was rewritten rather than removed.
2374
2405
  //
@@ -2386,6 +2417,15 @@ export function checkTestTampering(diffOrText = "", options = {}) {
2386
2417
  if (stats.removed.length > stats.added) {
2387
2418
  const unreplaced = stats.removed.slice(stats.added);
2388
2419
  for (const item of unreplaced) {
2420
+ // Did this exact assertion arrive somewhere else in the same diff?
2421
+ const key = collapseWhitespace(item.text).trim();
2422
+ const landing = movedIn.get(key);
2423
+ const elsewhere = landing ? landing.findIndex((f) => f !== file) : -1;
2424
+ if (elsewhere !== -1) {
2425
+ // Consume the landing so two removals cannot both claim one arrival.
2426
+ landing.splice(elsewhere, 1);
2427
+ continue;
2428
+ }
2389
2429
  violations.push({
2390
2430
  file,
2391
2431
  line: item.line,