jules-orchestrator-kit 0.72.2 → 0.73.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/{Overseer.md → Auditor.md} +3 -3
- package/.agent/prompts/{Alchemist.md → Database.md} +1 -1
- package/.agent/prompts/Debugger.md +25 -0
- package/.agent/prompts/{Scribe.md → Docs.md} +8 -5
- package/.agent/prompts/{Spectator.md → E2E.md} +9 -6
- package/.agent/prompts/{Janitor.md → Hygiene.md} +2 -2
- package/.agent/prompts/{Bolt.md → Performance.md} +1 -1
- package/.agent/prompts/Resilience.md +20 -0
- package/.agent/prompts/Security.md +21 -0
- package/.agent/prompts/Testing.md +30 -0
- package/.agent/prompts/Types.md +19 -0
- package/.agent/rules/jules-protocol.md +4 -3
- package/AGENTS.md +78 -96
- package/CHANGELOG.md +193 -0
- package/JULES_RULES_TEMPLATE.md +83 -96
- package/LICENSE +1 -1
- package/README.md +77 -439
- package/ROADMAP_V1.md +22 -132
- package/bin/agentctl.mjs +443 -144
- package/bin/init.js +6 -3
- package/index.mjs +9 -6
- package/package.json +1 -1
- package/scripts/asset-integrity-check.mjs +1 -1
- package/scripts/doc-sync-check.mjs +47 -0
- package/scripts/generate-command-reference.mjs +39 -0
- package/scripts/jules-dispatch.mjs +12 -113
- package/scripts/jules-merge-swarm.mjs +8 -196
- package/scripts/jules-patch.mjs +7 -8
- package/scripts/jules-queue-runner.mjs +6 -8
- package/scripts/jules-scan-todos.mjs +10 -38
- package/scripts/jules-self-audit.mjs +8 -139
- package/scripts/jules-status.mjs +32 -38
- package/scripts/jules-webhook-receiver.mjs +1 -1
- package/src/assertions.mjs +5 -50
- package/src/bidi-guard.mjs +36 -0
- package/src/budget.mjs +3 -14
- package/src/config.mjs +2 -6
- package/src/dashboard.mjs +7 -9
- package/src/dispatch.mjs +212 -0
- package/src/engine.mjs +40 -16
- package/src/evidence.mjs +10 -41
- package/src/execution-envelope.mjs +13 -1
- package/src/flaky-ledger.mjs +1 -1
- package/src/fs-atomic.mjs +72 -0
- package/src/git.mjs +298 -27
- package/src/mcp.mjs +296 -7
- package/src/memory.mjs +0 -0
- package/src/merge-swarm.mjs +202 -0
- package/src/ops/cli-intent.mjs +1 -0
- package/src/ops/command-registry.mjs +796 -70
- package/src/ops/doctor-registry.mjs +134 -47
- package/src/ops/handover.mjs +3 -27
- package/src/ops/pr-harvest.mjs +1 -1
- package/src/prompt-guard.mjs +33 -3
- package/src/provider.mjs +51 -7
- package/src/remediation.mjs +2 -2
- package/src/role-resolver.mjs +113 -3
- package/src/router.mjs +19 -11
- package/src/runtime-env.mjs +67 -0
- package/src/scaffold.mjs +3 -1
- package/src/scope-guard.mjs +249 -0
- package/src/secret-scanner.mjs +530 -0
- package/src/security.mjs +81 -2972
- package/src/self-audit.mjs +140 -0
- package/src/session-ops.mjs +29 -0
- package/src/stability.mjs +8 -1
- package/src/stack-detector.mjs +5 -2
- package/src/state.mjs +45 -0
- package/src/swarm.mjs +76 -0
- package/src/task-optimizer.mjs +34 -7
- package/src/telemetry.mjs +23 -0
- package/src/test-tamper-guard.mjs +2173 -0
- package/src/todo-scanner.mjs +129 -0
- package/src/web-templates.mjs +3 -3
- package/src/webhook.mjs +10 -3
- package/src/wizard-init.mjs +12 -20
- package/src/wizard-oracle.mjs +4 -3
- package/src/wizard-task.mjs +37 -9
- package/.agent/prompts/Sentinel.md +0 -18
- package/scripts/utils.mjs +0 -241
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-audit gatekeeper: runs the 4-phase safety gate against the working
|
|
3
|
+
* tree or base branch, plus the ledger/worktree/config audits around it.
|
|
4
|
+
*
|
|
5
|
+
* This is the home of the logic that used to live in
|
|
6
|
+
* `scripts/jules-self-audit.mjs`; that file is now a minimal CLI entry point.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { gate } from "./engine.mjs";
|
|
12
|
+
import { loadConfig, parseYaml } from "./config.mjs";
|
|
13
|
+
import { matchesGlob } from "./security.mjs";
|
|
14
|
+
|
|
15
|
+
/** Convenience wrapper kept for the historical `matchGlob` name. */
|
|
16
|
+
export function matchGlob(filepath, globPattern) {
|
|
17
|
+
return matchesGlob(filepath, globPattern);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const EXECUTION_CONFIG_FILES = ["package.json", "tsconfig.json", "Cargo.toml", "go.mod", "pyproject.toml", "vite.config.ts", "jest.config.js", ".npmrc"];
|
|
21
|
+
export const RESTRICTED_AGENT_FILES = ["AGENTS.md", "JULES_RULES_TEMPLATE.md"];
|
|
22
|
+
export const COMMAND_DEFINING_FILES = ["package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".agent/jules.yml", ".agent/config.yml"];
|
|
23
|
+
|
|
24
|
+
export function loadForbiddenPatterns(configContent = "") {
|
|
25
|
+
const defaults = ["scripts/jules-*", ".agent/jules.yml", ".github/**", "**/*.pem"];
|
|
26
|
+
if (!configContent) return defaults;
|
|
27
|
+
const parsed = parseYaml(configContent);
|
|
28
|
+
const userPaths = Array.isArray(parsed.forbidden_paths) ? parsed.forbidden_paths : [];
|
|
29
|
+
return Array.from(new Set([...defaults, ...userPaths]));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadAllowedPatterns(configContent = "") {
|
|
33
|
+
if (!configContent) return [];
|
|
34
|
+
const parsed = parseYaml(configContent);
|
|
35
|
+
return Array.isArray(parsed.allow_paths) ? parsed.allow_paths : [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function validateJulesConfig(_configContent = "", jsonGuardrailsContent = "") {
|
|
39
|
+
const errors = [];
|
|
40
|
+
if (jsonGuardrailsContent) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(jsonGuardrailsContent);
|
|
43
|
+
if (Array.isArray(parsed.rules)) {
|
|
44
|
+
for (const rule of parsed.rules) {
|
|
45
|
+
try {
|
|
46
|
+
new RegExp(rule.trigger, "i");
|
|
47
|
+
} catch (err) {
|
|
48
|
+
errors.push(`Invalid RegExp trigger: ${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
errors.push(`JSON Parse Error: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { ok: errors.length === 0, errors };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseAndCleanStderr(str = "") {
|
|
60
|
+
if (typeof str !== "string") return "";
|
|
61
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").replace(/\u001b\[[0-9;]*m/g, "");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getOodaStateFile(mergeBase = "main") {
|
|
65
|
+
return `.agent/state/ooda-${mergeBase}.json`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Audit ledger states and verify integrity of ledger/state files.
|
|
70
|
+
*/
|
|
71
|
+
export function auditLedgers(opts = {}) {
|
|
72
|
+
const root = opts.root || process.env.JULES_PROJECT_ROOT || process.cwd();
|
|
73
|
+
const stateDir = join(root, ".agent", "state");
|
|
74
|
+
const exists = existsSync(stateDir);
|
|
75
|
+
return { ok: true, stateDir, exists };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Audit git worktrees and active workspace boundaries.
|
|
80
|
+
*/
|
|
81
|
+
export function auditWorktrees(opts = {}) {
|
|
82
|
+
const root = opts.root || process.env.JULES_PROJECT_ROOT || process.cwd();
|
|
83
|
+
const worktreeDir = join(root, ".agent", "worktrees");
|
|
84
|
+
const exists = existsSync(worktreeDir);
|
|
85
|
+
return { ok: true, worktreeDir, exists };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Audit gate rules (scope, payload governor, secret scanning, verification suite).
|
|
90
|
+
*/
|
|
91
|
+
export async function auditGates(opts = {}) {
|
|
92
|
+
const root = opts.root || process.env.JULES_PROJECT_ROOT || process.cwd();
|
|
93
|
+
const config = opts.config || loadConfig(root);
|
|
94
|
+
const base = opts.base || process.env.BASE_BRANCH || config.baseBranch || "main";
|
|
95
|
+
|
|
96
|
+
return await gate({
|
|
97
|
+
root,
|
|
98
|
+
config,
|
|
99
|
+
base,
|
|
100
|
+
fix: process.env.ALLOW_AUTO_REPAIR === "true",
|
|
101
|
+
allowProtected: process.env.JULES_ALLOW_COMMAND_FILE_CHANGES === "true",
|
|
102
|
+
allowTestChanges: process.env.JULES_ALLOW_TEST_CHANGES || opts.allowTestChanges,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Main self-audit entrypoint executing validation passes.
|
|
108
|
+
*/
|
|
109
|
+
export async function runSelfAudit(opts = {}) {
|
|
110
|
+
const root = opts.root || process.env.JULES_PROJECT_ROOT || process.cwd();
|
|
111
|
+
const config = loadConfig(root);
|
|
112
|
+
const base = opts.base || process.env.BASE_BRANCH || config.baseBranch || "main";
|
|
113
|
+
|
|
114
|
+
const optsWithContext = { ...opts, root, config, base };
|
|
115
|
+
|
|
116
|
+
const ledgersResult = auditLedgers(optsWithContext);
|
|
117
|
+
const worktreesResult = auditWorktrees(optsWithContext);
|
|
118
|
+
const gatesResult = await auditGates(optsWithContext);
|
|
119
|
+
|
|
120
|
+
if (!gatesResult.ok) {
|
|
121
|
+
return gatesResult;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...gatesResult,
|
|
126
|
+
audits: {
|
|
127
|
+
ledgers: ledgersResult,
|
|
128
|
+
worktrees: worktreesResult,
|
|
129
|
+
gates: gatesResult,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Preflight sandbox probe. Retained as a compatibility surface: the sandbox
|
|
136
|
+
* it once wrapped is now executed by the gate itself, so it reports ok.
|
|
137
|
+
*/
|
|
138
|
+
export async function runPreflightSandbox() {
|
|
139
|
+
return { ok: true };
|
|
140
|
+
}
|
package/src/session-ops.mjs
CHANGED
|
@@ -219,6 +219,35 @@ export async function extractSessionPatch(sessionId, opts = {}) {
|
|
|
219
219
|
* @param {object} opts
|
|
220
220
|
* @returns {Promise<{ ok: boolean, patchApplied: boolean, checkPassed: boolean, patch: string, files: string[], error?: string }>}
|
|
221
221
|
*/
|
|
222
|
+
/**
|
|
223
|
+
* Fetch a session patch with argument validation at the boundary.
|
|
224
|
+
* (Moved out of scripts/jules-patch.mjs, which keeps a CLI entry point.)
|
|
225
|
+
*/
|
|
226
|
+
export async function fetchSessionPatch(sessionId, options = {}) {
|
|
227
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
228
|
+
throw new Error("Session ID is required.");
|
|
229
|
+
}
|
|
230
|
+
return await extractSessionPatch(sessionId, options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Maps a Jules session state to the coarse bucket the status CLI reports.
|
|
235
|
+
* (Moved out of scripts/jules-status.mjs, which keeps a CLI entry point.)
|
|
236
|
+
*/
|
|
237
|
+
export function categorizeTaskStatus(statusStr = "") {
|
|
238
|
+
const s = String(statusStr).toUpperCase();
|
|
239
|
+
if (s === "AWAITING_PLAN_APPROVAL" || s === "AWAITING_USER_FEEDBACK") {
|
|
240
|
+
return "action_required";
|
|
241
|
+
}
|
|
242
|
+
if (s === "IN_PROGRESS" || s === "DISPATCHED") {
|
|
243
|
+
return "in_progress";
|
|
244
|
+
}
|
|
245
|
+
if (s === "COMPLETED" || s === "FAILED") {
|
|
246
|
+
return "completed";
|
|
247
|
+
}
|
|
248
|
+
return "unknown";
|
|
249
|
+
}
|
|
250
|
+
|
|
222
251
|
export async function applySessionPatch(sessionId, opts = {}) {
|
|
223
252
|
const root = opts.root || resolveRoot();
|
|
224
253
|
const res = await extractSessionPatch(sessionId, { ...opts, root });
|
package/src/stability.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { resolveVerify } from "./config.mjs";
|
|
3
|
-
import { computeOscillation } from "./flaky-ledger.mjs";
|
|
3
|
+
import { computeOscillation, recordVerifyRun } from "./flaky-ledger.mjs";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Executes a test command repeatedly to probe for race conditions, non-deterministic timers, or test flakiness.
|
|
@@ -11,6 +11,7 @@ import { computeOscillation } from "./flaky-ledger.mjs";
|
|
|
11
11
|
* @param {number} [options.repeat=5] - Number of consecutive iterations to execute
|
|
12
12
|
* @param {number} [options.minPassRate=1.0] - Required minimum pass rate (0.0 - 1.0)
|
|
13
13
|
* @param {number} [options.timeoutMs=30000] - Timeout per iteration in milliseconds
|
|
14
|
+
* @param {boolean} [options.record=true] - Whether to record run outcomes to .agent/state/flaky.jsonl
|
|
14
15
|
* @returns {object} { ok, repeat, passes, failures, passRate, oscillation, runs, durationMs, summary }
|
|
15
16
|
*/
|
|
16
17
|
export function runStabilityProbe(testCmd, options = {}) {
|
|
@@ -19,6 +20,7 @@ export function runStabilityProbe(testCmd, options = {}) {
|
|
|
19
20
|
const repeat = typeof options.repeat === "number" && options.repeat > 0 ? options.repeat : 5;
|
|
20
21
|
const minPassRate = typeof options.minPassRate === "number" ? options.minPassRate : 1.0;
|
|
21
22
|
const timeoutMs = options.timeoutMs || 30000;
|
|
23
|
+
const shouldRecord = options.record !== false;
|
|
22
24
|
|
|
23
25
|
const runs = [];
|
|
24
26
|
let passes = 0;
|
|
@@ -57,6 +59,11 @@ export function runStabilityProbe(testCmd, options = {}) {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
const duration = Date.now() - runStart;
|
|
62
|
+
if (shouldRecord) {
|
|
63
|
+
const fingerprint = !pass && stderr ? stderr.trim().slice(0, 200) : null;
|
|
64
|
+
recordVerifyRun(root, cmd, pass, fingerprint, duration);
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
runs.push({
|
|
61
68
|
iteration: i,
|
|
62
69
|
pass,
|
package/src/stack-detector.mjs
CHANGED
|
@@ -221,7 +221,10 @@ test("every source file parses", () => {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
export function detectEdgeRuntime(projectRoot = process.cwd()) {
|
|
224
|
-
const isCloudflare =
|
|
224
|
+
const isCloudflare =
|
|
225
|
+
existsSync(join(projectRoot, "wrangler.toml")) ||
|
|
226
|
+
existsSync(join(projectRoot, "wrangler.json")) ||
|
|
227
|
+
existsSync(join(projectRoot, "wrangler.jsonc"));
|
|
225
228
|
const isDeno = existsSync(join(projectRoot, "deno.json")) || existsSync(join(projectRoot, "deno.jsonc"));
|
|
226
229
|
const isNetlify = existsSync(join(projectRoot, "netlify.toml"));
|
|
227
230
|
|
|
@@ -441,7 +444,7 @@ export function detectPolyglotStack(projectRoot = process.cwd()) {
|
|
|
441
444
|
|
|
442
445
|
// 5. Python / Django / Elixir / Ruby / Java
|
|
443
446
|
if (existsSync(join(projectRoot, "manage.py"))) {
|
|
444
|
-
return { ...container, stack: "django", testCmd:
|
|
447
|
+
return { ...container, stack: "django", testCmd: `${pythonBin()} manage.py test --keepdb`, buildCmd: `${pythonBin()} manage.py check`, triggerFile: "manage.py" };
|
|
445
448
|
}
|
|
446
449
|
if (existsSync(join(projectRoot, "pyproject.toml")) || existsSync(join(projectRoot, "requirements.txt")) || existsSync(join(projectRoot, "setup.py"))) {
|
|
447
450
|
const triggerFile = existsSync(join(projectRoot, "pyproject.toml")) ? "pyproject.toml" : existsSync(join(projectRoot, "requirements.txt")) ? "requirements.txt" : "setup.py";
|
package/src/state.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
fsyncSync,
|
|
11
11
|
unlinkSync,
|
|
12
12
|
lstatSync,
|
|
13
|
+
statSync,
|
|
13
14
|
} from "node:fs";
|
|
14
15
|
import { join, basename } from "node:path";
|
|
15
16
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -57,6 +58,32 @@ export function getDailyLedgerPath(rootOrOpts = resolveRoot()) {
|
|
|
57
58
|
return join(getStateDir(root), `ledger-${dateStr}.jsonl`);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Delete files in a ledger/state directory older than `retentionDays`.
|
|
63
|
+
*
|
|
64
|
+
* Moved out of the deleted scripts/utils.mjs shim; the ledger rotation policy
|
|
65
|
+
* lives with the ledger.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} stateDir
|
|
68
|
+
* @param {number} [retentionDays=30]
|
|
69
|
+
*/
|
|
70
|
+
export function pruneOldLedgers(stateDir, retentionDays = 30) {
|
|
71
|
+
if (!existsSync(stateDir)) return;
|
|
72
|
+
const cutoff = Date.now() - retentionDays * 86400 * 1000;
|
|
73
|
+
try {
|
|
74
|
+
const files = readdirSync(stateDir);
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
const full = join(stateDir, f);
|
|
77
|
+
try {
|
|
78
|
+
const stat = statSync(full);
|
|
79
|
+
if (stat.isFile() && stat.mtimeMs < cutoff) {
|
|
80
|
+
unlinkSync(full);
|
|
81
|
+
}
|
|
82
|
+
} catch (_) {}
|
|
83
|
+
}
|
|
84
|
+
} catch (_) {}
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
/**
|
|
61
88
|
* How far back a task still counts against the allowance.
|
|
62
89
|
*
|
|
@@ -351,6 +378,24 @@ export function checkDailyBudget(arg1 = resolveRoot(), arg2 = 300, opts = {}) {
|
|
|
351
378
|
}
|
|
352
379
|
}
|
|
353
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Reserve one unit of the daily budget, nameably.
|
|
383
|
+
*
|
|
384
|
+
* @param {number} [maxSessions=300]
|
|
385
|
+
* @param {string} [taskKey=""]
|
|
386
|
+
* @param {string} [root] - Ledger root. Defaults to the git toplevel. Pass an
|
|
387
|
+
* explicit root to keep callers (notably tests) off the operator's real ledger.
|
|
388
|
+
*/
|
|
389
|
+
export function reserveDailyBudget(maxSessions = 300, taskKey = "", root = resolveRoot()) {
|
|
390
|
+
// The id is what makes the reservation releasable. Written without one, a
|
|
391
|
+
// reservation counted against the day and no rollback, commit or reconcile
|
|
392
|
+
// could ever name it again — it stayed charged until the ledger rotated.
|
|
393
|
+
const reservationId = `res-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
394
|
+
appendLedger({ event: "budget_reserved", reservationId, key: taskKey }, root);
|
|
395
|
+
const check = checkDailyBudget(root, maxSessions);
|
|
396
|
+
return { ok: check.ok, used: check.used, budget: maxSessions, reservationId };
|
|
397
|
+
}
|
|
398
|
+
|
|
354
399
|
export class BudgetError extends Error {
|
|
355
400
|
constructor(message) {
|
|
356
401
|
super(message);
|
package/src/swarm.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm session audit & reservation sync manifest.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the deleted `scripts/utils.mjs` shim. These four functions
|
|
5
|
+
* are the SDK-level surface index.mjs re-exports (extractPrUrls,
|
|
6
|
+
* auditSessions, buildSyncManifest, pushReservationManifest): parsing PR
|
|
7
|
+
* links out of session outputs, bucketing sessions by staleness, and writing
|
|
8
|
+
* the swarm reservation manifest the merge gate reads.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
export function extractPrUrls(outputs = []) {
|
|
15
|
+
const prs = [];
|
|
16
|
+
const regex = /https:\/\/github\.com\/[^\s/]+\/[^\s/]+\/pull\/\d+/g;
|
|
17
|
+
for (const item of outputs) {
|
|
18
|
+
const text = typeof item === "string" ? item : item?.link || JSON.stringify(item);
|
|
19
|
+
const matches = text.match(regex);
|
|
20
|
+
if (matches) {
|
|
21
|
+
for (const m of matches) {
|
|
22
|
+
if (!prs.includes(m)) prs.push(m);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return prs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function auditSessions(sessions = [], opts = {}) {
|
|
30
|
+
const staleHoursThreshold = opts.staleHoursThreshold || 24;
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const merged = [];
|
|
33
|
+
const active = [];
|
|
34
|
+
const stale = [];
|
|
35
|
+
|
|
36
|
+
for (const s of sessions) {
|
|
37
|
+
if (s.state === "MERGED") {
|
|
38
|
+
merged.push(s);
|
|
39
|
+
} else {
|
|
40
|
+
const updatedMs = s.updateTime ? new Date(s.updateTime).getTime() : now;
|
|
41
|
+
const ageHours = (now - updatedMs) / (1000 * 60 * 60);
|
|
42
|
+
if (ageHours > staleHoursThreshold) {
|
|
43
|
+
stale.push(s);
|
|
44
|
+
} else {
|
|
45
|
+
active.push(s);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { merged, active, stale };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildSyncManifest(tasks = []) {
|
|
54
|
+
const reservations = tasks.map((t) => ({ id: t.id, title: t.title, scope: t.scope }));
|
|
55
|
+
return {
|
|
56
|
+
version: 1,
|
|
57
|
+
totalTasks: tasks.length,
|
|
58
|
+
reservations,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function pushReservationManifest(manifest, projectRoot = process.cwd()) {
|
|
63
|
+
const isDry = process.env.JULES_DRY_RUN === "true" || process.env.JULES_DRY_RUN === "1";
|
|
64
|
+
const isRemote = process.env.JULES_SWARM_REMOTE_PUSH === "true";
|
|
65
|
+
|
|
66
|
+
const agentDir = join(projectRoot, ".agent");
|
|
67
|
+
if (!existsSync(agentDir)) {
|
|
68
|
+
try { mkdirSync(agentDir, { recursive: true }); } catch (_) {}
|
|
69
|
+
}
|
|
70
|
+
const manifestPath = join(agentDir, "sync-manifest.json");
|
|
71
|
+
try { writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8"); } catch (_) {}
|
|
72
|
+
|
|
73
|
+
if (isDry) return { status: "DRY_RUN", path: manifestPath };
|
|
74
|
+
if (isRemote) return { status: "PUSHED", path: manifestPath };
|
|
75
|
+
return { status: "SAVED_LOCAL", path: manifestPath };
|
|
76
|
+
}
|
package/src/task-optimizer.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { checkScope } from "./security.mjs";
|
|
|
4
4
|
import { detectStackOracles } from "./wizard-oracle.mjs";
|
|
5
5
|
import { loadConfig } from "./config.mjs";
|
|
6
6
|
import { sanitizePromptVocabulary } from "./prompt-guard.mjs";
|
|
7
|
+
import { buildGuardrailFooter } from "./wizard-task.mjs";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Calculates Levenshtein distance between two strings.
|
|
@@ -350,7 +351,7 @@ export function optimizeTaskPrompt(promptText, options = {}) {
|
|
|
350
351
|
const isWeb = Boolean(options.web || (analysis.webIntent && analysis.webIntent.isWeb));
|
|
351
352
|
const includeExplorationBudget = options.explorationBudget !== false;
|
|
352
353
|
const includeCriticGuidance = options.criticGuidance !== false;
|
|
353
|
-
const verifyCmd = analysis.oracle.command || options.verifyCmd ||
|
|
354
|
+
const verifyCmd = analysis.oracle.command || options.verifyCmd || null;
|
|
354
355
|
|
|
355
356
|
// Construct structured Markdown envelope
|
|
356
357
|
const lines = [];
|
|
@@ -359,16 +360,24 @@ export function optimizeTaskPrompt(promptText, options = {}) {
|
|
|
359
360
|
|
|
360
361
|
if (includeExplorationBudget) {
|
|
361
362
|
lines.push("## Google Labs Exploration Budget Protocol (3-Phase Discovery)");
|
|
362
|
-
lines.push("
|
|
363
|
+
lines.push("Use deep planning mode. execute this task in 3 distinct phases (Discovery, Test Formulation, Surgical Implementation):");
|
|
363
364
|
lines.push("1. **PHASE 1: DISCOVERY & SYMBOL TRACING (Stay Silent, Write NO Code)**");
|
|
364
365
|
lines.push(" - Read target source files, definitions, and dependent call sites.");
|
|
365
366
|
lines.push(" - Formulate diagnostic hypothesis and verify exact symbol signatures before making edits.");
|
|
366
367
|
lines.push("2. **PHASE 2: ORACLE FORMULATION**");
|
|
367
|
-
|
|
368
|
+
if (verifyCmd) {
|
|
369
|
+
lines.push(` - Execute baseline verification: \`${verifyCmd}\`.`);
|
|
370
|
+
} else {
|
|
371
|
+
lines.push(" - Execute baseline verification: (None specified - define verification command).");
|
|
372
|
+
}
|
|
368
373
|
lines.push(" - Identify specific test assertions, benchmarks, or status codes to satisfy.");
|
|
369
374
|
lines.push("3. **PHASE 3: SURGICAL IMPLEMENTATION & VERIFICATION**");
|
|
370
375
|
lines.push(" - Apply minimal, zero-bloat code modifications.");
|
|
371
|
-
|
|
376
|
+
if (verifyCmd) {
|
|
377
|
+
lines.push(` - Execute \`${verifyCmd}\` and verify 100% clean exit code 0.`);
|
|
378
|
+
} else {
|
|
379
|
+
lines.push(" - Execute automated verification suite and verify 100% clean exit code 0.");
|
|
380
|
+
}
|
|
372
381
|
lines.push("");
|
|
373
382
|
}
|
|
374
383
|
|
|
@@ -376,6 +385,8 @@ export function optimizeTaskPrompt(promptText, options = {}) {
|
|
|
376
385
|
lines.push(`- **Goal**: ${promptBody}`);
|
|
377
386
|
if (analysis.oracle.command) {
|
|
378
387
|
lines.push(`- **Verification Command**: \`${analysis.oracle.command}\` (Must pass cleanly with exit code 0)`);
|
|
388
|
+
} else {
|
|
389
|
+
lines.push("- **Verification Command**: (None) - WARNING: Missing test oracle.");
|
|
379
390
|
}
|
|
380
391
|
lines.push("- **Falsifiability Criteria**: Zero deleted tests, zero weakened assertions, zero lint errors.");
|
|
381
392
|
lines.push("");
|
|
@@ -401,10 +412,26 @@ export function optimizeTaskPrompt(promptText, options = {}) {
|
|
|
401
412
|
lines.push("");
|
|
402
413
|
}
|
|
403
414
|
|
|
415
|
+
const root = options.rootDir || process.cwd();
|
|
416
|
+
let cfg = options.config;
|
|
417
|
+
if (!cfg) {
|
|
418
|
+
try {
|
|
419
|
+
cfg = loadConfig(root);
|
|
420
|
+
} catch (_) {
|
|
421
|
+
cfg = {};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
404
425
|
lines.push("## Standard Guardrails");
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
426
|
+
const rawFooter = buildGuardrailFooter(cfg, {
|
|
427
|
+
baseBranch: options.baseBranch,
|
|
428
|
+
diffKb: options.diffKb,
|
|
429
|
+
}).trim();
|
|
430
|
+
const cleanedFooter = rawFooter.replace(/^---\s*\n?HARD CONSTRAINTS:\s*\n?/, "").trim();
|
|
431
|
+
lines.push(cleanedFooter);
|
|
432
|
+
if (verifyCmd) {
|
|
433
|
+
lines.push(`- Verify before finishing: Execute \`${verifyCmd}\` and confirm zero errors.`);
|
|
434
|
+
}
|
|
408
435
|
|
|
409
436
|
const optimizedPrompt = lines.join("\n");
|
|
410
437
|
|
package/src/telemetry.mjs
CHANGED
|
@@ -226,6 +226,29 @@ export function appendTelemetry(rootOrOpts = resolveRoot(), kind = "event", fiel
|
|
|
226
226
|
});
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Best-effort companion to {@link appendTelemetry}: records the event when it
|
|
231
|
+
* can, and returns null instead of throwing when it cannot.
|
|
232
|
+
*
|
|
233
|
+
* Telemetry is diagnostic data. A gate verdict must never hinge on the mutex
|
|
234
|
+
* or disk write that logs how the verdict came about, so the safety policy
|
|
235
|
+
* "telemetry failures are swallowed" lives here, in the module that owns
|
|
236
|
+
* appending, instead of being re-implemented by each caller that needs it
|
|
237
|
+
* (src/engine.mjs carried its own copy of this wrapper).
|
|
238
|
+
*
|
|
239
|
+
* @param {string} [root]
|
|
240
|
+
* @param {string} kind
|
|
241
|
+
* @param {Object} [fields={}]
|
|
242
|
+
* @returns {Object|null} Appended telemetry record, or null if it was not written
|
|
243
|
+
*/
|
|
244
|
+
export function appendTelemetryBestEffort(root = resolveRoot(), kind = "event", fields = {}) {
|
|
245
|
+
try {
|
|
246
|
+
return appendTelemetry(root, kind, fields);
|
|
247
|
+
} catch (_) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
/**
|
|
230
253
|
* Days of telemetry kept on disk.
|
|
231
254
|
*
|