jules-orchestrator-kit 0.42.0 → 0.52.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/index.mjs CHANGED
@@ -16,6 +16,7 @@ export {
16
16
  hasEncodedSecret,
17
17
  checkEdgeRuntimeImports,
18
18
  checkCrossPackageImports,
19
+ checkTestTampering,
19
20
  } from "./src/security.mjs";
20
21
  export { sanitizeUntrustedData, buildAgentEnvelope } from "./src/prompt-guard.mjs";
21
22
  export { isolateMcpStdout, writeMcpFrame } from "./src/mcp.mjs";
@@ -76,7 +77,26 @@ export {
76
77
  } from "./src/execution-envelope.mjs";
77
78
  export { checkAssetIntegrity } from "./src/asset-integrity.mjs";
78
79
  export { classifyRiskTier, RISK_TIERS } from "./src/risk.mjs";
79
- export { recordRemediation, queryRemediations } from "./src/remediation.mjs";
80
+ export { recordRemediation, queryRemediations, createThrashDetector, createWhackAMoleDetector } from "./src/remediation.mjs";
81
+ export {
82
+ runMutationTest,
83
+ generateMutants,
84
+ generateDiffMutants,
85
+ executeMutant,
86
+ getStringLiteralRanges,
87
+ getFileStringLiteralLineMap,
88
+ MUTATION_RULES,
89
+ isExcludedFromMutation,
90
+ } from "./src/mutation.mjs";
91
+ export {
92
+ runV8Coverage,
93
+ mapV8RangesToLines,
94
+ calculateDiffCoverage,
95
+ extractAddedLinesFromDiff,
96
+ isExcludedFromCoverage,
97
+ } from "./src/coverage.mjs";
98
+ export { runStabilityProbe } from "./src/stability.mjs";
99
+ export { measureEventLoopDelay } from "./src/perf.mjs";
80
100
  export { DagExecutor, DagCycleError } from "./src/dag-engine.mjs";
81
101
  export { journalIntent, journalDone, reapOrphanedIntents, reapStaleMutexDirs } from "./src/journal.mjs";
82
102
 
@@ -159,6 +179,11 @@ export {
159
179
  assertFileSize,
160
180
  assertFilePatterns,
161
181
  assertFileExists,
182
+ assertMutation,
183
+ assertTestIntegrity,
184
+ assertDiffCoverage,
185
+ assertTestStability,
186
+ assertEventLoopLag,
162
187
  runAssertion,
163
188
  formatBytes,
164
189
  resolveBytesLimit,
@@ -204,3 +229,13 @@ export {
204
229
  getHandoverDir,
205
230
  HandoverError,
206
231
  } from "./src/ops/handover.mjs";
232
+
233
+ // Jules Power-User Session Operations & Lifecycle Engine
234
+ export {
235
+ extractSessionPatch,
236
+ applySessionPatch,
237
+ retrySession,
238
+ pruneSessions,
239
+ parseAgeDuration,
240
+ } from "./src/session-ops.mjs";
241
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.42.0",
3
+ "version": "0.52.0",
4
4
  "description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for Google Jules (jules) autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { checkAssetIntegrity } from "../src/asset_integrity.mjs";
3
+ import { checkAssetIntegrity } from "../src/asset-integrity.mjs";
4
4
  import { existsSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { checkRulesBudget } from "../src/rules_budget.mjs";
3
+ import { checkRulesBudget } from "../src/rules-budget.mjs";
4
4
 
5
5
  const root = process.cwd();
6
6
  const res = checkRulesBudget(root);
@@ -3,6 +3,12 @@ import { resolve, relative, join, sep } from "node:path";
3
3
  import { gzipSync } from "node:zlib";
4
4
  import { checkAssetIntegrity } from "./asset-integrity.mjs";
5
5
  import { checkRulesBudget } from "./rules-budget.mjs";
6
+ import { runMutationTest } from "./mutation.mjs";
7
+ import { checkTestTampering } from "./security.mjs";
8
+ import { runV8Coverage, calculateDiffCoverage } from "./coverage.mjs";
9
+ import { runStabilityProbe } from "./stability.mjs";
10
+ import { measureEventLoopDelay } from "./perf.mjs";
11
+ import { diffText } from "./git.mjs";
6
12
 
7
13
  /**
8
14
  * Normalizes path to POSIX slashes.
@@ -487,6 +493,204 @@ export function assertFileExists(config = {}, root = process.cwd()) {
487
493
  };
488
494
  }
489
495
 
496
+ /**
497
+ * Asserts that mutation testing score meets minimum threshold.
498
+ */
499
+ export function assertMutation(config = {}, root = process.cwd()) {
500
+ const minScore = typeof config.minScore === "number" ? config.minScore : 80;
501
+ const maxMutants = typeof config.maxMutants === "number" ? config.maxMutants : 20;
502
+ const testCmd = config.testCmd || config.cmd || "npm test";
503
+
504
+ const report = runMutationTest({
505
+ root,
506
+ minScore,
507
+ maxMutants,
508
+ testCmd,
509
+ base: config.base || "main",
510
+ mode: config.mode || "working-tree",
511
+ files: Array.isArray(config.files) ? config.files : config.file ? [config.file] : undefined,
512
+ executor: config.executor,
513
+ });
514
+
515
+ const diagnostics = [];
516
+ if (!report.ok) {
517
+ diagnostics.push(
518
+ `Mutation score ${report.mutationScore}% below required threshold of ${minScore}% (${report.killedMutants}/${report.totalMutants} killed, ${report.survivedMutants} survived).`
519
+ );
520
+ for (const survivor of report.survivors) {
521
+ diagnostics.push(` - Survivor in ${survivor.mutant.file}:${survivor.mutant.line}: "${survivor.mutant.mutatedLine.trim()}" (${survivor.mutant.description})`);
522
+ }
523
+ }
524
+
525
+ return {
526
+ ok: report.ok,
527
+ mutationScore: report.mutationScore,
528
+ minScore: report.minScore,
529
+ totalMutants: report.totalMutants,
530
+ killedMutants: report.killedMutants,
531
+ survivedMutants: report.survivedMutants,
532
+ diagnostics,
533
+ metrics: {
534
+ mutationScore: report.mutationScore,
535
+ minScore: report.minScore,
536
+ totalMutants: report.totalMutants,
537
+ killedMutants: report.killedMutants,
538
+ survivedMutants: report.survivedMutants,
539
+ },
540
+ };
541
+ }
542
+
543
+ /**
544
+ * Asserts that no test file assertion weakening or skip injections occurred.
545
+ */
546
+ export function assertTestIntegrity(config = {}, root = process.cwd()) {
547
+ let diffStr = config.diffStr;
548
+ if (!diffStr) {
549
+ try {
550
+ diffStr = diffText(root, config.base || "main", config.mode || "working-tree");
551
+ } catch (_) {
552
+ diffStr = "";
553
+ }
554
+ }
555
+
556
+ const res = checkTestTampering(diffStr, config);
557
+ const diagnostics = (res.violations || []).map((v) => v.reason);
558
+
559
+ return {
560
+ ok: res.ok,
561
+ violations: res.violations || [],
562
+ diagnostics,
563
+ metrics: {
564
+ violationCount: res.violations?.length || 0,
565
+ },
566
+ };
567
+ }
568
+
569
+ /**
570
+ * Asserts that newly added implementation lines meet the minimum diff coverage percentage.
571
+ */
572
+ export function assertDiffCoverage(config = {}, root = process.cwd()) {
573
+ let diffStr = config.diffStr;
574
+ if (!diffStr) {
575
+ try {
576
+ diffStr = diffText(root, config.base || "main", config.mode || "working-tree");
577
+ } catch (_) {
578
+ diffStr = "";
579
+ }
580
+ }
581
+
582
+ const covRes = runV8Coverage(config.cmd || config.testCmd, {
583
+ root,
584
+ timeoutMs: config.timeoutMs,
585
+ });
586
+
587
+ const diffCov = calculateDiffCoverage(covRes.coverageByFile, diffStr, {
588
+ root,
589
+ minCoverage: typeof config.minCoverage === "number" ? config.minCoverage : (typeof config.min === "number" ? config.min : 100),
590
+ });
591
+
592
+ const diagnostics = [];
593
+ if (!diffCov.ok) {
594
+ diagnostics.push(diffCov.summary);
595
+ for (const [file, missed] of Object.entries(diffCov.missedByFile)) {
596
+ diagnostics.push(`Uncovered added lines in ${file}: lines ${missed.join(", ")}`);
597
+ }
598
+ }
599
+
600
+ return {
601
+ ok: diffCov.ok && covRes.ok,
602
+ score: diffCov.score,
603
+ minCoverage: diffCov.minCoverage,
604
+ totalLines: diffCov.totalLines,
605
+ coveredLines: diffCov.coveredLines,
606
+ missedLines: diffCov.missedLines,
607
+ missedByFile: diffCov.missedByFile,
608
+ diagnostics,
609
+ metrics: {
610
+ diffCoverageScore: diffCov.score,
611
+ uncoveredLinesCount: diffCov.missedLines,
612
+ },
613
+ };
614
+ }
615
+
616
+ /**
617
+ * Asserts that a test suite executes deterministically across multiple consecutive repetitions.
618
+ */
619
+ export function assertTestStability(config = {}, root = process.cwd()) {
620
+ const repeat = config.repeat || config.iterations || 5;
621
+ const minPassRate = typeof config.minPassRate === "number" ? config.minPassRate : 1.0;
622
+
623
+ const probe = runStabilityProbe(config.cmd || config.testCmd, {
624
+ root,
625
+ repeat,
626
+ minPassRate,
627
+ timeoutMs: config.timeoutMs,
628
+ });
629
+
630
+ const diagnostics = [];
631
+ if (!probe.ok) {
632
+ diagnostics.push(probe.summary);
633
+ const failedRuns = probe.runs.filter((r) => !r.pass);
634
+ for (const r of failedRuns) {
635
+ diagnostics.push(`Iteration ${r.iteration} failed (exit ${r.exitCode})`);
636
+ }
637
+ }
638
+
639
+ return {
640
+ ok: probe.ok,
641
+ repeat: probe.repeat,
642
+ passes: probe.passes,
643
+ failures: probe.failures,
644
+ passRate: probe.passRate,
645
+ oscillation: probe.oscillation,
646
+ diagnostics,
647
+ metrics: {
648
+ passRate: probe.passRate,
649
+ oscillation: probe.oscillation,
650
+ failuresCount: probe.failures,
651
+ },
652
+ };
653
+ }
654
+
655
+ /**
656
+ * Asserts that Node.js Event Loop delay remains within acceptable thresholds during execution.
657
+ */
658
+ export function assertEventLoopLag(config = {}, root = process.cwd()) {
659
+ const maxDelayMs = config.maxDelayMs || config.maxMs || config.thresholdMs || 50;
660
+ const resolution = config.resolution || 10;
661
+
662
+ const res = measureEventLoopDelay(config.cmd || config.testCmd, {
663
+ root,
664
+ maxDelayMs,
665
+ resolution,
666
+ timeoutMs: config.timeoutMs,
667
+ });
668
+
669
+ const diagnostics = [];
670
+ if (!res.ok) {
671
+ diagnostics.push(res.summary);
672
+ if (res.exitCode !== 0) {
673
+ diagnostics.push(`Command failed with exit code ${res.exitCode}`);
674
+ } else {
675
+ diagnostics.push(`Event loop delay exceeded threshold: p99=${res.p99Ms}ms (max allowed: ${maxDelayMs}ms)`);
676
+ }
677
+ }
678
+
679
+ return {
680
+ ok: res.ok,
681
+ maxMs: res.maxMs,
682
+ p99Ms: res.p99Ms,
683
+ meanMs: res.meanMs,
684
+ thresholdMs: maxDelayMs,
685
+ diagnostics,
686
+ metrics: {
687
+ eventLoopP99Ms: res.p99Ms,
688
+ eventLoopMaxMs: res.maxMs,
689
+ eventLoopMeanMs: res.meanMs,
690
+ },
691
+ };
692
+ }
693
+
490
694
  /**
491
695
  * Dispatches and executes an assertion primitive.
492
696
  */
@@ -543,6 +747,46 @@ export function runAssertion(stage = {}, root = process.cwd()) {
543
747
  break;
544
748
  }
545
749
 
750
+ case "mutation":
751
+ case "mutation-testing":
752
+ case "mutation-score":
753
+ case "mutation_score":
754
+ res = assertMutation(stage, root);
755
+ break;
756
+
757
+ case "test-integrity":
758
+ case "anti-tamper":
759
+ case "test-tamper":
760
+ case "test_integrity":
761
+ res = assertTestIntegrity(stage, root);
762
+ break;
763
+
764
+ case "diff-coverage":
765
+ case "diff_coverage":
766
+ case "coverage":
767
+ case "patch-coverage":
768
+ case "patch_coverage":
769
+ case "v8-coverage":
770
+ res = assertDiffCoverage(stage, root);
771
+ break;
772
+
773
+ case "test-stability":
774
+ case "test_stability":
775
+ case "stability":
776
+ case "flaky-probe":
777
+ case "stability-probe":
778
+ res = assertTestStability(stage, root);
779
+ break;
780
+
781
+ case "event-loop-lag":
782
+ case "event-loop-delay":
783
+ case "event_loop_lag":
784
+ case "event_loop_delay":
785
+ case "perf-lag":
786
+ case "perf_lag":
787
+ res = assertEventLoopLag(stage, root);
788
+ break;
789
+
546
790
  case "exists":
547
791
  case "file-exists":
548
792
  case "not-exists": {
package/src/config.mjs CHANGED
@@ -79,6 +79,31 @@ export function normalizePath(p) {
79
79
  return p.split(sep).join("/").replace(/\\/g, "/");
80
80
  }
81
81
 
82
+ /**
83
+ * True when a path is a Windows absolute path and therefore can never name a
84
+ * file inside a repository checked out to a normal relative location.
85
+ *
86
+ * Covered spellings (after `normalizePath` folds `\` to `/`):
87
+ *
88
+ * - Drive-qualified: `C:/...`, `C:\...`
89
+ * - Drive-relative: `C:foo` — on Windows this resolves against the *current
90
+ * directory of drive C:*, which is never the repository root and so is an
91
+ * escape even without a leading slash.
92
+ * - UNC: `//server/share`, `\\server\share`
93
+ *
94
+ * A POSIX absolute path (`/etc/passwd`) is intentionally NOT matched here:
95
+ * the existing `startsWith("/")` check already covers it, and the two spellings
96
+ * need to stay distinguishable in messages.
97
+ *
98
+ * @param {string} p
99
+ * @returns {boolean}
100
+ */
101
+ export function isWindowsAbsolutePath(p) {
102
+ if (!p || typeof p !== "string") return false;
103
+ const norm = normalizePath(p);
104
+ return /^[A-Za-z]:/.test(norm) || norm.startsWith("//");
105
+ }
106
+
82
107
  /**
83
108
  * Reduces a repo-relative path to a canonical form for pattern matching:
84
109
  * separators normalised, duplicate slashes collapsed, `.` segments dropped,