@loops-adk/core 0.1.1 → 0.3.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.
@@ -1,12 +1,12 @@
1
1
  import { redactSecrets } from './chunk-JFTXJ7I2.js';
2
- import { isEngine } from './chunk-XC46B4FD.js';
2
+ import { isEngine } from './chunk-MA6NDQMO.js';
3
3
  import { isLimitError, waitMsFor } from './chunk-Y2SD7GBL.js';
4
4
  import { LoopError } from './chunk-I3STY7U6.js';
5
5
  import { readFileSync, mkdtempSync, existsSync, writeFileSync, appendFileSync, readdirSync, mkdirSync, rmSync } from 'fs';
6
6
  import { execa } from 'execa';
7
+ import { createHash, randomBytes } from 'crypto';
7
8
  import { tmpdir, homedir } from 'os';
8
9
  import { join, dirname } from 'path';
9
- import { randomBytes } from 'crypto';
10
10
 
11
11
  // src/core/describe.ts
12
12
  var META = /* @__PURE__ */ new WeakMap();
@@ -35,13 +35,30 @@ function describeConditions(input) {
35
35
  return [condLabel(input)];
36
36
  }
37
37
  var count = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
38
+ function renderContract(value) {
39
+ const c = value;
40
+ if (!c || typeof c !== "object") return void 0;
41
+ const bits = [];
42
+ if (c.tier) bits.push(`tier ${c.tier}`);
43
+ if (c.outputs?.length) bits.push(`outputs ${c.outputs.join(", ")}`);
44
+ if (c.capabilities?.length) bits.push(`capabilities ${c.capabilities.join(", ")}`);
45
+ if (c.requiresSkills?.length) bits.push(`requires ${c.requiresSkills.join(", ")}`);
46
+ if (c.usesSkills?.length) bits.push(`uses ${c.usesSkills.join(", ")}`);
47
+ if (c.humanGates?.length) bits.push(`gates ${c.humanGates.join(", ")}`);
48
+ if (c.failureModes?.length) bits.push(`failure modes ${c.failureModes.join(", ")}`);
49
+ return bits.join("; ");
50
+ }
38
51
  function renderPlan(meta, indent = "") {
39
52
  if (!meta) return [`${indent}(a runnable job, shape not introspectable)`];
40
53
  const nm = meta.name ? ` "${meta.name}"` : "";
41
54
  const out = [];
42
55
  switch (meta.kind) {
43
56
  case "loop": {
44
- const max = typeof meta.max === "number" ? ` (max ${meta.max})` : "";
57
+ const caps = [];
58
+ if (typeof meta.max === "number") caps.push(`max ${meta.max}`);
59
+ if (typeof meta.noProgress === "number")
60
+ caps.push(`stall after ${meta.noProgress} flat`);
61
+ const max = caps.length ? ` (${caps.join("; ")})` : "";
45
62
  out.push(`${indent}loop${nm}${max}`);
46
63
  const start = meta.start;
47
64
  const gate = meta.gate;
@@ -69,6 +86,10 @@ function renderPlan(meta, indent = "") {
69
86
  }
70
87
  case "agent":
71
88
  out.push(`${indent}agent${nm}${meta.ground ? " (grounded)" : ""}`);
89
+ {
90
+ const contract = renderContract(meta.contract);
91
+ if (contract) out.push(`${indent} contract: ${contract}`);
92
+ }
72
93
  break;
73
94
  case "fn":
74
95
  out.push(`${indent}fn${nm}`);
@@ -129,12 +150,59 @@ function defineSkill(skill) {
129
150
  if (!skill.instructions?.trim()) throw new Error(`defineSkill "${skill.name}": empty instructions`);
130
151
  return skill;
131
152
  }
153
+ function skillRefName(ref) {
154
+ return typeof ref === "string" ? ref : ref.name;
155
+ }
156
+ function validateName(value, label) {
157
+ if (!value?.trim()) throw new Error(`${label}: \`name\` is required`);
158
+ }
159
+ function validateSkillRef(ref, label) {
160
+ if (typeof ref === "string") {
161
+ if (!ref.trim()) throw new Error(`${label}: empty skill name`);
162
+ return;
163
+ }
164
+ defineSkill(ref);
165
+ }
132
166
  function defineAgent(def) {
133
167
  if (!def.name) throw new Error("defineAgent: `name` is required");
134
168
  if (!def.system?.trim()) throw new Error(`defineAgent "${def.name}": empty system prompt`);
135
169
  def.skills?.forEach((s) => defineSkill(s));
170
+ def.requiresSkills?.forEach(
171
+ (s) => validateSkillRef(s, `defineAgent "${def.name}" requiresSkills`)
172
+ );
173
+ def.usesSkills?.forEach(
174
+ (s) => validateSkillRef(s, `defineAgent "${def.name}" usesSkills`)
175
+ );
176
+ def.outputs?.forEach(
177
+ (o) => validateName(o.name, `defineAgent "${def.name}" outputs`)
178
+ );
179
+ def.humanGates?.forEach(
180
+ (g) => validateName(g.name, `defineAgent "${def.name}" humanGates`)
181
+ );
182
+ def.failureModes?.forEach((f) => {
183
+ if (!f.mode?.trim())
184
+ throw new Error(`defineAgent "${def.name}" failureModes: \`mode\` is required`);
185
+ if (!f.recovery?.trim())
186
+ throw new Error(`defineAgent "${def.name}" failureModes "${f.mode}": \`recovery\` is required`);
187
+ });
136
188
  return def;
137
189
  }
190
+ function agentContract(agent) {
191
+ if (!agent) return void 0;
192
+ const summary = {};
193
+ if (agent.tier) summary.tier = agent.tier;
194
+ if (agent.capabilities?.length) summary.capabilities = [...agent.capabilities];
195
+ if (agent.outputs?.length) summary.outputs = agent.outputs.map((o) => o.name);
196
+ if (agent.requiresSkills?.length)
197
+ summary.requiresSkills = agent.requiresSkills.map(skillRefName);
198
+ if (agent.usesSkills?.length)
199
+ summary.usesSkills = agent.usesSkills.map(skillRefName);
200
+ if (agent.humanGates?.length)
201
+ summary.humanGates = agent.humanGates.map((g) => g.name);
202
+ if (agent.failureModes?.length)
203
+ summary.failureModes = agent.failureModes.map((f) => f.mode);
204
+ return Object.keys(summary).length ? summary : void 0;
205
+ }
138
206
  function resolveSystem(agent) {
139
207
  if (!agent.skills?.length) return agent.system;
140
208
  const methods = agent.skills.map((s) => `### ${s.name}
@@ -748,6 +816,32 @@ async function isDirty(opts) {
748
816
  const r = await git(["status", "--porcelain"], opts);
749
817
  return r.stdout.trim().length > 0;
750
818
  }
819
+ async function workspaceFingerprint(opts) {
820
+ try {
821
+ if (!await isRepo(opts)) return void 0;
822
+ const hash = createHash("sha256");
823
+ const feed = (label, value) => {
824
+ hash.update(label);
825
+ hash.update("");
826
+ hash.update(value);
827
+ hash.update("");
828
+ };
829
+ feed("head", await headSha(opts) ?? "");
830
+ feed("status", (await git(["status", "--porcelain"], opts)).stdout);
831
+ feed("unstaged", (await git(["diff"], opts)).stdout);
832
+ feed("staged", (await git(["diff", "--cached"], opts)).stdout);
833
+ const untracked = (await git(["ls-files", "--others", "--exclude-standard"], opts)).stdout.split("\n").filter(Boolean);
834
+ for (let i = 0; i < untracked.length; i += 500) {
835
+ const chunk = untracked.slice(i, i + 500);
836
+ feed(`untracked-paths:${i}`, chunk.join("\n"));
837
+ const r = await git(["hash-object", "--", ...chunk], opts);
838
+ if (r.exitCode === 0) feed(`untracked-content:${i}`, r.stdout);
839
+ }
840
+ return hash.digest("hex");
841
+ } catch {
842
+ return void 0;
843
+ }
844
+ }
751
845
  async function commit(input, opts) {
752
846
  if (!input.allowEmpty && !await hasStagedChanges(opts)) return void 0;
753
847
  const message = input.body ? `${input.subject}
@@ -928,6 +1022,15 @@ function resetLedger(workspace) {
928
1022
  reset(ledgerPath(workspace));
929
1023
  }
930
1024
 
1025
+ // src/core/text.ts
1026
+ function oneLine(text) {
1027
+ return text.replace(/\s+/g, " ").trim();
1028
+ }
1029
+ function truncate(s, max) {
1030
+ return s.length > max ? `${s.slice(0, max).trimEnd()}
1031
+ \u2026` : s;
1032
+ }
1033
+
931
1034
  // src/core/consolidate.ts
932
1035
  var CONSOLIDATE_SYSTEM = "You maintain a project's CONSOLIDATED LEDGER from its commit history \u2014 the bounded coarse memory a fresh context reads to continue safely. Capture the current state and the open threads, and PRESERVE every binding decision, convention and constraint with its exact values verbatim (downstream work must honour them, so dropping or generalising even one is a failure). Tight markdown; MERGE new commits into the prior ledger, deduplicate, omit only narrative \u2014 never omit a decision.";
933
1036
  function digest(body, n = 280) {
@@ -966,11 +1069,6 @@ Output the updated consolidated ledger.`,
966
1069
  return result.text.trim();
967
1070
  }
968
1071
  var COMPACT_SYSTEM = "You write the HANDOFF a future agent reads if it lost ALL memory of this work. Include EVERYTHING it needs to continue safely, as structured markdown: ## Why (the problem and the root cause), ## What (exactly what changed, and where \u2014 names, paths, signatures), ## Alternatives (what was ruled out and why), ## Constraints (the invariants and limits that shaped it), ## Next (what is left or to watch). Preserve every decision and specific value verbatim. Completeness matters more than brevity \u2014 drop only literal repetition and play-by-play narration, never a decision or a detail. Omit a section only if it truly has nothing. No preamble.";
969
- function truncate(s, n) {
970
- const t = s.trim();
971
- return t.length > n ? `${t.slice(0, n).trimEnd()}
972
- \u2026` : t;
973
- }
974
1072
  async function compactLedger(ctx, text, opts = {}) {
975
1073
  const trimmed = text.trim();
976
1074
  if (!trimmed) return "";
@@ -1039,10 +1137,6 @@ function consolidateJob(config = {}) {
1039
1137
  }
1040
1138
 
1041
1139
  // src/core/ground.ts
1042
- function truncate2(s, n) {
1043
- return s.length > n ? `${s.slice(0, n).trimEnd()}
1044
- \u2026` : s;
1045
- }
1046
1140
  async function groundingText(workspace, opts = {}) {
1047
1141
  const records = await log({
1048
1142
  cwd: workspace.dir,
@@ -1059,7 +1153,7 @@ What prior iterations already did and why \u2014 read it before working so you d
1059
1153
  const head = `### ${r.sha.slice(0, 7)} ${r.subject}`;
1060
1154
  return r.body ? `${head}
1061
1155
 
1062
- ${truncate2(r.body, bodyChars)}` : head;
1156
+ ${truncate(r.body, bodyChars)}` : head;
1063
1157
  });
1064
1158
  return `${header}
1065
1159
 
@@ -1115,12 +1209,305 @@ Commits a search judged relevant \u2014 read them before working.`;
1115
1209
  const head = `### ${r.sha.slice(0, 7)} ${r.subject}`;
1116
1210
  return r.body ? `${head}
1117
1211
 
1118
- ${truncate2(r.body, bodyChars)}` : head;
1212
+ ${truncate(r.body, bodyChars)}` : head;
1119
1213
  });
1120
1214
  return `${header}
1121
1215
 
1122
1216
  ${entries.join("\n\n")}`;
1123
1217
  }
1218
+ function normalizeFeedbackSeverity(severity) {
1219
+ switch (severity) {
1220
+ case "advisory":
1221
+ return "nice-to-have";
1222
+ case "blocking":
1223
+ case void 0:
1224
+ return "block";
1225
+ default:
1226
+ return severity;
1227
+ }
1228
+ }
1229
+ function isRequiredFeedbackSeverity(severity) {
1230
+ const normalized = normalizeFeedbackSeverity(severity);
1231
+ return normalized === "block" || normalized === "should-fix";
1232
+ }
1233
+ function findingLine(finding) {
1234
+ const reviewer = finding.reviewer ? `${finding.reviewer} ` : "";
1235
+ const severity = normalizeFeedbackSeverity(finding.severity);
1236
+ const decision = finding.decision ? ` Decision: ${finding.decision}.` : "";
1237
+ const recommendation = finding.recommendation ? ` Recommendation: ${oneLine(finding.recommendation)}` : "";
1238
+ return `- ${reviewer}[${severity}]: ${oneLine(finding.evidence)}${decision}${recommendation}`;
1239
+ }
1240
+ function defaultReason(findings) {
1241
+ if (!findings?.length) return "Revision requested";
1242
+ if (findings.length === 1) return oneLine(findings[0].evidence);
1243
+ return `${findings.length} findings require another pass`;
1244
+ }
1245
+ function normalizeRevision(input) {
1246
+ const reason = input.reason?.trim() || defaultReason(input.findings);
1247
+ return {
1248
+ reason,
1249
+ target: input.target,
1250
+ findings: input.findings,
1251
+ rerun: input.rerun ?? (input.target ? "target-and-dependents" : void 0),
1252
+ source: input.source,
1253
+ decision: input.decision
1254
+ };
1255
+ }
1256
+ function revisionRequest(input, over = {}) {
1257
+ const revision = normalizeRevision(input);
1258
+ return {
1259
+ status: over.status ?? "fail",
1260
+ confidence: over.confidence,
1261
+ summary: over.summary ?? revision.reason,
1262
+ data: over.data,
1263
+ error: over.error,
1264
+ revision
1265
+ };
1266
+ }
1267
+ function kickback(to, reason, over = {}) {
1268
+ return revisionRequest({ target: to, reason }, over);
1269
+ }
1270
+ function revisionFromOutcome(outcome) {
1271
+ return outcome.revision;
1272
+ }
1273
+ function feedbackBlock(outcome) {
1274
+ const revision = revisionFromOutcome(outcome);
1275
+ const parts = [
1276
+ "## Feedback to address",
1277
+ "A review or downstream stage requested another pass. Address this before unrelated work."
1278
+ ];
1279
+ if (revision?.target) parts.push(`Target: ${revision.target}`);
1280
+ if (revision?.source) parts.push(`Source: ${revision.source}`);
1281
+ if (revision?.decision) parts.push(`Caller decision: ${revision.decision}`);
1282
+ const reason = revision?.reason ?? outcome.summary;
1283
+ if (reason) parts.push(`Reason: ${reason}`);
1284
+ const findings = revision?.findings ?? [];
1285
+ if (findings.length) {
1286
+ parts.push("Findings:");
1287
+ parts.push(findings.map(findingLine).join("\n"));
1288
+ }
1289
+ return parts.join("\n\n");
1290
+ }
1291
+ function graphPositionBlock(graph) {
1292
+ return [
1293
+ "## Graph position",
1294
+ `DAG: ${graph.dag}`,
1295
+ `Current node: ${graph.node}`,
1296
+ `Path: ${graph.path.join(" > ")}`,
1297
+ `Depends on: ${graph.needs.length ? graph.needs.join(", ") : "none"}`,
1298
+ `Direct dependents: ${graph.dependents.length ? graph.dependents.join(", ") : "none"}`
1299
+ ].join("\n");
1300
+ }
1301
+ async function runReviewer(reviewer, index, ctx) {
1302
+ const name = reviewer.name ?? `reviewer-${index + 1}`;
1303
+ try {
1304
+ if ("job" in reviewer) {
1305
+ const outcome = await reviewer.job(ctx);
1306
+ return {
1307
+ name,
1308
+ met: outcome.status === "pass",
1309
+ confidence: outcome.confidence,
1310
+ reason: outcome.summary ?? outcome.status
1311
+ };
1312
+ }
1313
+ const result = await toCondition(reviewer.review)(
1314
+ ctx,
1315
+ ctx.lastOutcome
1316
+ );
1317
+ return {
1318
+ name,
1319
+ met: result.met,
1320
+ confidence: result.confidence,
1321
+ reason: result.reason
1322
+ };
1323
+ } catch (e) {
1324
+ if (ctx.signal.aborted) throw e;
1325
+ return {
1326
+ name,
1327
+ met: false,
1328
+ reason: `reviewer errored: ${e instanceof Error ? e.message : String(e)}`
1329
+ };
1330
+ }
1331
+ }
1332
+ function reviewFinding(result) {
1333
+ return {
1334
+ reviewer: result.name,
1335
+ severity: "block",
1336
+ evidence: result.reason
1337
+ };
1338
+ }
1339
+ function findingSeverityCounts(findings) {
1340
+ const counts = {};
1341
+ for (const finding of findings) {
1342
+ const severity = normalizeFeedbackSeverity(finding.severity);
1343
+ counts[severity] = (counts[severity] ?? 0) + 1;
1344
+ }
1345
+ return counts;
1346
+ }
1347
+ function reviewPanel(config) {
1348
+ const label = config.label ?? "review-panel";
1349
+ if (!config.reviewers.length)
1350
+ throw new LoopError({
1351
+ code: "CONFIG",
1352
+ message: `reviewPanel "${label}": at least one reviewer is required`
1353
+ });
1354
+ const job = async (ctx) => {
1355
+ ctx.emit({ kind: "job:start", ts: Date.now(), path: [...ctx.path], label });
1356
+ const results = await Promise.all(
1357
+ config.reviewers.map((reviewer, i) => runReviewer(reviewer, i, ctx))
1358
+ );
1359
+ const passedCount = results.filter((r) => r.met).length;
1360
+ const required = config.pass === void 0 || config.pass === "all" ? results.length : config.pass;
1361
+ const findings = results.filter((r) => !r.met).map(reviewFinding);
1362
+ const passed = passedCount >= required;
1363
+ const summaryHead = `Review panel: ${passedCount}/${results.length} reviewer(s) cleared`;
1364
+ const summary = findings.length ? `${summaryHead}.
1365
+ ${findings.map(findingLine).join("\n")}` : `${summaryHead}.`;
1366
+ const scored = results.map((r) => r.confidence).filter((c) => c != null);
1367
+ const confidence = scored.length ? scored.reduce((sum, c) => sum + c, 0) / scored.length : void 0;
1368
+ const data = {
1369
+ findings,
1370
+ results,
1371
+ passed: passedCount,
1372
+ required,
1373
+ severityCounts: findingSeverityCounts(findings)
1374
+ };
1375
+ const outcome = passed ? { status: "pass", summary, confidence, data } : revisionRequest(
1376
+ {
1377
+ target: config.target,
1378
+ // A clean one-line reason. The findings ride the `findings` array, so
1379
+ // feedbackBlock renders them once (not embedded in the reason too) and
1380
+ // the records/tail `reason` stays a single tidy line. The full
1381
+ // multi-line `summary` is kept on the outcome below for logs/TUI.
1382
+ reason: `${summaryHead}.`,
1383
+ findings,
1384
+ rerun: config.rerun
1385
+ },
1386
+ { summary, confidence, data }
1387
+ );
1388
+ ctx.emit({ kind: "job:end", ts: Date.now(), path: [...ctx.path], label, outcome });
1389
+ return outcome;
1390
+ };
1391
+ return setMeta(job, { kind: "reviewPanel", name: label });
1392
+ }
1393
+ async function gitOutput(cwd, args, signal) {
1394
+ const out = await execa("git", args, {
1395
+ cwd,
1396
+ reject: false,
1397
+ stripFinalNewline: false,
1398
+ cancelSignal: signal
1399
+ });
1400
+ return out.stdout.trim();
1401
+ }
1402
+ async function resolveFiles(ctx, patterns) {
1403
+ const fromGit = await gitOutput(
1404
+ ctx.workspace.dir,
1405
+ ["ls-files", "--", ...patterns],
1406
+ ctx.signal
1407
+ ).catch(() => "");
1408
+ const files = fromGit ? fromGit.split("\n").filter(Boolean) : [];
1409
+ if (files.length) return files;
1410
+ return patterns.filter((p) => existsSync(join(ctx.workspace.dir, p)));
1411
+ }
1412
+ function reviewContext(config) {
1413
+ return async (ctx, last) => {
1414
+ const max = config.maxChars ?? 6e3;
1415
+ const buildTests = async () => {
1416
+ if (!config.tests) return [];
1417
+ if (config.tests === true) {
1418
+ const lines = [];
1419
+ if (last?.status) lines.push(`Last outcome status: ${last.status}`);
1420
+ if (last?.summary) lines.push(`Last outcome summary: ${last.summary}`);
1421
+ if (last?.data !== void 0) {
1422
+ let rendered;
1423
+ try {
1424
+ rendered = JSON.stringify(last.data, null, 2);
1425
+ } catch {
1426
+ rendered = String(last.data);
1427
+ }
1428
+ lines.push(`Last outcome data: ${rendered}`);
1429
+ }
1430
+ return lines.length ? [`## Test and outcome context
1431
+
1432
+ ${lines.join("\n")}`] : [];
1433
+ }
1434
+ const cwd = config.tests.cwd ?? ctx.workspace.dir;
1435
+ const result = await execa(
1436
+ config.tests.command,
1437
+ config.tests.args ?? [],
1438
+ { cwd, reject: false, stripFinalNewline: false, cancelSignal: ctx.signal }
1439
+ ).catch((e) => {
1440
+ if (ctx.signal.aborted) throw e;
1441
+ return {
1442
+ exitCode: void 0,
1443
+ stdout: "",
1444
+ stderr: e instanceof Error ? e.message : String(e)
1445
+ };
1446
+ });
1447
+ const exit = result.exitCode ?? "(command did not run)";
1448
+ return [
1449
+ `## Test command
1450
+
1451
+ ${config.tests.command} ${(config.tests.args ?? []).join(" ")}
1452
+
1453
+ exit: ${exit}
1454
+
1455
+ stdout:
1456
+ ${truncate(result.stdout ?? "", max)}
1457
+
1458
+ stderr:
1459
+ ${truncate(result.stderr ?? "", max)}`
1460
+ ];
1461
+ };
1462
+ const buildDiff = async () => {
1463
+ if (!config.diff) return [];
1464
+ const diff2 = await gitOutput(
1465
+ ctx.workspace.dir,
1466
+ ["diff", "HEAD", "--"],
1467
+ ctx.signal
1468
+ ).catch(() => "");
1469
+ return diff2 ? [`## Git diff
1470
+
1471
+ ${truncate(diff2, max)}`] : [];
1472
+ };
1473
+ const buildFiles = async () => {
1474
+ if (!config.files?.length) return [];
1475
+ const files2 = await resolveFiles(ctx, config.files);
1476
+ const out = [];
1477
+ for (const file of files2) {
1478
+ const path = join(ctx.workspace.dir, file);
1479
+ if (!existsSync(path)) continue;
1480
+ out.push(`## File: ${file}
1481
+
1482
+ ${truncate(readFileSync(path, "utf8"), max)}`);
1483
+ }
1484
+ return out;
1485
+ };
1486
+ const buildLedger = async () => {
1487
+ if (!config.ledger) return [];
1488
+ const out = [];
1489
+ const live = [readPrompt(ctx.workspace), readLedger(ctx.workspace)].filter(Boolean).join("\n\n");
1490
+ if (live) out.push(`## Live ledger
1491
+
1492
+ ${truncate(live, max)}`);
1493
+ const committed = await groundingText(ctx.workspace, {
1494
+ max: 5,
1495
+ bodyChars: 1200,
1496
+ signal: ctx.signal
1497
+ }).catch(() => "");
1498
+ if (committed) out.push(truncate(committed, max));
1499
+ return out;
1500
+ };
1501
+ const [tests, diff, files, ledger] = await Promise.all([
1502
+ buildTests(),
1503
+ buildDiff(),
1504
+ buildFiles(),
1505
+ buildLedger()
1506
+ ]);
1507
+ const sections = [...tests, ...diff, ...files, ...ledger];
1508
+ return sections.join("\n\n---\n\n") || "(no review context)";
1509
+ };
1510
+ }
1124
1511
 
1125
1512
  // src/core/job.ts
1126
1513
  var HANDOFF_MARK = "===HANDOFF===";
@@ -1198,6 +1585,16 @@ var TERMINAL = (text) => ({
1198
1585
  summary: text.trim().slice(0, 280),
1199
1586
  data: text
1200
1587
  });
1588
+ function withOperationalContext(ctx, userPrompt, config) {
1589
+ const parts = [userPrompt];
1590
+ if (config.consumeFeedback && ctx.lastReview) {
1591
+ parts.push(feedbackBlock(ctx.lastReview));
1592
+ }
1593
+ if (config.graphContext && ctx.graph) {
1594
+ parts.push(graphPositionBlock(ctx.graph));
1595
+ }
1596
+ return parts.join("\n\n---\n\n");
1597
+ }
1201
1598
  function agentJob(config) {
1202
1599
  const job = async (ctx) => {
1203
1600
  const path = [...ctx.path];
@@ -1205,7 +1602,8 @@ function agentJob(config) {
1205
1602
  ctx.emit({ kind: "job:start", ts: Date.now(), path, label });
1206
1603
  const engine = ctx.resolveEngine(config.engine);
1207
1604
  const userPrompt = typeof config.prompt === "function" ? await config.prompt(ctx) : config.prompt;
1208
- const prompt = config.ground ? await withGrounding(ctx, userPrompt, config.ground) : userPrompt;
1605
+ const contextualPrompt = withOperationalContext(ctx, userPrompt, config);
1606
+ const prompt = config.ground ? await withGrounding(ctx, contextualPrompt, config.ground) : contextualPrompt;
1209
1607
  const system = config.system !== void 0 ? typeof config.system === "function" ? config.system(ctx) : config.system : config.agent ? resolveSystem(config.agent) : void 0;
1210
1608
  let result;
1211
1609
  const toolUses = /* @__PURE__ */ new Map();
@@ -1306,7 +1704,8 @@ function agentJob(config) {
1306
1704
  return setMeta(job, {
1307
1705
  kind: "agent",
1308
1706
  name: config.label ?? config.agent?.name ?? "agent",
1309
- ground: !!config.ground
1707
+ ground: !!config.ground,
1708
+ contract: agentContract(config.agent)
1310
1709
  });
1311
1710
  }
1312
1711
  function composeWay(ctx, last) {
@@ -1382,14 +1781,6 @@ function commitJob(config) {
1382
1781
  }
1383
1782
  };
1384
1783
  }
1385
- function kickback(to, reason, over) {
1386
- return {
1387
- status: "fail",
1388
- summary: reason,
1389
- ...over,
1390
- kickback: { to, reason }
1391
- };
1392
- }
1393
1784
  function fnJob(label, fn) {
1394
1785
  const job = async (ctx) => {
1395
1786
  const path = [...ctx.path];
@@ -1419,6 +1810,102 @@ function fnJob(label, fn) {
1419
1810
  return setMeta(job, { kind: "fn", name: label });
1420
1811
  }
1421
1812
 
1813
+ // src/core/progress.ts
1814
+ function resolveNoProgress(input) {
1815
+ if (input == null) return void 0;
1816
+ const cfg = typeof input === "number" ? { window: input } : input;
1817
+ return {
1818
+ ...cfg,
1819
+ window: cfg.window ?? 3,
1820
+ minConfidenceDelta: cfg.minConfidenceDelta ?? 0.02
1821
+ };
1822
+ }
1823
+ var ProgressTracker = class {
1824
+ window;
1825
+ minConfidenceDelta;
1826
+ /** Every state this run has reached, namespaced by channel. */
1827
+ seen = /* @__PURE__ */ new Set();
1828
+ /** Confidence high-water mark — the best score at the last progress point. */
1829
+ best;
1830
+ /** The current run of consecutive no-progress iterations. */
1831
+ stalledRun = [];
1832
+ lastEvidence = [];
1833
+ lastReason = "gate not met";
1834
+ indeterminate = 0;
1835
+ sampled = 0;
1836
+ constructor(cfg) {
1837
+ this.window = cfg.window;
1838
+ this.minConfidenceDelta = cfg.minConfidenceDelta;
1839
+ }
1840
+ /**
1841
+ * Record one iteration. Returns a `StallReport` when this sample fills the
1842
+ * window, else undefined.
1843
+ */
1844
+ record(sample) {
1845
+ this.sampled += 1;
1846
+ if (sample.reason) this.lastReason = sample.reason;
1847
+ const flat = [];
1848
+ let progressed = false;
1849
+ let channels = 0;
1850
+ if (sample.fingerprint !== void 0) {
1851
+ channels += 1;
1852
+ const key = `fp:${sample.fingerprint}`;
1853
+ if (this.seen.has(key)) {
1854
+ flat.push("workspace: no state this run has not already visited");
1855
+ } else {
1856
+ this.seen.add(key);
1857
+ progressed = true;
1858
+ }
1859
+ }
1860
+ if (sample.signal !== void 0) {
1861
+ channels += 1;
1862
+ const key = `sig:${sample.signal}`;
1863
+ if (this.seen.has(key)) {
1864
+ flat.push(`signal: "${sample.signal}" already seen this run`);
1865
+ } else {
1866
+ this.seen.add(key);
1867
+ progressed = true;
1868
+ }
1869
+ }
1870
+ if (sample.confidence !== void 0) {
1871
+ channels += 1;
1872
+ if (this.best === void 0 || sample.confidence >= this.best + this.minConfidenceDelta) {
1873
+ this.best = Math.max(this.best ?? -Infinity, sample.confidence);
1874
+ progressed = true;
1875
+ } else {
1876
+ flat.push(
1877
+ `confidence ${sample.confidence.toFixed(2)} did not improve on ${this.best.toFixed(2)} (needs +${this.minConfidenceDelta})`
1878
+ );
1879
+ }
1880
+ }
1881
+ if (channels === 0) {
1882
+ this.indeterminate += 1;
1883
+ return void 0;
1884
+ }
1885
+ if (progressed) {
1886
+ this.stalledRun = [];
1887
+ return void 0;
1888
+ }
1889
+ this.stalledRun.push(sample.iteration);
1890
+ this.lastEvidence = flat;
1891
+ if (this.stalledRun.length < this.window) return void 0;
1892
+ return {
1893
+ window: this.window,
1894
+ iterations: [...this.stalledRun],
1895
+ reason: this.lastReason,
1896
+ evidence: [...this.lastEvidence]
1897
+ };
1898
+ }
1899
+ /**
1900
+ * True when the detector has seen a full window of samples and none carried
1901
+ * any evidence channel — detection is configured but cannot fire. The loop
1902
+ * uses this to warn once instead of failing silently-inert.
1903
+ */
1904
+ isInert() {
1905
+ return this.indeterminate >= this.window && this.indeterminate === this.sampled;
1906
+ }
1907
+ };
1908
+
1422
1909
  // src/core/context.ts
1423
1910
  function childContext(parent, over) {
1424
1911
  return {
@@ -1439,6 +1926,7 @@ function childContext(parent, over) {
1439
1926
  log: parent.log,
1440
1927
  depth: over.depth,
1441
1928
  path: over.path,
1929
+ graph: over.graph ?? parent.graph,
1442
1930
  // Inherit the enclosing iteration by default. A `loop` always passes one
1443
1931
  // explicitly; a `dag`/`sequence` does not, so without this a node nested in a
1444
1932
  // loop would reset to 0 — the "Attempt 0" confound where a retry body could not
@@ -1480,6 +1968,7 @@ function loop(config) {
1480
1968
  const until = config.until ? toCondition(config.until) : void 0;
1481
1969
  const stopOn = config.stopOn ? toCondition(config.stopOn) : void 0;
1482
1970
  const onError = config.retry?.onError ?? "continue";
1971
+ const noProgress = resolveNoProgress(config.noProgress);
1483
1972
  const job = async (parent) => {
1484
1973
  const path = [...parent.path, config.name];
1485
1974
  const depth = parent.depth + 1;
@@ -1566,6 +2055,8 @@ function loop(config) {
1566
2055
  let last;
1567
2056
  let consecutiveErrors = 0;
1568
2057
  let consecutiveReviewFails = 0;
2058
+ const tracker = noProgress ? new ProgressTracker(noProgress) : void 0;
2059
+ let warnedInert = false;
1569
2060
  while (true) {
1570
2061
  await yieldToLoop();
1571
2062
  if (parent.signal.aborted)
@@ -1586,6 +2077,7 @@ function loop(config) {
1586
2077
  }
1587
2078
  iteration += 1;
1588
2079
  const ctx = ctxAt(iteration, last);
2080
+ let turnReview;
1589
2081
  parent.emit({ kind: "loop:iteration", ts: ts(), path, iteration });
1590
2082
  let bodyThrew = false;
1591
2083
  try {
@@ -1746,13 +2238,18 @@ function loop(config) {
1746
2238
  iteration
1747
2239
  });
1748
2240
  }
2241
+ const reviewPassed = reviewOutcome.status === "pass";
2242
+ const restartsExhausted = config.maxReviewRestarts != null && consecutiveReviewFails + 1 >= config.maxReviewRestarts;
2243
+ const iterationsRemain = config.max == null || iteration < config.max;
2244
+ const willReenter = !reviewPassed && !restartsExhausted && iterationsRemain;
1749
2245
  parent.emit({
1750
2246
  kind: "loop:review",
1751
2247
  ts: ts(),
1752
2248
  path,
1753
- outcome: reviewOutcome
2249
+ outcome: reviewOutcome,
2250
+ accepted: willReenter
1754
2251
  });
1755
- if (reviewOutcome.status === "pass") {
2252
+ if (reviewPassed) {
1756
2253
  await recordMilestone(ctxAt(iteration, last));
1757
2254
  return finish(
1758
2255
  {
@@ -1766,11 +2263,12 @@ function loop(config) {
1766
2263
  }
1767
2264
  consecutiveReviewFails += 1;
1768
2265
  lastReview = reviewOutcome;
2266
+ turnReview = reviewOutcome;
1769
2267
  parent.log(
1770
2268
  `review did not pass (${reviewOutcome.summary ?? reviewOutcome.status}); re-entering ${config.name}`,
1771
2269
  "warn"
1772
2270
  );
1773
- if (config.maxReviewRestarts != null && consecutiveReviewFails >= config.maxReviewRestarts) {
2271
+ if (restartsExhausted) {
1774
2272
  return finish(
1775
2273
  {
1776
2274
  status: "exhausted",
@@ -1781,6 +2279,62 @@ function loop(config) {
1781
2279
  );
1782
2280
  }
1783
2281
  }
2282
+ if (tracker) {
2283
+ let fingerprint;
2284
+ if (noProgress.workspace !== false) {
2285
+ fingerprint = await workspaceFingerprint({
2286
+ cwd: ctx.workspace.dir,
2287
+ signal: parent.signal
2288
+ });
2289
+ }
2290
+ let signalValue;
2291
+ if (noProgress.signal) {
2292
+ try {
2293
+ const v = await noProgress.signal(ctx, last);
2294
+ signalValue = v == null ? void 0 : String(v);
2295
+ } catch (e) {
2296
+ throw LoopError.from(e, {
2297
+ code: "VALIDATION",
2298
+ phase: "body",
2299
+ path,
2300
+ iteration
2301
+ });
2302
+ }
2303
+ }
2304
+ const report = tracker.record({
2305
+ iteration,
2306
+ fingerprint,
2307
+ signal: signalValue,
2308
+ confidence: turnReview?.confidence ?? conv.confidence ?? last.confidence,
2309
+ reason: turnReview ? turnReview.summary ?? "review rejected" : conv.reason
2310
+ });
2311
+ if (!warnedInert && tracker.isInert()) {
2312
+ warnedInert = true;
2313
+ parent.log(
2314
+ `noProgress is set on ${config.name} but no evidence channel exists (no git workspace, no gate confidence, no custom signal); stall detection is inert`,
2315
+ "warn"
2316
+ );
2317
+ }
2318
+ if (report) {
2319
+ parent.emit({
2320
+ kind: "loop:stall",
2321
+ ts: ts(),
2322
+ path,
2323
+ iteration,
2324
+ report
2325
+ });
2326
+ return finish(
2327
+ {
2328
+ status: "exhausted",
2329
+ summary: `stalled after ${report.iterations.length} iterations with no observable progress: ${report.reason}`,
2330
+ confidence: last.confidence,
2331
+ data: last.data,
2332
+ stall: report
2333
+ },
2334
+ iteration
2335
+ );
2336
+ }
2337
+ }
1784
2338
  if (config.delayMs) await delay(config.delayMs, parent.signal);
1785
2339
  }
1786
2340
  } catch (e) {
@@ -1802,6 +2356,7 @@ function loop(config) {
1802
2356
  kind: "loop",
1803
2357
  name: config.name,
1804
2358
  max: config.max,
2359
+ noProgress: noProgress?.window,
1805
2360
  start: describeConditions(config.start),
1806
2361
  gate: describeConditions(config.until),
1807
2362
  stopOn: describeConditions(config.stopOn),
@@ -1866,14 +2421,14 @@ var EngineRegistry = class {
1866
2421
  this.register(
1867
2422
  "agent-sdk",
1868
2423
  (o) => lazy(
1869
- () => import('./agent-sdk-RF5VJZAT.js').then((m) => new m.AgentSdkEngine(o)),
2424
+ () => import('./agent-sdk-4QJDWM7N.js').then((m) => new m.AgentSdkEngine(o)),
1870
2425
  "agent-sdk"
1871
2426
  )
1872
2427
  );
1873
2428
  this.register(
1874
2429
  "claude-cli",
1875
2430
  (o) => lazy(
1876
- () => import('./claude-cli-U7WEVAOL.js').then((m) => new m.ClaudeCliEngine(o)),
2431
+ () => import('./claude-cli-75AOQUKG.js').then((m) => new m.ClaudeCliEngine(o)),
1877
2432
  "claude-cli"
1878
2433
  )
1879
2434
  );
@@ -1886,7 +2441,7 @@ var EngineRegistry = class {
1886
2441
  );
1887
2442
  this.register(
1888
2443
  "codex",
1889
- (o) => lazy(() => import('./codex-6I5UZ2HM.js').then((m) => new m.CodexEngine(o)), "codex")
2444
+ (o) => lazy(() => import('./codex-LYZF52WL.js').then((m) => new m.CodexEngine(o)), "codex")
1890
2445
  );
1891
2446
  }
1892
2447
  };
@@ -1972,6 +2527,188 @@ var Stats = class {
1972
2527
  return m;
1973
2528
  }
1974
2529
  };
2530
+ function ensureDir2(path) {
2531
+ const dir = dirname(path);
2532
+ if (dir && dir !== ".") mkdirSync(dir, { recursive: true });
2533
+ }
2534
+ function outcomeSummary(outcome) {
2535
+ return {
2536
+ status: outcome.status,
2537
+ summary: outcome.summary,
2538
+ confidence: outcome.confidence
2539
+ };
2540
+ }
2541
+ function strongestFinding(findings) {
2542
+ if (!findings?.length) return void 0;
2543
+ const severities = findings.map((f) => normalizeFeedbackSeverity(f.severity));
2544
+ if (severities.includes("block")) return "block";
2545
+ if (severities.includes("should-fix")) return "should-fix";
2546
+ if (severities.includes("nice-to-have")) return "nice-to-have";
2547
+ if (severities.includes("approve")) return "approve";
2548
+ return void 0;
2549
+ }
2550
+ function emittedRevisionRecord(event, outcome) {
2551
+ const revision = revisionFromOutcome(outcome);
2552
+ return revision ? [
2553
+ {
2554
+ kind: "revision-emitted",
2555
+ ts: event.ts,
2556
+ path: event.path,
2557
+ sourceEvent: "job:end",
2558
+ revision
2559
+ }
2560
+ ] : [];
2561
+ }
2562
+ function semanticRecordsFromEvent(event) {
2563
+ switch (event.kind) {
2564
+ case "job:start":
2565
+ return [
2566
+ {
2567
+ kind: "dispatch",
2568
+ ts: event.ts,
2569
+ path: event.path,
2570
+ unit: "job",
2571
+ label: event.label
2572
+ }
2573
+ ];
2574
+ case "dag:node":
2575
+ if (event.phase === "start")
2576
+ return [
2577
+ {
2578
+ kind: "dispatch",
2579
+ ts: event.ts,
2580
+ path: [...event.path, event.node],
2581
+ unit: "dag-node",
2582
+ node: event.node,
2583
+ attempt: event.attempt
2584
+ }
2585
+ ];
2586
+ return event.outcome ? [
2587
+ {
2588
+ kind: "completion",
2589
+ ts: event.ts,
2590
+ path: [...event.path, event.node],
2591
+ unit: "dag-node",
2592
+ label: event.node,
2593
+ outcome: outcomeSummary(event.outcome),
2594
+ attempt: event.attempt
2595
+ }
2596
+ ] : [];
2597
+ case "job:end":
2598
+ return [
2599
+ {
2600
+ kind: "completion",
2601
+ ts: event.ts,
2602
+ path: event.path,
2603
+ unit: "job",
2604
+ label: event.label,
2605
+ outcome: outcomeSummary(event.outcome)
2606
+ },
2607
+ ...emittedRevisionRecord(event, event.outcome)
2608
+ ];
2609
+ case "loop:review": {
2610
+ if (event.outcome.status === "pass") return [];
2611
+ const revision = revisionFromOutcome(event.outcome);
2612
+ const decision = event.accepted === false ? "rejected" : "accepted";
2613
+ const records = [
2614
+ {
2615
+ kind: "surfacing",
2616
+ ts: event.ts,
2617
+ path: event.path,
2618
+ source: "loop-review",
2619
+ decision,
2620
+ severity: strongestFinding(revision?.findings),
2621
+ reason: revision?.reason ?? event.outcome.summary ?? event.outcome.status
2622
+ }
2623
+ ];
2624
+ if (revision) {
2625
+ records.push({
2626
+ kind: "revision-routed",
2627
+ ts: event.ts,
2628
+ path: event.path,
2629
+ sourceEvent: "loop:review",
2630
+ decision,
2631
+ revision
2632
+ });
2633
+ }
2634
+ return records;
2635
+ }
2636
+ case "loop:end":
2637
+ return [
2638
+ {
2639
+ kind: "completion",
2640
+ ts: event.ts,
2641
+ path: event.path,
2642
+ unit: "loop",
2643
+ outcome: outcomeSummary(event.outcome),
2644
+ iterations: event.iterations
2645
+ }
2646
+ ];
2647
+ case "dag:kickback": {
2648
+ const at = [...event.path, event.to];
2649
+ const decision = event.accepted ? "accepted" : "rejected";
2650
+ return [
2651
+ {
2652
+ kind: "surfacing",
2653
+ ts: event.ts,
2654
+ path: at,
2655
+ source: "dag-kickback",
2656
+ decision,
2657
+ severity: "block",
2658
+ from: event.from,
2659
+ to: event.to,
2660
+ reason: event.reason,
2661
+ note: event.note
2662
+ },
2663
+ {
2664
+ kind: "revision-routed",
2665
+ ts: event.ts,
2666
+ path: at,
2667
+ sourceEvent: "dag:kickback",
2668
+ decision,
2669
+ revision: {
2670
+ target: event.to,
2671
+ reason: event.reason,
2672
+ source: event.from,
2673
+ rerun: event.accepted ? "target-and-dependents" : void 0
2674
+ }
2675
+ }
2676
+ ];
2677
+ }
2678
+ case "dag:end":
2679
+ return [
2680
+ {
2681
+ kind: "completion",
2682
+ ts: event.ts,
2683
+ path: event.path,
2684
+ unit: "dag",
2685
+ outcome: outcomeSummary(event.outcome)
2686
+ }
2687
+ ];
2688
+ default:
2689
+ return [];
2690
+ }
2691
+ }
2692
+ function makeSemanticRecorder(path) {
2693
+ try {
2694
+ ensureDir2(path);
2695
+ writeFileSync(path, "");
2696
+ } catch {
2697
+ return () => {
2698
+ };
2699
+ }
2700
+ return (event) => {
2701
+ const records = semanticRecordsFromEvent(event);
2702
+ if (!records.length) return;
2703
+ try {
2704
+ for (const record of records) {
2705
+ appendFileSync(path, `${JSON.stringify(record)}
2706
+ `);
2707
+ }
2708
+ } catch {
2709
+ }
2710
+ };
2711
+ }
1975
2712
  var NOISE = /* @__PURE__ */ new Set([
1976
2713
  "engine:text",
1977
2714
  "engine:thinking"
@@ -1990,11 +2727,13 @@ function startSupervisor(input) {
1990
2727
  const dir = join(runsHome(), input.runId);
1991
2728
  mkdirSync(dir, { recursive: true });
1992
2729
  const eventsPath = join(dir, "events.jsonl");
2730
+ const semanticPath = join(dir, "semantic.jsonl");
1993
2731
  const statusPath = join(dir, "status.json");
1994
2732
  try {
1995
2733
  writeFileSync(eventsPath, "");
1996
2734
  } catch {
1997
2735
  }
2736
+ const semanticSink = makeSemanticRecorder(semanticPath);
1998
2737
  const status = {
1999
2738
  runId: input.runId,
2000
2739
  pid: process.pid,
@@ -2025,6 +2764,7 @@ function startSupervisor(input) {
2025
2764
  `);
2026
2765
  } catch {
2027
2766
  }
2767
+ semanticSink(event);
2028
2768
  }
2029
2769
  switch (event.kind) {
2030
2770
  case "loop:iteration":
@@ -2101,6 +2841,9 @@ function listRuns() {
2101
2841
  function runEventsPath(runId) {
2102
2842
  return join(runsHome(), runId, "events.jsonl");
2103
2843
  }
2844
+ function runSemanticRecordsPath(runId) {
2845
+ return join(runsHome(), runId, "semantic.jsonl");
2846
+ }
2104
2847
  function formatEvent(event) {
2105
2848
  const at = event.path.length ? `${event.path.join(" \u203A ")} ` : "";
2106
2849
  switch (event.kind) {
@@ -2118,6 +2861,8 @@ function formatEvent(event) {
2118
2861
  return `${at}\u25C2 ${event.outcome.status} (${event.iterations} iter)`;
2119
2862
  case "dag:node":
2120
2863
  return `${at}\xB7 node ${event.node}: ${event.phase}${event.outcome ? ` (${event.outcome.status})` : ""}`;
2864
+ case "dag:kickback":
2865
+ return `${at}\u21A9 kickback ${event.accepted ? "accepted" : "rejected"} ${event.from} -> ${event.to}: ${event.reason}${event.note ? ` (${event.note})` : ""}`;
2121
2866
  case "dag:end":
2122
2867
  return `${at}\u25C2 dag ${event.outcome.status}`;
2123
2868
  case "job:start":
@@ -2128,6 +2873,8 @@ function formatEvent(event) {
2128
2873
  return `${at} tool ${event.name} ${event.phase}`;
2129
2874
  case "engine:usage":
2130
2875
  return `${at} ${event.model}: ${event.usage.inputTokens}/${event.usage.outputTokens} tok`;
2876
+ case "loop:stall":
2877
+ return `${at}\u23F9 stalled after ${event.report.iterations.length} no-progress iterations: ${event.report.reason}`;
2131
2878
  case "limit:wait":
2132
2879
  return `${at}\u23F8 limit ${event.code}: waiting ${Math.round(event.waitMs / 1e3)}s`;
2133
2880
  case "limit:pause":
@@ -2150,12 +2897,12 @@ var CHECKPOINT_AT = /* @__PURE__ */ new Set([
2150
2897
  "dag:end",
2151
2898
  "job:end"
2152
2899
  ]);
2153
- function ensureDir2(path) {
2900
+ function ensureDir3(path) {
2154
2901
  const dir = dirname(path);
2155
2902
  if (dir && dir !== ".") mkdirSync(dir, { recursive: true });
2156
2903
  }
2157
2904
  function makeRecorder(path) {
2158
- ensureDir2(path);
2905
+ ensureDir3(path);
2159
2906
  writeFileSync(path, "");
2160
2907
  return (event) => {
2161
2908
  if (NOISE2.has(event.kind)) return;
@@ -2167,14 +2914,14 @@ function makeRecorder(path) {
2167
2914
  };
2168
2915
  }
2169
2916
  function makeCheckpointer(path, state) {
2170
- ensureDir2(path);
2917
+ ensureDir3(path);
2171
2918
  return (event) => {
2172
2919
  if (!CHECKPOINT_AT.has(event.kind)) return;
2173
2920
  flushCheckpoint(path, state);
2174
2921
  };
2175
2922
  }
2176
2923
  function flushCheckpoint(path, state) {
2177
- ensureDir2(path);
2924
+ ensureDir3(path);
2178
2925
  try {
2179
2926
  writeFileSync(path, JSON.stringify({ ts: Date.now(), state }, null, 2));
2180
2927
  } catch {
@@ -2344,6 +3091,6 @@ function exitCodeFor(outcome) {
2344
3091
  }
2345
3092
  }
2346
3093
 
2347
- export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, Stats, addWorktree, agentCheck, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, childContext, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, fnJob, forgeChecks, formatEvent, fromFile, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, not, predicate, promptPath, push, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, run, runEventsPath, runsHome, setMeta, stageAll, toCondition };
2348
- //# sourceMappingURL=chunk-6BDWTFOS.js.map
2349
- //# sourceMappingURL=chunk-6BDWTFOS.js.map
3094
+ export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, ProgressTracker, Stats, addWorktree, agentCheck, agentContract, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, childContext, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, feedbackBlock, fnJob, forgeChecks, formatEvent, fromFile, gateJob, graphPositionBlock, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, isRequiredFeedbackSeverity, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, normalizeFeedbackSeverity, not, predicate, promptPath, push, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveNoProgress, resolveSystem, retrieveLedger, reviewContext, reviewPanel, revisionFromOutcome, revisionRequest, run, runEventsPath, runSemanticRecordsPath, runsHome, semanticRecordsFromEvent, setMeta, stageAll, toCondition, workspaceFingerprint };
3095
+ //# sourceMappingURL=chunk-3PMVII43.js.map
3096
+ //# sourceMappingURL=chunk-3PMVII43.js.map