jules-orchestrator-kit 0.29.1 → 0.32.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 +169 -86
- package/bin/agentctl.mjs +267 -4
- package/bin/mcp-server.mjs +0 -0
- package/index.mjs +21 -1
- package/package.json +1 -1
- package/scripts/stale-base-check.mjs +2 -2
- package/src/config.mjs +18 -2
- package/src/dag-engine.mjs +76 -0
- package/src/engine.mjs +331 -32
- package/src/envelope.mjs +32 -2
- package/src/execution_envelope.mjs +9 -1
- package/src/git.mjs +41 -0
- package/src/mcp.mjs +34 -0
- package/src/memory.mjs +148 -0
- package/src/ops/checkpoint.mjs +167 -0
- package/src/ops/command-registry.mjs +274 -0
- package/src/ops/doctor-planner.mjs +214 -0
- package/src/ops/doctor-registry.mjs +442 -0
- package/src/ops/ide-scaffold.mjs +139 -0
- package/src/ops/receipts.mjs +171 -0
- package/src/ops/swarm-actions.mjs +156 -0
- package/src/ops/task-actions.mjs +214 -0
- package/src/ops/tdd-generator.mjs +115 -0
- package/src/ops/transaction.mjs +278 -0
- package/src/prompt-guard.mjs +8 -1
- package/src/provider.mjs +164 -8
- package/src/remediation.mjs +164 -0
- package/src/security.mjs +14 -6
- package/src/stack-detector.mjs +15 -5
- package/src/state.mjs +71 -7
- package/src/task-optimizer.mjs +306 -0
- package/src/telemetry.mjs +35 -2
- package/src/tui.mjs +2 -6
- package/src/ux/capabilities.mjs +180 -0
- package/src/ux/diff-viewer.mjs +289 -0
- package/src/ux/key-decoder.mjs +281 -0
- package/src/ux/layout.mjs +195 -0
- package/src/ux/log-viewer.mjs +110 -0
- package/src/ux/palette.mjs +179 -0
- package/src/ux/queue-model.mjs +206 -0
- package/src/ux/renderer.mjs +188 -0
- package/src/ux/swarm-model.mjs +146 -0
- package/src/ux/terminal-session.mjs +215 -0
- package/src/ux/widgets.mjs +323 -0
- package/src/webhook.mjs +120 -0
- package/src/wizard-task.mjs +6 -0
package/bin/agentctl.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
6
|
import { loadConfig, resolveRoot, detectStack, bootstrapZeroTestRepo } from "../src/config.mjs";
|
|
7
7
|
import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
|
|
8
8
|
import { acquireLock, releaseLock, lockStatus, checkDailyBudget, getQueueDir } from "../src/state.mjs";
|
|
@@ -14,7 +14,7 @@ const command = args[0];
|
|
|
14
14
|
|
|
15
15
|
function printHelp() {
|
|
16
16
|
console.log(`
|
|
17
|
-
🚀 agentctl v0.
|
|
17
|
+
🚀 agentctl v0.32.0 — Universal Agent Orchestrator & Safety Gatekeeper
|
|
18
18
|
|
|
19
19
|
Usage: agentctl <command> [options]
|
|
20
20
|
|
|
@@ -32,8 +32,16 @@ Commands:
|
|
|
32
32
|
dashboard Start local HTTP telemetry and audit dashboard
|
|
33
33
|
init Scaffold .agent/ config and run onboarding wizard
|
|
34
34
|
task create Interactively author and scope a Jules task envelope
|
|
35
|
+
task optimize Linter & optimizer for Jules task prompts (--fix, --json)
|
|
36
|
+
test-gen Scaffold & run automated TDD Red-to-Green test cycle (--run)
|
|
37
|
+
mcp init Scaffold IDE integration config (cursor | vscode | claude | all)
|
|
38
|
+
rollback Restore git state & working tree to atomic pre-flight checkpoint
|
|
39
|
+
resume Resume warm session with human response (--response "<text>")
|
|
35
40
|
status Display queue and system status summary
|
|
36
41
|
scan Scan codebase for TODO/FIXME task candidates
|
|
42
|
+
hydrate [prompt] Prepend active system learnings and baton-pass state to a prompt
|
|
43
|
+
harvest Harvest failure traces and record/quarantine resolution rules
|
|
44
|
+
learning add Record a system learning rule into .agent/knowledge/
|
|
37
45
|
version Output agentctl version
|
|
38
46
|
|
|
39
47
|
Options:
|
|
@@ -54,7 +62,7 @@ async function main() {
|
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
57
|
-
console.log("agentctl v0.
|
|
65
|
+
console.log("agentctl v0.32.0");
|
|
58
66
|
process.exit(0);
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -391,8 +399,66 @@ async function main() {
|
|
|
391
399
|
console.log(` Auto-PR : ${res.plan.flags.autoPr}`);
|
|
392
400
|
}
|
|
393
401
|
process.exit(0);
|
|
402
|
+
} else if (subCommand === "optimize") {
|
|
403
|
+
const { values, positionals } = parseArgs({
|
|
404
|
+
args: args.slice(2),
|
|
405
|
+
options: {
|
|
406
|
+
fix: { type: "boolean", short: "f" },
|
|
407
|
+
file: { type: "string" },
|
|
408
|
+
dir: { type: "string", short: "d" },
|
|
409
|
+
json: { type: "boolean", short: "j" },
|
|
410
|
+
"verify-cmd": { type: "string", short: "v" },
|
|
411
|
+
},
|
|
412
|
+
allowPositionals: true,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const { scorePromptFalsifiability, optimizeTaskPrompt } = await import("../src/task-optimizer.mjs");
|
|
416
|
+
const targetDir = values.dir ? resolve(values.dir) : root;
|
|
417
|
+
let promptText = positionals.join(" ");
|
|
418
|
+
|
|
419
|
+
if (values.file) {
|
|
420
|
+
if (existsSync(values.file)) {
|
|
421
|
+
promptText = readFileSync(values.file, "utf-8");
|
|
422
|
+
} else {
|
|
423
|
+
console.error(`Error: File '${values.file}' does not exist.`);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (values.fix) {
|
|
429
|
+
const opt = optimizeTaskPrompt(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] });
|
|
430
|
+
if (values.json) {
|
|
431
|
+
console.log(JSON.stringify(opt, null, 2));
|
|
432
|
+
} else {
|
|
433
|
+
console.log(opt.optimizedPrompt);
|
|
434
|
+
}
|
|
435
|
+
process.exit(0);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const analysis = scorePromptFalsifiability(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] });
|
|
439
|
+
if (values.json) {
|
|
440
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
441
|
+
} else {
|
|
442
|
+
console.log(`\n🎯 Task Prompt Falsifiability Analysis`);
|
|
443
|
+
console.log(`--------------------------------------------------`);
|
|
444
|
+
console.log(` Score / Grade : ${analysis.score} / 100 (${analysis.grade})`);
|
|
445
|
+
console.log(` Falsifiable : ${analysis.isFalsifiable ? "✅ YES" : "❌ NO"}`);
|
|
446
|
+
if (analysis.oracle.command) {
|
|
447
|
+
console.log(` Oracle Command : "${analysis.oracle.command}" (${analysis.oracle.autoDetected ? "Auto-detected" : "User-supplied"})`);
|
|
448
|
+
}
|
|
449
|
+
if (analysis.issues.length > 0) {
|
|
450
|
+
console.log(`\n Issues Identified (${analysis.issues.length}):`);
|
|
451
|
+
analysis.issues.forEach((i) => console.log(` - [${i.type}] ${i.message} (-${i.penalty} pts)`));
|
|
452
|
+
}
|
|
453
|
+
if (analysis.suggestions.length > 0) {
|
|
454
|
+
console.log(`\n Suggestions for Improvement:`);
|
|
455
|
+
analysis.suggestions.forEach((s) => console.log(` - ${s}`));
|
|
456
|
+
}
|
|
457
|
+
console.log(`--------------------------------------------------\n`);
|
|
458
|
+
}
|
|
459
|
+
process.exit(analysis.isFalsifiable ? 0 : 1);
|
|
394
460
|
} else {
|
|
395
|
-
console.error(`Unknown task subcommand '${subCommand}'. Supported: agentctl task create`);
|
|
461
|
+
console.error(`Unknown task subcommand '${subCommand}'. Supported: agentctl task create, agentctl task optimize`);
|
|
396
462
|
process.exit(1);
|
|
397
463
|
}
|
|
398
464
|
break;
|
|
@@ -427,12 +493,209 @@ async function main() {
|
|
|
427
493
|
break;
|
|
428
494
|
}
|
|
429
495
|
|
|
496
|
+
case "rollback": {
|
|
497
|
+
const { restoreCheckpoint } = await import("../src/ops/checkpoint.mjs");
|
|
498
|
+
const targetId = args[1] || "--latest";
|
|
499
|
+
try {
|
|
500
|
+
const res = restoreCheckpoint(targetId, { root });
|
|
501
|
+
console.log(`\n✅ Git Checkpoint Restored Successfully!`);
|
|
502
|
+
console.log(` Session ID : ${res.id}`);
|
|
503
|
+
console.log(` HEAD SHA : ${res.headSha || "N/A"}`);
|
|
504
|
+
console.log(` RestoredAt : ${res.restoredAt}\n`);
|
|
505
|
+
process.exit(0);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
console.error(`❌ Rollback Failed: ${err.message}`);
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
case "resume": {
|
|
514
|
+
const { createProvider } = await import("../src/provider.mjs");
|
|
515
|
+
const sessionId = args[1];
|
|
516
|
+
const { values } = parseArgs({
|
|
517
|
+
args: args.slice(1),
|
|
518
|
+
options: {
|
|
519
|
+
response: { type: "string", short: "r" },
|
|
520
|
+
"dry-run": { type: "boolean", short: "d" },
|
|
521
|
+
json: { type: "boolean", short: "j" },
|
|
522
|
+
},
|
|
523
|
+
allowPositionals: true,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (!sessionId || sessionId.startsWith("-")) {
|
|
527
|
+
console.error("Error: Session ID is required for agentctl resume <sessionId>.");
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const responseText = values.response || args.slice(2).join(" ");
|
|
532
|
+
if (!responseText) {
|
|
533
|
+
console.error("Error: --response text is required to resume warm session.");
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
538
|
+
try {
|
|
539
|
+
const res = await provider.resume(sessionId, responseText, { root, dryRun: values["dry-run"] });
|
|
540
|
+
if (values.json) {
|
|
541
|
+
console.log(JSON.stringify(res, null, 2));
|
|
542
|
+
} else {
|
|
543
|
+
console.log(`\n✅ Warm Session Resumed Successfully!`);
|
|
544
|
+
console.log(` Session ID : ${res.id}`);
|
|
545
|
+
console.log(` Status : ${res.status}\n`);
|
|
546
|
+
}
|
|
547
|
+
process.exit(0);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
console.error(`❌ Resume Failed: ${err.message}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
case "test-gen": {
|
|
556
|
+
const { scaffoldTddTest, runTddCycle } = await import("../src/ops/tdd-generator.mjs");
|
|
557
|
+
const { values } = parseArgs({
|
|
558
|
+
args: args.slice(1),
|
|
559
|
+
options: {
|
|
560
|
+
title: { type: "string", short: "t" },
|
|
561
|
+
spec: { type: "string", short: "s" },
|
|
562
|
+
run: { type: "boolean", short: "r" },
|
|
563
|
+
json: { type: "boolean", short: "j" },
|
|
564
|
+
},
|
|
565
|
+
allowPositionals: true,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const title = values.title || args[1] || "feature-spec";
|
|
569
|
+
const specText = values.spec || args.slice(2).join(" ") || "TDD requirement specification.";
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
if (values.run) {
|
|
573
|
+
const res = await runTddCycle({ title, spec: specText }, { root });
|
|
574
|
+
if (values.json) {
|
|
575
|
+
console.log(JSON.stringify(res, null, 2));
|
|
576
|
+
} else {
|
|
577
|
+
console.log(`\n🔴 TDD RED Check Verified!`);
|
|
578
|
+
console.log(` Test File : ${res.testFile}`);
|
|
579
|
+
console.log(` Scope Lock : Locked into scope.deny`);
|
|
580
|
+
console.log(` Status : Ready for green implementation dispatch\n`);
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
const scaffold = scaffoldTddTest({ title, spec: specText }, { root });
|
|
584
|
+
if (values.json) {
|
|
585
|
+
console.log(JSON.stringify(scaffold, null, 2));
|
|
586
|
+
} else {
|
|
587
|
+
console.log(`\n🧪 TDD Test Scaffolded Successfully!`);
|
|
588
|
+
console.log(` File Path : ${scaffold.relativePath}`);
|
|
589
|
+
console.log(` Command : ${scaffold.testCmd}\n`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
process.exit(0);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
console.error(`❌ TDD Generation Failed: ${err.message}`);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
|
|
430
600
|
case "mcp": {
|
|
601
|
+
const subAction = args[1];
|
|
602
|
+
if (subAction === "init") {
|
|
603
|
+
const { scaffoldIdeConfig } = await import("../src/ops/ide-scaffold.mjs");
|
|
604
|
+
const { values } = parseArgs({
|
|
605
|
+
args: args.slice(2),
|
|
606
|
+
options: {
|
|
607
|
+
target: { type: "string", short: "t", default: "all" },
|
|
608
|
+
json: { type: "boolean", short: "j" },
|
|
609
|
+
},
|
|
610
|
+
allowPositionals: true,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
const target = values.target || args[2] || "all";
|
|
614
|
+
try {
|
|
615
|
+
const res = scaffoldIdeConfig(target, { root });
|
|
616
|
+
if (values.json) {
|
|
617
|
+
console.log(JSON.stringify(res, null, 2));
|
|
618
|
+
} else {
|
|
619
|
+
console.log(`\n🔌 IDE MCP Config Scaffolded Successfully!`);
|
|
620
|
+
console.log(` Target : ${res.target}`);
|
|
621
|
+
for (const item of res.results) {
|
|
622
|
+
console.log(` - ${item.target.toUpperCase()} : ${item.file}`);
|
|
623
|
+
}
|
|
624
|
+
console.log("");
|
|
625
|
+
}
|
|
626
|
+
process.exit(0);
|
|
627
|
+
} catch (err) {
|
|
628
|
+
console.error(`❌ IDE MCP Scaffold Failed: ${err.message}`);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
431
633
|
const { startMcpServer } = await import("../src/mcp.mjs");
|
|
432
634
|
startMcpServer();
|
|
433
635
|
break;
|
|
434
636
|
}
|
|
435
637
|
|
|
638
|
+
case "hydrate": {
|
|
639
|
+
const { hydratePrompt } = await import("../src/memory.mjs");
|
|
640
|
+
const promptInput = args.slice(1).join(" ") || "";
|
|
641
|
+
const result = hydratePrompt(root, promptInput);
|
|
642
|
+
console.log(result);
|
|
643
|
+
process.exit(0);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
case "harvest": {
|
|
647
|
+
const { harvestFailure } = await import("../src/memory.mjs");
|
|
648
|
+
const { values } = parseArgs({
|
|
649
|
+
args: args.slice(1),
|
|
650
|
+
options: {
|
|
651
|
+
"exit-code": { type: "string" },
|
|
652
|
+
log: { type: "string" },
|
|
653
|
+
diff: { type: "string" },
|
|
654
|
+
task: { type: "string" },
|
|
655
|
+
agent: { type: "string" },
|
|
656
|
+
json: { type: "boolean", short: "j" },
|
|
657
|
+
},
|
|
658
|
+
allowPositionals: true,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const exitCode = parseInt(values["exit-code"] || "4", 10);
|
|
662
|
+
const logPath = values.log || "";
|
|
663
|
+
const diffText = values.diff || "";
|
|
664
|
+
const taskId = values.task || "unknown";
|
|
665
|
+
const agent = values.agent || "jules";
|
|
666
|
+
|
|
667
|
+
const res = harvestFailure(root, { exitCode, logPath, diffText, taskId, agent });
|
|
668
|
+
if (values.json) {
|
|
669
|
+
console.log(JSON.stringify(res, null, 2));
|
|
670
|
+
} else {
|
|
671
|
+
if (res.status === "HARVESTED") {
|
|
672
|
+
console.log(`🌾 Harvested failure candidate: ${res.candidate.trigger}`);
|
|
673
|
+
console.log(` Solution: ${res.candidate.solution}`);
|
|
674
|
+
} else {
|
|
675
|
+
console.log(`⚠️ Harvest rejected: ${res.reason}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
process.exit(0);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
case "learning": {
|
|
682
|
+
const subcmd = args[1];
|
|
683
|
+
if (subcmd === "add") {
|
|
684
|
+
const { recordLearning } = await import("../src/memory.mjs");
|
|
685
|
+
const trigger = args[2];
|
|
686
|
+
const solution = args[3];
|
|
687
|
+
if (!trigger || !solution) {
|
|
688
|
+
console.error('Usage: agentctl learning add "<trigger/symptom>" "<solution>"');
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
const res = recordLearning(root, { trigger, solution });
|
|
692
|
+
console.log(`✅ System learning recorded. Total learnings: ${res.count}`);
|
|
693
|
+
process.exit(0);
|
|
694
|
+
}
|
|
695
|
+
console.error('Usage: agentctl learning add "<trigger/symptom>" "<solution>"');
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
|
|
436
699
|
default:
|
|
437
700
|
console.error(`Unknown command: ${command}`);
|
|
438
701
|
printHelp();
|
package/bin/mcp-server.mjs
CHANGED
|
File without changes
|
package/index.mjs
CHANGED
|
@@ -61,7 +61,7 @@ export {
|
|
|
61
61
|
} from "./src/execution_envelope.mjs";
|
|
62
62
|
export { checkAssetIntegrity } from "./src/asset_integrity.mjs";
|
|
63
63
|
export { classifyRiskTier, RISK_TIERS } from "./src/risk.mjs";
|
|
64
|
-
export {
|
|
64
|
+
export { recordRemediation, queryRemediations } from "./src/remediation.mjs";
|
|
65
65
|
export { DagExecutor, DagCycleError } from "./src/dag-engine.mjs";
|
|
66
66
|
export { journalIntent, journalDone, reapOrphanedIntents, reapStaleMutexDirs } from "./src/journal.mjs";
|
|
67
67
|
|
|
@@ -85,4 +85,24 @@ export { planInit, loadPresets, runInitWizard, TIER_PROFILES, BUILTIN_PRESETS }
|
|
|
85
85
|
// Guided Task Authoring Subsystem
|
|
86
86
|
export { planTaskCreate, runTaskCreateWizard, GUARDRAIL_FOOTER } from "./src/wizard-task.mjs";
|
|
87
87
|
|
|
88
|
+
// Prompt Falsifiability & Task Optimizer Engine
|
|
89
|
+
export { scorePromptFalsifiability, optimizeTaskPrompt, levenshteinDistance, extractPathTokens } from "./src/task-optimizer.mjs";
|
|
90
|
+
|
|
91
|
+
// Atomic Git Checkpoints & Rollback
|
|
92
|
+
export { createCheckpoint, restoreCheckpoint, listCheckpoints, pruneCheckpoints, CheckpointError } from "./src/ops/checkpoint.mjs";
|
|
93
|
+
|
|
94
|
+
// Webhook & HITL Escalation Bridge
|
|
95
|
+
export { dispatchEscalation, verifySignature, parseWebhookPayload, routeWebhookEvent, createWebhookServer } from "./src/webhook.mjs";
|
|
96
|
+
|
|
97
|
+
// PR Review Evidence Bundler & Dev Server Probe
|
|
98
|
+
export { synthesizePrDescription, probeDevServer } from "./src/engine.mjs";
|
|
99
|
+
|
|
100
|
+
// Automated TDD Red-to-Green Harness
|
|
101
|
+
export { scaffoldTddTest, runTddCycle, TddError } from "./src/ops/tdd-generator.mjs";
|
|
102
|
+
|
|
103
|
+
// IDE Native MCP Scaffolder
|
|
104
|
+
export { scaffoldIdeConfig, IdeScaffoldError } from "./src/ops/ide-scaffold.mjs";
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
88
108
|
|
package/package.json
CHANGED
|
@@ -21,6 +21,6 @@ try {
|
|
|
21
21
|
console.log("✅ Stale-base predicate check passed.");
|
|
22
22
|
process.exit(0);
|
|
23
23
|
} catch (err) {
|
|
24
|
-
console.
|
|
25
|
-
process.exit(
|
|
24
|
+
console.error(`❌ STALE BASE GATE FAIL: Could not resolve base branch "${baseBranch}": ${err.message}`);
|
|
25
|
+
process.exit(1);
|
|
26
26
|
}
|
package/src/config.mjs
CHANGED
|
@@ -196,9 +196,19 @@ export function detectStack(projectRoot = process.cwd()) {
|
|
|
196
196
|
return detectPolyglotStack(projectRoot);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
export function resolveVerify(root = process.cwd()) {
|
|
199
|
+
export function resolveVerify(root = process.cwd(), userVerify = {}) {
|
|
200
200
|
const s = detectStack(root);
|
|
201
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
setup: userVerify.setup ?? s.setupCmd ?? "",
|
|
203
|
+
test: userVerify.test ?? s.testCmd ?? "",
|
|
204
|
+
teardown: userVerify.teardown ?? s.teardownCmd ?? "",
|
|
205
|
+
build: userVerify.build ?? s.buildCmd ?? "",
|
|
206
|
+
server: userVerify.server ? {
|
|
207
|
+
command: userVerify.server.command || "",
|
|
208
|
+
url: userVerify.server.url || "http://localhost:3000",
|
|
209
|
+
timeoutMs: Number(userVerify.server.timeoutMs || 15000),
|
|
210
|
+
} : null,
|
|
211
|
+
};
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
export const TIER_PRESETS = {
|
|
@@ -247,8 +257,11 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
259
|
|
|
260
|
+
const setupCmd = parsed.setup_cmd || parsed.verify?.setup || "";
|
|
250
261
|
const testCmd = parsed.test_cmd || parsed.verify?.test || "";
|
|
262
|
+
const teardownCmd = parsed.teardown_cmd || parsed.verify?.teardown || "";
|
|
251
263
|
const buildCmd = parsed.build_cmd || parsed.verify?.build || "";
|
|
264
|
+
const verifyTimeoutMs = parsed.verify?.timeoutMs ?? parsed.verify?.timeout_ms ?? 60000;
|
|
252
265
|
const autoVerify = resolveVerify(root);
|
|
253
266
|
|
|
254
267
|
const activeTier = String(process.env.JULES_TIER || parsed.tier || "ultra").toLowerCase();
|
|
@@ -275,8 +288,11 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
275
288
|
provider: parsed.provider || DEFAULTS.provider,
|
|
276
289
|
tier: activeTier,
|
|
277
290
|
verify: {
|
|
291
|
+
setup: setupCmd || autoVerify.setup || "",
|
|
278
292
|
test: testCmd || autoVerify.test,
|
|
293
|
+
teardown: teardownCmd || autoVerify.teardown || "",
|
|
279
294
|
build: buildCmd || autoVerify.build,
|
|
295
|
+
timeoutMs: Number.isFinite(Number(verifyTimeoutMs)) ? Number(verifyTimeoutMs) : 60000,
|
|
280
296
|
},
|
|
281
297
|
scope: normalizeScope(parsed),
|
|
282
298
|
limits: {
|
package/src/dag-engine.mjs
CHANGED
|
@@ -330,3 +330,79 @@ export class DagExecutor {
|
|
|
330
330
|
});
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Global Contract Trigger List:
|
|
336
|
+
* Files matching these patterns are considered global structural contracts.
|
|
337
|
+
* If ANY modified file matches these patterns, selective test execution MUST yield to full test suite execution (returns null).
|
|
338
|
+
*/
|
|
339
|
+
export const GLOBAL_CONTRACT_PATTERNS = [
|
|
340
|
+
/^package(-lock)?\.json$/,
|
|
341
|
+
/^pnpm-lock\.yaml$/,
|
|
342
|
+
/^yarn\.lock$/,
|
|
343
|
+
/^tsconfig.*\.json$/,
|
|
344
|
+
/^schema\./,
|
|
345
|
+
/\.d\.ts$/,
|
|
346
|
+
/^\.env/,
|
|
347
|
+
/^\.agent\//,
|
|
348
|
+
/^Cargo\.(toml|lock)$/,
|
|
349
|
+
/^go\.(mod|sum)$/,
|
|
350
|
+
/^composer\.(json|lock)$/,
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Checks if a relative file path matches global contract triggers.
|
|
355
|
+
*/
|
|
356
|
+
export function isGlobalContractFile(filePath = "") {
|
|
357
|
+
if (typeof filePath !== "string") return false;
|
|
358
|
+
const normalized = filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
359
|
+
const baseName = normalized.split("/").pop();
|
|
360
|
+
return GLOBAL_CONTRACT_PATTERNS.some((pattern) => pattern.test(normalized) || pattern.test(baseName));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Resolves affected test files based on modified files and static import analysis.
|
|
365
|
+
* Returns null if full test suite should be executed (due to global contract change or unknown dependency),
|
|
366
|
+
* or an array of affected test file paths.
|
|
367
|
+
*/
|
|
368
|
+
export function resolveAffectedTests(modifiedFiles = [], options = {}) {
|
|
369
|
+
if (!Array.isArray(modifiedFiles) || modifiedFiles.length === 0) {
|
|
370
|
+
return [];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 1. Check Global Contract Trigger List
|
|
374
|
+
for (const file of modifiedFiles) {
|
|
375
|
+
if (isGlobalContractFile(file)) {
|
|
376
|
+
return null; // Force full test suite execution
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const knownTestFiles = Array.isArray(options.knownTestFiles) ? options.knownTestFiles : [];
|
|
381
|
+
const testSuffixRegex = options.testSuffixRegex || /\.(test|spec)\.(m?[jt]sx?|py|rs|go)$/i;
|
|
382
|
+
|
|
383
|
+
const affected = new Set();
|
|
384
|
+
|
|
385
|
+
for (const file of modifiedFiles) {
|
|
386
|
+
if (typeof file !== "string") continue;
|
|
387
|
+
const normalized = file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
388
|
+
|
|
389
|
+
// If the modified file itself is a test file, add it directly
|
|
390
|
+
if (testSuffixRegex.test(normalized)) {
|
|
391
|
+
affected.add(normalized);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Match known test files that import or reference this file/module
|
|
396
|
+
const baseNameNoExt = normalized.split("/").pop().replace(/\.[^/.]+$/, "");
|
|
397
|
+
for (const testFile of knownTestFiles) {
|
|
398
|
+
if (typeof testFile !== "string") continue;
|
|
399
|
+
const normTest = testFile.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
400
|
+
if (normTest.includes(baseNameNoExt)) {
|
|
401
|
+
affected.add(normTest);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return affected.size > 0 ? Array.from(affected) : null;
|
|
407
|
+
}
|
|
408
|
+
|