jules-orchestrator-kit 0.52.0 → 0.52.1
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 +2 -2
- package/bin/agentctl.mjs +32 -6
- package/package.json +1 -1
- package/scripts/jules-scan-todos.mjs +2 -2
- package/src/ops/command-registry.mjs +204 -7
- package/src/ops/next-step.mjs +2 -1
- package/src/stack-detector.mjs +31 -9
- package/src/wizard-oracle.mjs +42 -8
- package/src/wizard-task.mjs +4 -3
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ npx jules-orchestrator-kit init
|
|
|
59
59
|
```bash
|
|
60
60
|
# 2. Commit what init wrote — .agent/config.yml is on the gate's deny list by
|
|
61
61
|
# design, so leaving it uncommitted makes the first gate reject your tree
|
|
62
|
-
git add .agent AGENTS.md .gitignore && git commit -m "chore: add agent config"
|
|
62
|
+
git add .agent AGENTS.md SPEC.md CONSTRAINTS.md .gitignore && git commit -m "chore: add agent config"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
```bash
|
|
@@ -143,7 +143,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
143
143
|
* **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).
|
|
144
144
|
* **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.
|
|
145
145
|
* **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
|
|
146
|
-
* **Verified Test Suite:** Tested with **
|
|
146
|
+
* **Verified Test Suite:** Tested with **802 unit tests across 91 suites passing in < 10.0s**.
|
|
147
147
|
|
|
148
148
|
<br/>
|
|
149
149
|
|
package/bin/agentctl.mjs
CHANGED
|
@@ -228,9 +228,16 @@ async function main() {
|
|
|
228
228
|
process.exit(0);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
//
|
|
231
|
+
// Resolve --help / -h on subcommands (e.g. `agentctl init --help`, `agentctl mutate --help`)
|
|
232
232
|
const subArgs = args.slice(1);
|
|
233
233
|
if (subArgs.includes("--help") || subArgs.includes("-h")) {
|
|
234
|
+
const { getCommandDescriptor, formatCommandHelp } = await import("../src/ops/command-registry.mjs");
|
|
235
|
+
const subSub = subArgs[0] && !subArgs[0].startsWith("-") ? `${command} ${subArgs[0]}` : command;
|
|
236
|
+
const desc = getCommandDescriptor(subSub) || getCommandDescriptor(command);
|
|
237
|
+
if (desc) {
|
|
238
|
+
console.log(formatCommandHelp(desc));
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
234
241
|
printHelp();
|
|
235
242
|
process.exit(0);
|
|
236
243
|
}
|
|
@@ -1236,6 +1243,9 @@ async function main() {
|
|
|
1236
1243
|
args: args.slice(1),
|
|
1237
1244
|
options: {
|
|
1238
1245
|
interactive: { type: "boolean", short: "i" },
|
|
1246
|
+
"non-interactive": { type: "boolean" },
|
|
1247
|
+
"no-interactive": { type: "boolean" },
|
|
1248
|
+
yes: { type: "boolean", short: "y" },
|
|
1239
1249
|
tier: { type: "string", short: "t" },
|
|
1240
1250
|
json: { type: "boolean", short: "j" },
|
|
1241
1251
|
"dry-run": { type: "boolean", short: "d" },
|
|
@@ -1244,9 +1254,11 @@ async function main() {
|
|
|
1244
1254
|
allowPositionals: true,
|
|
1245
1255
|
});
|
|
1246
1256
|
|
|
1257
|
+
const isInteractive = values.interactive !== false && !values["non-interactive"] && !values["no-interactive"] && !values.yes;
|
|
1258
|
+
|
|
1247
1259
|
const { runInitWizard } = await import("../src/wizard-init.mjs");
|
|
1248
1260
|
const res = await runInitWizard(root, {
|
|
1249
|
-
interactive:
|
|
1261
|
+
interactive: isInteractive,
|
|
1250
1262
|
// No `|| "pro"`: a hardcoded default here overrode both the tier picked
|
|
1251
1263
|
// in the menu and the tier already recorded in .agent/config.yml on a
|
|
1252
1264
|
// re-run. Undefined lets the wizard seed the menu from the existing
|
|
@@ -1267,7 +1279,11 @@ async function main() {
|
|
|
1267
1279
|
} else {
|
|
1268
1280
|
console.log(`✅ Onboarding complete! Manifest generated at ${res.configPath}`);
|
|
1269
1281
|
console.log(` Tier: ${res.plan.tier.toUpperCase()} (${res.plan.limits.concurrency} worker(s), ${res.plan.limits.daily_tasks} daily tasks)`);
|
|
1270
|
-
|
|
1282
|
+
if (res.plan.verify.test) {
|
|
1283
|
+
console.log(` Verification Test Command : "${res.plan.verify.test}"`);
|
|
1284
|
+
} else {
|
|
1285
|
+
console.log(` Verification Test Command : None detected (run "agentctl bootstrap" to create a test oracle)`);
|
|
1286
|
+
}
|
|
1271
1287
|
console.log(` Active Presets : ${res.plan.presets.join(", ")}`);
|
|
1272
1288
|
for (const item of scaffold.created) {
|
|
1273
1289
|
console.log(` Scaffolded : ${item}`);
|
|
@@ -1281,8 +1297,10 @@ async function main() {
|
|
|
1281
1297
|
// uncommitted meant the very first `agentctl gate` rejected the working
|
|
1282
1298
|
// tree for files init had just written, which reads as the tool
|
|
1283
1299
|
// catching the user cheating on step three.
|
|
1284
|
-
|
|
1285
|
-
|
|
1300
|
+
const rootContracts = ["SPEC.md", "CONSTRAINTS.md", "DESIGN.md"].filter((f) => existsSync(join(root, f)));
|
|
1301
|
+
const filesToAdd = [".agent", "AGENTS.md", ...rootContracts, ".gitignore"].filter((f) => existsSync(join(root, f)));
|
|
1302
|
+
console.log(`\n Commit the manifest and contracts so the gate does not read them as agent edits:`);
|
|
1303
|
+
console.log(` git add ${filesToAdd.join(" ")} && git commit -m "chore: add agent config"`);
|
|
1286
1304
|
|
|
1287
1305
|
const { resolveNextStep, renderNextStep } = await import("../src/ops/next-step.mjs");
|
|
1288
1306
|
const next = resolveNextStep(root);
|
|
@@ -1310,12 +1328,19 @@ async function main() {
|
|
|
1310
1328
|
"auto-pr": { type: "boolean" },
|
|
1311
1329
|
"require-plan-approval": { type: "boolean" },
|
|
1312
1330
|
repoless: { type: "boolean" },
|
|
1331
|
+
interactive: { type: "boolean", short: "i" },
|
|
1332
|
+
"non-interactive": { type: "boolean" },
|
|
1333
|
+
"no-interactive": { type: "boolean" },
|
|
1334
|
+
yes: { type: "boolean", short: "y" },
|
|
1313
1335
|
json: { type: "boolean", short: "j" },
|
|
1314
1336
|
"dry-run": { type: "boolean", short: "d" },
|
|
1315
1337
|
},
|
|
1316
1338
|
allowPositionals: true,
|
|
1317
1339
|
});
|
|
1318
1340
|
|
|
1341
|
+
const isNonInteractive = Boolean(values["non-interactive"] || values["no-interactive"] || values.yes);
|
|
1342
|
+
const isInteractive = values.interactive === true ? true : (isNonInteractive ? false : undefined);
|
|
1343
|
+
|
|
1319
1344
|
const { runTaskCreateWizard } = await import("../src/wizard-task.mjs");
|
|
1320
1345
|
const res = await runTaskCreateWizard(root, {
|
|
1321
1346
|
title: values.title,
|
|
@@ -1328,6 +1353,7 @@ async function main() {
|
|
|
1328
1353
|
autoPr: values["auto-pr"],
|
|
1329
1354
|
requirePlanApproval: values["require-plan-approval"],
|
|
1330
1355
|
repoless: values.repoless,
|
|
1356
|
+
interactive: isInteractive,
|
|
1331
1357
|
});
|
|
1332
1358
|
|
|
1333
1359
|
if (values.json) {
|
|
@@ -1475,7 +1501,7 @@ async function main() {
|
|
|
1475
1501
|
const todos = scanCodebaseForTodos(root);
|
|
1476
1502
|
console.log(`\n🔍 Scanned ${todos.length} TODO/FIXME annotation(s).`);
|
|
1477
1503
|
for (const t of todos.slice(0, 10)) {
|
|
1478
|
-
console.log(` - ${t.file}:${t.line} [${t.type}] ${t.text}`);
|
|
1504
|
+
console.log(` - ${t.file}:${t.line} [${t.type || t.tag || "TODO"}] ${t.text}`);
|
|
1479
1505
|
}
|
|
1480
1506
|
if (todos.length > 10) {
|
|
1481
1507
|
console.log(` ... and ${todos.length - 10} more.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.1",
|
|
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",
|
|
@@ -24,9 +24,9 @@ export function scanCodebaseForTodos(dir = process.cwd()) {
|
|
|
24
24
|
const lines = content.split("\n");
|
|
25
25
|
lines.forEach((line, idx) => {
|
|
26
26
|
if (line.includes("TODO:")) {
|
|
27
|
-
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "TODO", priority: "MEDIUM" });
|
|
27
|
+
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "TODO", type: "TODO", priority: "MEDIUM" });
|
|
28
28
|
} else if (line.includes("FIXME:")) {
|
|
29
|
-
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "FIXME", priority: "HIGH" });
|
|
29
|
+
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "FIXME", type: "FIXME", priority: "HIGH" });
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -141,10 +141,23 @@ export const COMMAND_REGISTRY = [
|
|
|
141
141
|
"agentctl task create --interactive",
|
|
142
142
|
],
|
|
143
143
|
flags: [
|
|
144
|
-
{ name: "title", type: "string", description: "Short title for task" },
|
|
145
|
-
{ name: "prompt", type: "string", description: "Detailed task instructions" },
|
|
146
|
-
{ name: "
|
|
147
|
-
{ name: "
|
|
144
|
+
{ name: "title", type: "string", description: "Short title for task (-t)" },
|
|
145
|
+
{ name: "prompt", type: "string", description: "Detailed task instructions (-p)" },
|
|
146
|
+
{ name: "prompt-file", type: "string", description: "Read prompt from file (-f)" },
|
|
147
|
+
{ name: "role", type: "string", description: "Specialist role (overseer, bolt, sentinel, janitor, a11y, scribe, spectator, alchemist) (-r)" },
|
|
148
|
+
{ name: "tier", type: "string", description: "Execution tier override (fast | complex)" },
|
|
149
|
+
{ name: "template", type: "string", description: "Task template preset ID" },
|
|
150
|
+
{ name: "depends-on", type: "string", description: "Comma-separated task dependency IDs" },
|
|
151
|
+
{ name: "verify-cmd", type: "string", description: "Verification command override (-v)" },
|
|
152
|
+
{ name: "auto-pr", type: "boolean", description: "Automatically create GitHub PR upon completion" },
|
|
153
|
+
{ name: "require-plan-approval", type: "boolean", description: "Require approval of agent plan before execution" },
|
|
154
|
+
{ name: "repoless", type: "boolean", description: "Execute in repoless sandbox mode" },
|
|
155
|
+
{ name: "interactive", type: "boolean", description: "Launch interactive task wizard (-i)" },
|
|
156
|
+
{ name: "non-interactive", type: "boolean", description: "Bypass interactive prompts and use CLI flags" },
|
|
157
|
+
{ name: "no-interactive", type: "boolean", description: "Alias for --non-interactive" },
|
|
158
|
+
{ name: "yes", type: "boolean", description: "Accept default values non-interactively (-y)" },
|
|
159
|
+
{ name: "dry-run", type: "boolean", description: "Simulate task envelope creation without queueing (-d)" },
|
|
160
|
+
{ name: "json", type: "boolean", description: "Output structured JSON envelope (-j)" },
|
|
148
161
|
],
|
|
149
162
|
},
|
|
150
163
|
{
|
|
@@ -187,9 +200,14 @@ export const COMMAND_REGISTRY = [
|
|
|
187
200
|
"agentctl init --tier pro --yes",
|
|
188
201
|
],
|
|
189
202
|
flags: [
|
|
190
|
-
{ name: "tier", type: "string", description: "Target configuration tier (free, pro, ultra, enterprise)" },
|
|
191
|
-
{ name: "interactive", type: "boolean", description: "Launch interactive onboarding wizard" },
|
|
192
|
-
{ name: "
|
|
203
|
+
{ name: "tier", type: "string", description: "Target configuration tier (free, pro, ultra, enterprise) (-t)" },
|
|
204
|
+
{ name: "interactive", type: "boolean", description: "Launch interactive onboarding wizard (-i)" },
|
|
205
|
+
{ name: "non-interactive", type: "boolean", description: "Run non-interactively with defaults" },
|
|
206
|
+
{ name: "no-interactive", type: "boolean", description: "Alias for --non-interactive" },
|
|
207
|
+
{ name: "yes", type: "boolean", description: "Accept auto-detected Stack Oracle defaults (-y)" },
|
|
208
|
+
{ name: "force", type: "boolean", description: "Force overwrite existing config and assets (-f)" },
|
|
209
|
+
{ name: "dry-run", type: "boolean", description: "Preview plan without writing files (-d)" },
|
|
210
|
+
{ name: "json", type: "boolean", description: "Output structured JSON manifest (-j)" },
|
|
193
211
|
],
|
|
194
212
|
},
|
|
195
213
|
{
|
|
@@ -333,6 +351,185 @@ export const COMMAND_REGISTRY = [
|
|
|
333
351
|
{ name: "limit", type: "string", description: "Maximum handovers to list" },
|
|
334
352
|
],
|
|
335
353
|
},
|
|
354
|
+
{
|
|
355
|
+
id: "mutate",
|
|
356
|
+
path: ["mutate"],
|
|
357
|
+
title: "mutate",
|
|
358
|
+
description: "Run zero-dependency diff mutation testing harness",
|
|
359
|
+
category: "Inspect",
|
|
360
|
+
mutates: false,
|
|
361
|
+
risk: "low",
|
|
362
|
+
interactive: "never",
|
|
363
|
+
requiresRepository: true,
|
|
364
|
+
shortcuts: ["mutation"],
|
|
365
|
+
examples: [
|
|
366
|
+
"agentctl mutate",
|
|
367
|
+
"agentctl mutate --min-score 80",
|
|
368
|
+
'agentctl mutate --max-mutants 20 --test-cmd "npm test"',
|
|
369
|
+
],
|
|
370
|
+
flags: [
|
|
371
|
+
{ name: "min-score", type: "string", description: "Target minimum mutation score threshold (0-100)" },
|
|
372
|
+
{ name: "max-mutants", type: "string", description: "Maximum number of mutants to generate and test" },
|
|
373
|
+
{ name: "test-cmd", type: "string", description: "Test command override" },
|
|
374
|
+
{ name: "verify-cmd", type: "string", description: "Alias for test-cmd" },
|
|
375
|
+
{ name: "tier", type: "string", description: "Execution tier (fast | complex)" },
|
|
376
|
+
{ name: "dry-run", type: "boolean", description: "Simulate mutation analysis without executing tests (-d)" },
|
|
377
|
+
{ name: "json", type: "boolean", description: "Output structured JSON mutation report (-j)" },
|
|
378
|
+
],
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: "coverage",
|
|
382
|
+
path: ["coverage"],
|
|
383
|
+
title: "coverage",
|
|
384
|
+
description: "Run native zero-dependency V8 diff coverage check",
|
|
385
|
+
category: "Inspect",
|
|
386
|
+
mutates: false,
|
|
387
|
+
risk: "low",
|
|
388
|
+
interactive: "never",
|
|
389
|
+
requiresRepository: true,
|
|
390
|
+
shortcuts: ["cov"],
|
|
391
|
+
examples: [
|
|
392
|
+
"agentctl coverage",
|
|
393
|
+
"agentctl coverage --min 80",
|
|
394
|
+
'agentctl coverage --cmd "node --test"',
|
|
395
|
+
],
|
|
396
|
+
flags: [
|
|
397
|
+
{ name: "min", type: "string", description: "Minimum diff line coverage percentage required" },
|
|
398
|
+
{ name: "cmd", type: "string", description: "Test command override to run with V8 coverage" },
|
|
399
|
+
{ name: "test-cmd", type: "string", description: "Alias for cmd" },
|
|
400
|
+
{ name: "verify-cmd", type: "string", description: "Alias for cmd" },
|
|
401
|
+
{ name: "dry-run", type: "boolean", description: "Simulate coverage collection without running (-d)" },
|
|
402
|
+
{ name: "json", type: "boolean", description: "Output structured JSON coverage report (-j)" },
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "gate",
|
|
407
|
+
path: ["gate"],
|
|
408
|
+
title: "gate",
|
|
409
|
+
description: "Run CI security, rules, and stack verification gate",
|
|
410
|
+
category: "Inspect",
|
|
411
|
+
mutates: false,
|
|
412
|
+
risk: "low",
|
|
413
|
+
interactive: "never",
|
|
414
|
+
requiresRepository: true,
|
|
415
|
+
shortcuts: ["audit", "check"],
|
|
416
|
+
examples: [
|
|
417
|
+
"agentctl gate",
|
|
418
|
+
"agentctl gate --mode working-tree",
|
|
419
|
+
"agentctl gate --fix",
|
|
420
|
+
"agentctl gate --json",
|
|
421
|
+
],
|
|
422
|
+
flags: [
|
|
423
|
+
{ name: "mode", type: "string", description: "Evaluation mode (working-tree | committed | staged) (-m)" },
|
|
424
|
+
{ name: "tier", type: "string", description: "Enforce specific tier policies (free | pro | ultra | enterprise)" },
|
|
425
|
+
{ name: "allow-protected", type: "boolean", description: "Bypass protected path checks for authorized maintainers" },
|
|
426
|
+
{ name: "fix", type: "boolean", description: "Trigger automated OODA self-repair loop on failure" },
|
|
427
|
+
{ name: "strict-locks", type: "boolean", description: "Enforce strict anti-tampering verification on test files" },
|
|
428
|
+
{ name: "dry-run", type: "boolean", description: "Simulate gate evaluation without persisting evidence (-d)" },
|
|
429
|
+
{ name: "json", type: "boolean", description: "Output structured JSON gate report (-j)" },
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: "probe",
|
|
434
|
+
path: ["probe"],
|
|
435
|
+
title: "probe",
|
|
436
|
+
description: "Run test flakiness stability probe across N repetitions",
|
|
437
|
+
category: "Inspect",
|
|
438
|
+
mutates: false,
|
|
439
|
+
risk: "low",
|
|
440
|
+
interactive: "never",
|
|
441
|
+
requiresRepository: true,
|
|
442
|
+
shortcuts: ["stability"],
|
|
443
|
+
examples: [
|
|
444
|
+
"agentctl probe",
|
|
445
|
+
"agentctl probe --repeat 10",
|
|
446
|
+
'agentctl probe --repeat 5 --cmd "pytest"',
|
|
447
|
+
],
|
|
448
|
+
flags: [
|
|
449
|
+
{ name: "repeat", type: "string", description: "Number of probe executions (default: 5)" },
|
|
450
|
+
{ name: "cmd", type: "string", description: "Test command to probe" },
|
|
451
|
+
{ name: "test-cmd", type: "string", description: "Alias for cmd" },
|
|
452
|
+
{ name: "verify-cmd", type: "string", description: "Alias for cmd" },
|
|
453
|
+
{ name: "dry-run", type: "boolean", description: "Preview probe plan without execution (-d)" },
|
|
454
|
+
{ name: "json", type: "boolean", description: "Output structured JSON probe telemetry (-j)" },
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
id: "perf",
|
|
459
|
+
path: ["perf"],
|
|
460
|
+
title: "perf",
|
|
461
|
+
description: "Monitor Node.js event loop delay and Big-O performance lag",
|
|
462
|
+
category: "Inspect",
|
|
463
|
+
mutates: false,
|
|
464
|
+
risk: "low",
|
|
465
|
+
interactive: "never",
|
|
466
|
+
requiresRepository: true,
|
|
467
|
+
shortcuts: ["event-loop"],
|
|
468
|
+
examples: [
|
|
469
|
+
"agentctl perf",
|
|
470
|
+
'agentctl perf --max-ms 50 --cmd "npm test"',
|
|
471
|
+
],
|
|
472
|
+
flags: [
|
|
473
|
+
{ name: "max-ms", type: "string", description: "Maximum allowable event loop delay threshold in ms" },
|
|
474
|
+
{ name: "cmd", type: "string", description: "Command to execute during performance monitoring" },
|
|
475
|
+
{ name: "test-cmd", type: "string", description: "Alias for cmd" },
|
|
476
|
+
{ name: "verify-cmd", type: "string", description: "Alias for cmd" },
|
|
477
|
+
{ name: "dry-run", type: "boolean", description: "Preview performance monitoring configuration (-d)" },
|
|
478
|
+
{ name: "json", type: "boolean", description: "Output structured JSON performance report (-j)" },
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: "dispatch",
|
|
483
|
+
path: ["dispatch"],
|
|
484
|
+
title: "dispatch",
|
|
485
|
+
description: "Dispatch a single task to an AI agent",
|
|
486
|
+
category: "Operate",
|
|
487
|
+
mutates: true,
|
|
488
|
+
risk: "moderate",
|
|
489
|
+
interactive: "never",
|
|
490
|
+
requiresRepository: true,
|
|
491
|
+
shortcuts: ["create"],
|
|
492
|
+
examples: [
|
|
493
|
+
'agentctl dispatch --prompt "Add retry handling to src/webhook.js"',
|
|
494
|
+
'agentctl dispatch -p "Fix type errors" --role bolt --tier fast',
|
|
495
|
+
'agentctl dispatch --prompt-file task.md --dry-run',
|
|
496
|
+
],
|
|
497
|
+
flags: [
|
|
498
|
+
{ name: "prompt", type: "string", description: "Task prompt instructions (-p)" },
|
|
499
|
+
{ name: "prompt-file", type: "string", description: "Read prompt from file (-f)" },
|
|
500
|
+
{ name: "role", type: "string", description: "Specialist role (overseer, bolt, sentinel, janitor, a11y, scribe, spectator, alchemist) (-r)" },
|
|
501
|
+
{ name: "tier", type: "string", description: "Execution tier override (fast | complex)" },
|
|
502
|
+
{ name: "check-premise", type: "boolean", description: "Verify premise locally before dispatching" },
|
|
503
|
+
{ name: "source", type: "string", description: "Jules repository source identifier (-s)" },
|
|
504
|
+
{ name: "branch", type: "string", description: "Starting branch for task execution (-b)" },
|
|
505
|
+
{ name: "repoless", type: "boolean", description: "Dispatch task in repoless execution mode" },
|
|
506
|
+
{ name: "auto-pr", type: "boolean", description: "Automatically create PR upon completion" },
|
|
507
|
+
{ name: "require-plan-approval", type: "boolean", description: "Require human approval for proposed plan" },
|
|
508
|
+
{ name: "dry-run", type: "boolean", description: "Simulate dispatch without sending to provider (-d)" },
|
|
509
|
+
{ name: "json", type: "boolean", description: "Output JSON structured dispatch result (-j)" },
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
id: "bootstrap",
|
|
514
|
+
path: ["bootstrap"],
|
|
515
|
+
title: "bootstrap",
|
|
516
|
+
description: "Bootstrap zero-test repository with verification oracle",
|
|
517
|
+
category: "Configure",
|
|
518
|
+
mutates: true,
|
|
519
|
+
risk: "moderate",
|
|
520
|
+
interactive: "never",
|
|
521
|
+
requiresRepository: true,
|
|
522
|
+
shortcuts: ["bs"],
|
|
523
|
+
examples: [
|
|
524
|
+
"agentctl bootstrap",
|
|
525
|
+
"agentctl bootstrap --force",
|
|
526
|
+
],
|
|
527
|
+
flags: [
|
|
528
|
+
{ name: "force", type: "boolean", description: "Overwrite existing verification oracle or config (-f)" },
|
|
529
|
+
{ name: "dry-run", type: "boolean", description: "Simulate bootstrap without writing files (-d)" },
|
|
530
|
+
{ name: "json", type: "boolean", description: "Output JSON structured bootstrap result (-j)" },
|
|
531
|
+
],
|
|
532
|
+
},
|
|
336
533
|
];
|
|
337
534
|
|
|
338
535
|
/**
|
package/src/ops/next-step.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { isTaskFile } from "../engine.mjs";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Work out the one thing the operator should do next.
|
|
@@ -66,7 +67,7 @@ export function resolveNextStep(root, env = process.env) {
|
|
|
66
67
|
const fallbackQueueDir = join(root, ".agent", "queue");
|
|
67
68
|
const queueDir = existsSync(primaryQueueDir) ? primaryQueueDir : fallbackQueueDir;
|
|
68
69
|
const queued = existsSync(queueDir)
|
|
69
|
-
? readdirSync(queueDir).filter((f) =>
|
|
70
|
+
? readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir)).length
|
|
70
71
|
: 0;
|
|
71
72
|
if (queued > 0) {
|
|
72
73
|
return {
|
package/src/stack-detector.mjs
CHANGED
|
@@ -210,8 +210,12 @@ export function detectPolyglotStack(projectRoot = process.cwd()) {
|
|
|
210
210
|
setupCmd = "npx drizzle-kit push";
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
if (existsSync(join(projectRoot, "bunfig.toml")) || existsSync(join(projectRoot, "bun.lockb"))) {
|
|
214
|
-
const triggerFile = existsSync(join(projectRoot, "bunfig.toml"))
|
|
213
|
+
if (existsSync(join(projectRoot, "bunfig.toml")) || existsSync(join(projectRoot, "bun.lockb")) || existsSync(join(projectRoot, "bun.lock"))) {
|
|
214
|
+
const triggerFile = existsSync(join(projectRoot, "bunfig.toml"))
|
|
215
|
+
? "bunfig.toml"
|
|
216
|
+
: existsSync(join(projectRoot, "bun.lockb"))
|
|
217
|
+
? "bun.lockb"
|
|
218
|
+
: "bun.lock";
|
|
215
219
|
return { ...container, stack: "bun", setupCmd, testCmd: "bun test", buildCmd: "bun run build", triggerFile };
|
|
216
220
|
}
|
|
217
221
|
if (existsSync(join(projectRoot, "deno.json")) || existsSync(join(projectRoot, "deno.jsonc"))) {
|
|
@@ -219,20 +223,38 @@ export function detectPolyglotStack(projectRoot = process.cwd()) {
|
|
|
219
223
|
return { ...container, stack: "deno", setupCmd, testCmd: "deno test", buildCmd: "deno task build", triggerFile };
|
|
220
224
|
}
|
|
221
225
|
if (existsSync(join(projectRoot, "package.json"))) {
|
|
222
|
-
|
|
223
|
-
|
|
226
|
+
const hasPnpm = existsSync(join(projectRoot, "pnpm-lock.yaml"));
|
|
227
|
+
const hasYarn = existsSync(join(projectRoot, "yarn.lock"));
|
|
228
|
+
const hasBun = existsSync(join(projectRoot, "bun.lock")) || existsSync(join(projectRoot, "bun.lockb"));
|
|
229
|
+
const pm = hasPnpm ? "pnpm" : hasYarn ? "yarn" : hasBun ? "bun" : "npm";
|
|
230
|
+
const triggerFile = hasPnpm
|
|
231
|
+
? "pnpm-lock.yaml"
|
|
232
|
+
: hasYarn
|
|
233
|
+
? "yarn.lock"
|
|
234
|
+
: hasBun
|
|
235
|
+
? (existsSync(join(projectRoot, "bun.lock")) ? "bun.lock" : "bun.lockb")
|
|
236
|
+
: "package.json";
|
|
237
|
+
|
|
238
|
+
let testScript = "";
|
|
239
|
+
let buildScript = "";
|
|
224
240
|
try {
|
|
225
241
|
const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf-8"));
|
|
226
242
|
if (pkg && typeof pkg === "object") {
|
|
227
243
|
const scripts = pkg.scripts || {};
|
|
228
|
-
|
|
229
|
-
|
|
244
|
+
if (scripts.test) {
|
|
245
|
+
testScript = pm === "yarn" ? "yarn test" : `${pm} test`;
|
|
246
|
+
} else if (pm === "bun") {
|
|
247
|
+
testScript = "bun test";
|
|
248
|
+
}
|
|
249
|
+
if (scripts.build) {
|
|
250
|
+
buildScript = pm === "yarn" ? "yarn build" : `${pm} run build`;
|
|
251
|
+
}
|
|
230
252
|
}
|
|
231
253
|
} catch (_) {
|
|
232
|
-
testScript = "
|
|
233
|
-
buildScript = "
|
|
254
|
+
testScript = "";
|
|
255
|
+
buildScript = "";
|
|
234
256
|
}
|
|
235
|
-
return { ...container, stack: "node", setupCmd, testCmd: testScript, buildCmd: buildScript, triggerFile
|
|
257
|
+
return { ...container, stack: pm === "npm" ? "node" : pm, setupCmd, testCmd: testScript, buildCmd: buildScript, triggerFile };
|
|
236
258
|
}
|
|
237
259
|
|
|
238
260
|
// 7. .NET / C# / F# / PHP / Python root file extension fallback
|
package/src/wizard-oracle.mjs
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { detectStack, parseYaml } from "./config.mjs";
|
|
4
5
|
import { runCmd } from "./git.mjs";
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Check if a command-line binary is available on the system PATH.
|
|
9
|
+
* @param {string} binName
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function hasBinary(binName) {
|
|
13
|
+
if (!binName || typeof binName !== "string") return false;
|
|
14
|
+
const bin = binName.trim().split(/\s+/)[0];
|
|
15
|
+
if (!bin) return false;
|
|
16
|
+
try {
|
|
17
|
+
const isWin = process.platform === "win32";
|
|
18
|
+
const checker = isWin ? "where" : "which";
|
|
19
|
+
const res = spawnSync(checker, [bin], { stdio: "ignore" });
|
|
20
|
+
return res.status === 0;
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
/**
|
|
7
27
|
* Inspect repository topology, workspace structure, and manifests to infer candidate verification oracles.
|
|
8
28
|
* @param {string} [root=process.cwd()]
|
|
@@ -37,18 +57,24 @@ export function detectStackOracles(root = process.cwd()) {
|
|
|
37
57
|
|
|
38
58
|
const packages = [];
|
|
39
59
|
|
|
40
|
-
// Check Node.
|
|
60
|
+
// Check Node.js package.json scripts
|
|
41
61
|
const pkgPath = join(root, "package.json");
|
|
42
62
|
if (existsSync(pkgPath)) {
|
|
43
63
|
try {
|
|
44
64
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
45
65
|
const scripts = pkg.scripts || {};
|
|
66
|
+
const hasPnpm = existsSync(join(root, "pnpm-lock.yaml")) || existsSync(join(root, "pnpm-workspace.yaml"));
|
|
67
|
+
const hasYarn = existsSync(join(root, "yarn.lock"));
|
|
68
|
+
const hasBun = existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bunfig.toml"));
|
|
69
|
+
const pm = hasPnpm ? "pnpm" : hasYarn ? "yarn" : hasBun ? "bun" : "npm";
|
|
46
70
|
|
|
47
|
-
if (scripts.test) candidates.testCmd = candidates.testCmd || "
|
|
48
|
-
if (scripts.build) candidates.buildCmd = candidates.buildCmd || "
|
|
49
|
-
if (scripts.lint) candidates.lintCmd = "
|
|
71
|
+
if (scripts.test) candidates.testCmd = candidates.testCmd || (pm === "yarn" ? "yarn test" : `${pm} test`);
|
|
72
|
+
if (scripts.build) candidates.buildCmd = candidates.buildCmd || (pm === "yarn" ? "yarn build" : `${pm} run build`);
|
|
73
|
+
if (scripts.lint) candidates.lintCmd = pm === "yarn" ? "yarn lint" : `${pm} run lint`;
|
|
50
74
|
if (scripts.typecheck || scripts["type-check"]) {
|
|
51
|
-
candidates.typecheckCmd = scripts.typecheck
|
|
75
|
+
candidates.typecheckCmd = scripts.typecheck
|
|
76
|
+
? (pm === "yarn" ? "yarn typecheck" : `${pm} run typecheck`)
|
|
77
|
+
: (pm === "yarn" ? "yarn type-check" : `${pm} run type-check`);
|
|
52
78
|
} else if (existsSync(join(root, "tsconfig.json"))) {
|
|
53
79
|
candidates.typecheckCmd = "npx tsc --noEmit";
|
|
54
80
|
}
|
|
@@ -69,7 +95,9 @@ export function detectStackOracles(root = process.cwd()) {
|
|
|
69
95
|
if (stack.includes("go") || existsSync(join(root, "go.mod"))) {
|
|
70
96
|
candidates.testCmd = candidates.testCmd || "go test ./...";
|
|
71
97
|
candidates.buildCmd = candidates.buildCmd || "go build ./...";
|
|
72
|
-
|
|
98
|
+
if (hasBinary("golangci-lint")) {
|
|
99
|
+
candidates.lintCmd = candidates.lintCmd || "golangci-lint run";
|
|
100
|
+
}
|
|
73
101
|
candidates.typecheckCmd = candidates.typecheckCmd || "go vet ./...";
|
|
74
102
|
}
|
|
75
103
|
|
|
@@ -77,8 +105,14 @@ export function detectStackOracles(root = process.cwd()) {
|
|
|
77
105
|
if (stack.includes("pytest") || existsSync(join(root, "pyproject.toml")) || existsSync(join(root, "requirements.txt"))) {
|
|
78
106
|
candidates.testCmd = candidates.testCmd || "pytest";
|
|
79
107
|
candidates.buildCmd = candidates.buildCmd || "";
|
|
80
|
-
|
|
81
|
-
|
|
108
|
+
if (hasBinary("flake8")) {
|
|
109
|
+
candidates.lintCmd = candidates.lintCmd || "flake8 .";
|
|
110
|
+
} else if (hasBinary("ruff")) {
|
|
111
|
+
candidates.lintCmd = candidates.lintCmd || "ruff check .";
|
|
112
|
+
}
|
|
113
|
+
if (hasBinary("mypy")) {
|
|
114
|
+
candidates.typecheckCmd = candidates.typecheckCmd || "mypy .";
|
|
115
|
+
}
|
|
82
116
|
}
|
|
83
117
|
|
|
84
118
|
// Discover subpackages if monorepo
|
package/src/wizard-task.mjs
CHANGED
|
@@ -280,14 +280,15 @@ export async function runTaskCreateWizard(root = process.cwd(), options = {}) {
|
|
|
280
280
|
if (useTodo) {
|
|
281
281
|
const selectedTodo = await select(
|
|
282
282
|
todos.slice(0, 10).map((t) => ({
|
|
283
|
-
label: `[${t.type}] ${t.text} (${t.file}:${t.line})`,
|
|
283
|
+
label: `[${t.type || t.tag || "TODO"}] ${t.text} (${t.file}:${t.line})`,
|
|
284
284
|
value: t,
|
|
285
285
|
})),
|
|
286
286
|
"Select TODO Candidate",
|
|
287
287
|
options
|
|
288
288
|
);
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
const todoKind = selectedTodo.type || selectedTodo.tag || "TODO";
|
|
290
|
+
title = `Fix ${todoKind}: ${selectedTodo.text.slice(0, 40)}`;
|
|
291
|
+
promptText = `Resolve ${todoKind} in ${selectedTodo.file} at line ${selectedTodo.line}: ${selectedTodo.text}`;
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
|