jules-orchestrator-kit 0.29.0 → 0.31.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/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.29.0 — Universal Agent Orchestrator & Safety Gatekeeper
17
+ šŸš€ agentctl v0.30.0 — Universal Agent Orchestrator & Safety Gatekeeper
18
18
 
19
19
  Usage: agentctl <command> [options]
20
20
 
@@ -32,6 +32,11 @@ 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
37
42
  version Output agentctl version
@@ -54,7 +59,7 @@ async function main() {
54
59
  }
55
60
 
56
61
  if (command === "version" || command === "--version" || command === "-v") {
57
- console.log("agentctl v0.29.0");
62
+ console.log("agentctl v0.29.1");
58
63
  process.exit(0);
59
64
  }
60
65
 
@@ -265,7 +270,7 @@ async function main() {
265
270
  }
266
271
 
267
272
  case "doctor": {
268
- console.log(`\nšŸ” agentctl System Diagnostics (v0.29.0)`);
273
+ console.log(`\nšŸ” agentctl System Diagnostics (v0.29.1)`);
269
274
  console.log(`--------------------------------------------------`);
270
275
  console.log(` Project Root : ${root}`);
271
276
  console.log(` Config File : ${config._file || "None (Using defaults)"}`);
@@ -391,8 +396,66 @@ async function main() {
391
396
  console.log(` Auto-PR : ${res.plan.flags.autoPr}`);
392
397
  }
393
398
  process.exit(0);
399
+ } else if (subCommand === "optimize") {
400
+ const { values, positionals } = parseArgs({
401
+ args: args.slice(2),
402
+ options: {
403
+ fix: { type: "boolean", short: "f" },
404
+ file: { type: "string" },
405
+ dir: { type: "string", short: "d" },
406
+ json: { type: "boolean", short: "j" },
407
+ "verify-cmd": { type: "string", short: "v" },
408
+ },
409
+ allowPositionals: true,
410
+ });
411
+
412
+ const { scorePromptFalsifiability, optimizeTaskPrompt } = await import("../src/task-optimizer.mjs");
413
+ const targetDir = values.dir ? resolve(values.dir) : root;
414
+ let promptText = positionals.join(" ");
415
+
416
+ if (values.file) {
417
+ if (existsSync(values.file)) {
418
+ promptText = readFileSync(values.file, "utf-8");
419
+ } else {
420
+ console.error(`Error: File '${values.file}' does not exist.`);
421
+ process.exit(1);
422
+ }
423
+ }
424
+
425
+ if (values.fix) {
426
+ const opt = optimizeTaskPrompt(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] });
427
+ if (values.json) {
428
+ console.log(JSON.stringify(opt, null, 2));
429
+ } else {
430
+ console.log(opt.optimizedPrompt);
431
+ }
432
+ process.exit(0);
433
+ }
434
+
435
+ const analysis = scorePromptFalsifiability(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] });
436
+ if (values.json) {
437
+ console.log(JSON.stringify(analysis, null, 2));
438
+ } else {
439
+ console.log(`\nšŸŽÆ Task Prompt Falsifiability Analysis`);
440
+ console.log(`--------------------------------------------------`);
441
+ console.log(` Score / Grade : ${analysis.score} / 100 (${analysis.grade})`);
442
+ console.log(` Falsifiable : ${analysis.isFalsifiable ? "āœ… YES" : "āŒ NO"}`);
443
+ if (analysis.oracle.command) {
444
+ console.log(` Oracle Command : "${analysis.oracle.command}" (${analysis.oracle.autoDetected ? "Auto-detected" : "User-supplied"})`);
445
+ }
446
+ if (analysis.issues.length > 0) {
447
+ console.log(`\n Issues Identified (${analysis.issues.length}):`);
448
+ analysis.issues.forEach((i) => console.log(` - [${i.type}] ${i.message} (-${i.penalty} pts)`));
449
+ }
450
+ if (analysis.suggestions.length > 0) {
451
+ console.log(`\n Suggestions for Improvement:`);
452
+ analysis.suggestions.forEach((s) => console.log(` - ${s}`));
453
+ }
454
+ console.log(`--------------------------------------------------\n`);
455
+ }
456
+ process.exit(analysis.isFalsifiable ? 0 : 1);
394
457
  } else {
395
- console.error(`Unknown task subcommand '${subCommand}'. Supported: agentctl task create`);
458
+ console.error(`Unknown task subcommand '${subCommand}'. Supported: agentctl task create, agentctl task optimize`);
396
459
  process.exit(1);
397
460
  }
398
461
  break;
@@ -401,7 +464,7 @@ async function main() {
401
464
  case "status": {
402
465
  const queueDir = getQueueDir(root);
403
466
  const files = existsSync(queueDir) ? readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir)) : [];
404
- console.log(`\nšŸ“Š agentctl Status Summary (v0.29.0)`);
467
+ console.log(`\nšŸ“Š agentctl Status Summary (v0.29.1)`);
405
468
  console.log(`--------------------------------------------------`);
406
469
  console.log(` Project Root : ${root}`);
407
470
  console.log(` Pending Tasks : ${files.length}`);
@@ -427,7 +490,143 @@ async function main() {
427
490
  break;
428
491
  }
429
492
 
493
+ case "rollback": {
494
+ const { restoreCheckpoint } = await import("../src/ops/checkpoint.mjs");
495
+ const targetId = args[1] || "--latest";
496
+ try {
497
+ const res = restoreCheckpoint(targetId, { root });
498
+ console.log(`\nāœ… Git Checkpoint Restored Successfully!`);
499
+ console.log(` Session ID : ${res.id}`);
500
+ console.log(` HEAD SHA : ${res.headSha || "N/A"}`);
501
+ console.log(` RestoredAt : ${res.restoredAt}\n`);
502
+ process.exit(0);
503
+ } catch (err) {
504
+ console.error(`āŒ Rollback Failed: ${err.message}`);
505
+ process.exit(1);
506
+ }
507
+ break;
508
+ }
509
+
510
+ case "resume": {
511
+ const { createProvider } = await import("../src/provider.mjs");
512
+ const sessionId = args[1];
513
+ const { values } = parseArgs({
514
+ args: args.slice(1),
515
+ options: {
516
+ response: { type: "string", short: "r" },
517
+ "dry-run": { type: "boolean", short: "d" },
518
+ json: { type: "boolean", short: "j" },
519
+ },
520
+ allowPositionals: true,
521
+ });
522
+
523
+ if (!sessionId || sessionId.startsWith("-")) {
524
+ console.error("Error: Session ID is required for agentctl resume <sessionId>.");
525
+ process.exit(1);
526
+ }
527
+
528
+ const responseText = values.response || args.slice(2).join(" ");
529
+ if (!responseText) {
530
+ console.error("Error: --response text is required to resume warm session.");
531
+ process.exit(1);
532
+ }
533
+
534
+ const provider = createProvider(config.provider || "jules", config);
535
+ try {
536
+ const res = await provider.resume(sessionId, responseText, { root, dryRun: values["dry-run"] });
537
+ if (values.json) {
538
+ console.log(JSON.stringify(res, null, 2));
539
+ } else {
540
+ console.log(`\nāœ… Warm Session Resumed Successfully!`);
541
+ console.log(` Session ID : ${res.id}`);
542
+ console.log(` Status : ${res.status}\n`);
543
+ }
544
+ process.exit(0);
545
+ } catch (err) {
546
+ console.error(`āŒ Resume Failed: ${err.message}`);
547
+ process.exit(1);
548
+ }
549
+ break;
550
+ }
551
+
552
+ case "test-gen": {
553
+ const { scaffoldTddTest, runTddCycle } = await import("../src/ops/tdd-generator.mjs");
554
+ const { values } = parseArgs({
555
+ args: args.slice(1),
556
+ options: {
557
+ title: { type: "string", short: "t" },
558
+ spec: { type: "string", short: "s" },
559
+ run: { type: "boolean", short: "r" },
560
+ json: { type: "boolean", short: "j" },
561
+ },
562
+ allowPositionals: true,
563
+ });
564
+
565
+ const title = values.title || args[1] || "feature-spec";
566
+ const specText = values.spec || args.slice(2).join(" ") || "TDD requirement specification.";
567
+
568
+ try {
569
+ if (values.run) {
570
+ const res = await runTddCycle({ title, spec: specText }, { root });
571
+ if (values.json) {
572
+ console.log(JSON.stringify(res, null, 2));
573
+ } else {
574
+ console.log(`\nšŸ”“ TDD RED Check Verified!`);
575
+ console.log(` Test File : ${res.testFile}`);
576
+ console.log(` Scope Lock : Locked into scope.deny`);
577
+ console.log(` Status : Ready for green implementation dispatch\n`);
578
+ }
579
+ } else {
580
+ const scaffold = scaffoldTddTest({ title, spec: specText }, { root });
581
+ if (values.json) {
582
+ console.log(JSON.stringify(scaffold, null, 2));
583
+ } else {
584
+ console.log(`\n🧪 TDD Test Scaffolded Successfully!`);
585
+ console.log(` File Path : ${scaffold.relativePath}`);
586
+ console.log(` Command : ${scaffold.testCmd}\n`);
587
+ }
588
+ }
589
+ process.exit(0);
590
+ } catch (err) {
591
+ console.error(`āŒ TDD Generation Failed: ${err.message}`);
592
+ process.exit(1);
593
+ }
594
+ break;
595
+ }
596
+
430
597
  case "mcp": {
598
+ const subAction = args[1];
599
+ if (subAction === "init") {
600
+ const { scaffoldIdeConfig } = await import("../src/ops/ide-scaffold.mjs");
601
+ const { values } = parseArgs({
602
+ args: args.slice(2),
603
+ options: {
604
+ target: { type: "string", short: "t", default: "all" },
605
+ json: { type: "boolean", short: "j" },
606
+ },
607
+ allowPositionals: true,
608
+ });
609
+
610
+ const target = values.target || args[2] || "all";
611
+ try {
612
+ const res = scaffoldIdeConfig(target, { root });
613
+ if (values.json) {
614
+ console.log(JSON.stringify(res, null, 2));
615
+ } else {
616
+ console.log(`\nšŸ”Œ IDE MCP Config Scaffolded Successfully!`);
617
+ console.log(` Target : ${res.target}`);
618
+ for (const item of res.results) {
619
+ console.log(` - ${item.target.toUpperCase()} : ${item.file}`);
620
+ }
621
+ console.log("");
622
+ }
623
+ process.exit(0);
624
+ } catch (err) {
625
+ console.error(`āŒ IDE MCP Scaffold Failed: ${err.message}`);
626
+ process.exit(1);
627
+ }
628
+ }
629
+
431
630
  const { startMcpServer } = await import("../src/mcp.mjs");
432
631
  startMcpServer();
433
632
  break;
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Google Jules Orchestrator Kit - Node.js SDK (v0.29.0)
2
+ * Google Jules Orchestrator Kit - Node.js SDK (v0.29.1)
3
3
  *
4
4
  * Exposes core orchestrator functions for programmatically driving agent tasks,
5
5
  * security auditing, repo gating, and state operations.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "Orchestration kit for running Google Jules autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
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 { test: s.testCmd || "", build: s.buildCmd || "" };
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: {
@@ -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
+
package/src/dashboard.mjs CHANGED
@@ -32,7 +32,7 @@ export function getDashboardHtml(root = process.cwd()) {
32
32
  </style>
33
33
  </head>
34
34
  <body>
35
- <h1>šŸš€ jules-orchestrator-kit Dashboard (v0.29.0)</h1>
35
+ <h1>šŸš€ jules-orchestrator-kit Dashboard (v0.29.1)</h1>
36
36
  <div class="subtitle">Repository: <code>${root}</code></div>
37
37
 
38
38
  <div class="grid">
@@ -87,7 +87,7 @@ export function createDashboardServer({ root = process.cwd(), port: _port = 4100
87
87
 
88
88
  if (url.pathname === "/api/status") {
89
89
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
90
- return res.end(JSON.stringify({ ok: true, version: "0.29.0", root, ts: new Date().toISOString() }));
90
+ return res.end(JSON.stringify({ ok: true, version: "0.29.1", root, ts: new Date().toISOString() }));
91
91
  }
92
92
 
93
93
  if (url.pathname === "/api/telemetry") {