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/.agent/prompts/A11y.md +23 -0
- package/.agent/prompts/Alchemist.md +23 -0
- package/.agent/prompts/Scribe.md +23 -0
- package/.agent/prompts/Spectator.md +24 -0
- package/JULES_RULES_TEMPLATE.md +10 -8
- package/README.md +11 -3
- package/bin/agentctl.mjs +428 -1
- package/index.mjs +36 -1
- package/package.json +1 -1
- package/scripts/asset-integrity-check.mjs +1 -1
- package/scripts/rules-lint.mjs +1 -1
- package/src/assertions.mjs +244 -0
- package/src/config.mjs +25 -0
- package/src/coverage.mjs +328 -0
- package/src/engine.mjs +20 -4
- package/src/git.mjs +131 -9
- package/src/mcp.mjs +277 -0
- package/src/mutation.mjs +739 -0
- package/src/ops/checkpoint.mjs +31 -3
- package/src/perf.mjs +92 -0
- package/src/provider.mjs +144 -6
- package/src/remediation.mjs +154 -1
- package/src/rules-budget.mjs +8 -2
- package/src/scaffold.mjs +1 -1
- package/src/security.mjs +286 -28
- package/src/session-ops.mjs +312 -0
- package/src/stability.mjs +86 -0
- package/src/web-templates.mjs +172 -0
- package/src/asset_integrity.mjs +0 -1
- package/src/execution_envelope.mjs +0 -1
- package/src/rules_budget.mjs +0 -1
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.
|
|
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",
|
package/scripts/rules-lint.mjs
CHANGED
package/src/assertions.mjs
CHANGED
|
@@ -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,
|