@shapeshift-labs/frontier-swarm-codex 0.5.32 → 0.5.33

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/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { spawn } from 'node:child_process';
10
10
  import fs from 'node:fs/promises';
11
11
  import os from 'node:os';
12
12
  import path from 'node:path';
13
- import { FRONTIER_SWARM_DEFAULT_MODEL, FRONTIER_SWARM_DEFAULT_REASONING_EFFORT, checkSwarmOwnership, completeSwarmJob, createSwarmCoordinatorDashboard, createSwarmEvidenceIndex, createSwarmMergeAdmission, FRONTIER_SWARM_MERGE_BUNDLE_KIND, FRONTIER_SWARM_MERGE_BUNDLE_VERSION, createSwarmMergeBundle, createSwarmMergeIndex, createSwarmQueueOverlay, matchesGlob, createSwarmEventStream, createSwarmLeases, createSwarmPlan, createSwarmProof, createSwarmRun, createSwarmSchedule, recordSwarmEvent, routeSwarmEventToMailboxes } from '@shapeshift-labs/frontier-swarm';
13
+ import { FRONTIER_SWARM_DEFAULT_MODEL, FRONTIER_SWARM_DEFAULT_REASONING_EFFORT, checkSwarmOwnership, completeSwarmJob, createSwarmCoordinatorDashboard, createSwarmAdaptiveLoadPlan, createSwarmEvidenceIndex, createSwarmMergeAdmission, FRONTIER_SWARM_MERGE_BUNDLE_KIND, FRONTIER_SWARM_MERGE_BUNDLE_VERSION, createSwarmMergeBundle, createSwarmMergeIndex, createSwarmQueueOverlay, matchesGlob, createSwarmEventStream, createSwarmLeases, createSwarmPlan, createSwarmProof, createSwarmRun, createSwarmSchedule, createSwarmScheduleInputFromAdaptiveLoadPlan, recordSwarmEvent, routeSwarmEventToMailboxes } from '@shapeshift-labs/frontier-swarm';
14
14
  export const FRONTIER_SWARM_CODEX_DEFAULT_MODEL = FRONTIER_SWARM_DEFAULT_MODEL;
15
15
  export const FRONTIER_SWARM_CODEX_DEFAULT_REASONING_EFFORT = FRONTIER_SWARM_DEFAULT_REASONING_EFFORT;
16
16
  export const FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_KIND = 'frontier.swarm-codex.workspace-manifest';
@@ -29,6 +29,10 @@ export const FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_KIND = 'frontier.swarm-codex.s
29
29
  export const FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_VERSION = 1;
30
30
  export const FRONTIER_SWARM_CODEX_JOB_EVIDENCE_KIND = 'frontier.swarm-codex.job-evidence';
31
31
  export const FRONTIER_SWARM_CODEX_JOB_EVIDENCE_VERSION = 1;
32
+ export const FRONTIER_SWARM_CODEX_PATCH_INTENT_KIND = 'frontier.swarm-codex.patch-intent';
33
+ export const FRONTIER_SWARM_CODEX_PATCH_INTENT_VERSION = 1;
34
+ export const FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_KIND = 'frontier.swarm-codex.compact-dashboard';
35
+ export const FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_VERSION = 1;
32
36
  const DEFAULT_WORKSPACE_INCLUDES = ['AGENTS.md', 'package.json', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'config'];
33
37
  const DEFAULT_WORKSPACE_EXCLUDES = [
34
38
  '.git',
@@ -135,7 +139,12 @@ export async function runCodexSwarm(plan, options) {
135
139
  run = recordSwarmEvent(run, startedEvent);
136
140
  await appendFileSwarmEvent(eventStream, startedEvent);
137
141
  const runOptions = { ...options, eventStream, pidManifestPath };
138
- const results = await runScheduledJobPool(plan, Math.max(1, options.maxConcurrency ?? 1), (job, lease) => runCodexJob(job, runOptions, outDir, lease));
142
+ const results = await runScheduledJobPool(plan, {
143
+ concurrency: Math.max(1, options.maxConcurrency ?? 1),
144
+ adaptive: options.adaptiveConcurrency,
145
+ outDir,
146
+ eventStream
147
+ }, (job, lease) => runCodexJob(job, runOptions, outDir, lease));
139
148
  for (const result of results) {
140
149
  const job = plan.jobs.find((entry) => entry.id === result.jobId);
141
150
  if (job) {
@@ -231,8 +240,12 @@ export async function runCodexJob(job, options, outDir, lease) {
231
240
  paths,
232
241
  resourceAllocation,
233
242
  env: resourceAllocation.env,
234
- timeoutMs: job.compute.timeoutMs ?? options.jobTimeoutMs ?? 7200000
243
+ timeoutMs: job.compute.timeoutMs ?? options.jobTimeoutMs ?? 7200000,
244
+ compactLogs: normalizeCompactLogOptions(options.compactLogs)
235
245
  });
246
+ const logSummary = execution.logSummary ?? createEmptyCodexLogSummary(paths);
247
+ if (!execution.logSummary)
248
+ await fs.writeFile(paths.logSummaryPath, JSON.stringify(logSummary, null, 2) + '\n');
236
249
  const collected = execution.changedPaths
237
250
  ? filterWorkspaceChangedPaths(execution.changedPaths, workspacePlan)
238
251
  : options.collectGitStatus === false
@@ -270,6 +283,8 @@ export async function runCodexJob(job, options, outDir, lease) {
270
283
  paths.mergeBundlePath,
271
284
  ...(patchPath ? [patchPath] : []),
272
285
  ...(semanticImport ? [semanticImport.path] : []),
286
+ paths.patchIntentPath,
287
+ paths.logSummaryPath,
273
288
  ...handoffArtifacts.map((artifact) => artifact.path)
274
289
  ]);
275
290
  const result = {
@@ -292,6 +307,7 @@ export async function runCodexJob(job, options, outDir, lease) {
292
307
  metadata: {
293
308
  ...(lease ? { leaseId: lease.id, leaseToken: lease.token, fencingToken: lease.fencingToken } : {}),
294
309
  resourceAllocation,
310
+ logSummary,
295
311
  ...(semanticImport ? { semanticImport: semanticImport.sidecar.summary } : {}),
296
312
  codexHandoffArtifacts: handoffArtifacts
297
313
  }
@@ -306,6 +322,8 @@ export async function runCodexJob(job, options, outDir, lease) {
306
322
  evidenceSummaryPath,
307
323
  paths.resourceAllocationPath,
308
324
  paths.workspaceProofPath,
325
+ paths.patchIntentPath,
326
+ paths.logSummaryPath,
309
327
  ...(semanticImport ? [semanticImport.path] : []),
310
328
  ...handoffArtifacts.map((artifact) => artifact.path)
311
329
  ]),
@@ -314,6 +332,16 @@ export async function runCodexJob(job, options, outDir, lease) {
314
332
  ...(semanticImport ? { metadata: { semanticImport: semanticImport.sidecar.summary } } : {})
315
333
  });
316
334
  await fs.writeFile(paths.mergeBundlePath, JSON.stringify(mergeBundle, null, 2) + '\n');
335
+ await writeCodexPatchIntent({
336
+ file: paths.patchIntentPath,
337
+ job,
338
+ result,
339
+ mergeBundle,
340
+ patchPath,
341
+ semanticImport: semanticImport?.sidecar,
342
+ semanticImportExpected: options.semanticImportExpected ?? semanticImportEnabled(options.semanticImport),
343
+ evidencePaths
344
+ });
317
345
  await writeCodexJobEvidenceSummary({
318
346
  file: evidenceSummaryPath,
319
347
  job,
@@ -321,6 +349,8 @@ export async function runCodexJob(job, options, outDir, lease) {
321
349
  mergeBundle,
322
350
  mergeBundlePath: paths.mergeBundlePath,
323
351
  patchPath,
352
+ patchIntentPath: paths.patchIntentPath,
353
+ logSummary,
324
354
  semanticImportPath: semanticImport?.path,
325
355
  semanticImport: semanticImport?.sidecar,
326
356
  handoffArtifacts
@@ -346,6 +376,7 @@ async function writeCodexJobEvidenceSummary(input) {
346
376
  ownershipViolations: [...input.mergeBundle.ownershipViolations],
347
377
  ...(input.patchPath ? { patchPath: input.patchPath } : {}),
348
378
  mergeBundlePath: input.mergeBundlePath,
379
+ ...(input.patchIntentPath ? { patchIntentPath: input.patchIntentPath } : {}),
349
380
  ...(input.semanticImportPath ? { semanticImportPath: input.semanticImportPath } : {}),
350
381
  evidencePaths: uniqueStrings(input.mergeBundle.evidencePaths),
351
382
  handoffArtifacts: input.handoffArtifacts.map((artifact) => ({ ...artifact })),
@@ -368,11 +399,56 @@ async function writeCodexJobEvidenceSummary(input) {
368
399
  metadata: {
369
400
  autoMergeable: input.mergeBundle.autoMergeable,
370
401
  staleAgainstHead: input.mergeBundle.staleAgainstHead,
402
+ ...(input.logSummary ? { logSummary: input.logSummary } : {}),
371
403
  reasons: input.mergeBundle.reasons
372
404
  }
373
405
  };
374
406
  await fs.writeFile(input.file, JSON.stringify(evidence, null, 2) + '\n');
375
407
  }
408
+ async function writeCodexPatchIntent(input) {
409
+ const patchHunks = input.patchPath ? await readPatchHunks(input.patchPath) : [];
410
+ const semanticImportQuality = summarizeCodexSemanticImportQuality(input.semanticImport?.summary, input.semanticImportExpected);
411
+ const warnings = uniqueStrings([
412
+ ...semanticImportQuality.warnings,
413
+ ...(input.mergeBundle.staleAgainstHead ? ['stale against coordinator head'] : []),
414
+ ...(input.mergeBundle.ownershipViolations.length ? ['ownership violations present'] : []),
415
+ ...(input.mergeBundle.commandsFailed.length ? ['verification commands failed'] : []),
416
+ ...(input.mergeBundle.disposition === 'discovery-only' ? ['discovery-only output'] : [])
417
+ ]);
418
+ const intent = {
419
+ kind: FRONTIER_SWARM_CODEX_PATCH_INTENT_KIND,
420
+ version: FRONTIER_SWARM_CODEX_PATCH_INTENT_VERSION,
421
+ generatedAt: Date.now(),
422
+ jobId: input.job.id,
423
+ taskId: input.job.taskId,
424
+ lane: input.job.lane,
425
+ changedPaths: [...input.mergeBundle.changedPaths],
426
+ changedRegions: [...input.mergeBundle.changedRegions],
427
+ intent: input.mergeBundle.changedPaths.length
428
+ ? `Patch ${input.mergeBundle.changedPaths.slice(0, 5).join(', ')}`
429
+ : 'No source patch produced',
430
+ why: input.result.lastMessage ? firstNonEmptyLine(input.result.lastMessage) ?? input.job.task.objective : input.job.task.objective,
431
+ riskLevel: input.mergeBundle.riskLevel,
432
+ mergeReadiness: input.mergeBundle.mergeReadiness,
433
+ disposition: input.mergeBundle.disposition,
434
+ safeToPortManually: input.mergeBundle.commandsFailed.length === 0
435
+ && input.mergeBundle.ownershipViolations.length === 0
436
+ && !input.mergeBundle.staleAgainstHead
437
+ && input.mergeBundle.disposition !== 'rejected'
438
+ && input.mergeBundle.disposition !== 'blocked',
439
+ verification: input.mergeBundle.commandsPassed.concat(input.mergeBundle.commandsFailed).map((command) => ({
440
+ name: command.name,
441
+ command: [...command.command],
442
+ ...(command.status !== undefined ? { status: command.status } : {}),
443
+ required: command.required
444
+ })),
445
+ evidencePaths: uniqueStrings(input.evidencePaths),
446
+ semanticImportQuality,
447
+ patchHunks,
448
+ warnings
449
+ };
450
+ await fs.writeFile(input.file, JSON.stringify(intent, null, 2) + '\n');
451
+ }
376
452
  async function readPatchHunks(file) {
377
453
  const text = await fs.readFile(file, 'utf8').catch(() => '');
378
454
  if (!text.trim())
@@ -423,7 +499,7 @@ async function createCodexSemanticImportSidecar(input) {
423
499
  const options = normalizeSemanticImportOptions(input.options);
424
500
  if (!options)
425
501
  return undefined;
426
- const selection = selectSemanticImportPaths(input.changedPaths, options);
502
+ const selection = selectSemanticImportPaths(semanticImportCandidatePaths(input.job, input.changedPaths), options);
427
503
  const selected = selection.selected;
428
504
  const records = [];
429
505
  const importPath = path.join(input.evidenceDir, 'semantic-imports.json');
@@ -756,6 +832,19 @@ export function renderCodexPrompt(job, input) {
756
832
  export async function spawnCodexExecutor(input) {
757
833
  await fs.writeFile(input.paths.eventsPath, '');
758
834
  await fs.writeFile(input.paths.stderrPath, '');
835
+ const logOptions = normalizeCompactLogOptions(input.compactLogs);
836
+ const eventLimit = logOptions.enabled === false ? Number.POSITIVE_INFINITY : logOptions.maxEventBytes ?? 1_000_000;
837
+ const stderrLimit = logOptions.enabled === false ? Number.POSITIVE_INFINITY : logOptions.maxStderrBytes ?? 256_000;
838
+ const logSummary = {
839
+ eventsPath: input.paths.eventsPath,
840
+ stderrPath: input.paths.stderrPath,
841
+ eventBytes: 0,
842
+ stderrBytes: 0,
843
+ eventBytesWritten: 0,
844
+ stderrBytesWritten: 0,
845
+ eventBytesTruncated: 0,
846
+ stderrBytesTruncated: 0
847
+ };
759
848
  return new Promise((resolve) => {
760
849
  const child = spawn(input.codexPath, input.args, {
761
850
  cwd: input.cwd,
@@ -772,23 +861,52 @@ export async function spawnCodexExecutor(input) {
772
861
  }).catch(() => { });
773
862
  }
774
863
  const timer = setTimeout(() => child.kill('SIGTERM'), input.timeoutMs);
775
- child.stdout.on('data', (chunk) => fs.appendFile(input.paths.eventsPath, chunk).catch(() => { }));
776
- child.stderr.on('data', (chunk) => fs.appendFile(input.paths.stderrPath, chunk).catch(() => { }));
864
+ child.stdout.on('data', (chunk) => appendLimitedLogChunk(input.paths.eventsPath, chunk, eventLimit, logSummary, 'event').catch(() => { }));
865
+ child.stderr.on('data', (chunk) => appendLimitedLogChunk(input.paths.stderrPath, chunk, stderrLimit, logSummary, 'stderr').catch(() => { }));
777
866
  child.stdin.end(input.prompt);
778
867
  child.on('close', async (code, signal) => {
779
868
  clearTimeout(timer);
869
+ await fs.writeFile(input.paths.logSummaryPath, JSON.stringify(logSummary, null, 2) + '\n').catch(() => { });
780
870
  resolve({
781
871
  exitCode: code ?? 1,
782
872
  ...(signal ? { signal } : {}),
783
- lastMessage: await readOptionalText(input.paths.lastMessagePath)
873
+ lastMessage: await readOptionalText(input.paths.lastMessagePath),
874
+ logSummary
784
875
  });
785
876
  });
786
877
  child.on('error', (error) => {
787
878
  clearTimeout(timer);
788
- resolve({ exitCode: 1, error });
879
+ fs.writeFile(input.paths.logSummaryPath, JSON.stringify(logSummary, null, 2) + '\n').catch(() => { });
880
+ resolve({ exitCode: 1, logSummary, error });
789
881
  });
790
882
  });
791
883
  }
884
+ async function appendLimitedLogChunk(file, chunk, limit, summary, kind) {
885
+ const bytes = chunk.byteLength;
886
+ if (kind === 'event')
887
+ summary.eventBytes += bytes;
888
+ else
889
+ summary.stderrBytes += bytes;
890
+ const written = kind === 'event' ? summary.eventBytesWritten : summary.stderrBytesWritten;
891
+ const available = Math.max(0, limit - written);
892
+ if (available <= 0) {
893
+ if (kind === 'event')
894
+ summary.eventBytesTruncated += bytes;
895
+ else
896
+ summary.stderrBytesTruncated += bytes;
897
+ return;
898
+ }
899
+ const slice = bytes > available ? chunk.subarray(0, available) : chunk;
900
+ await fs.appendFile(file, slice);
901
+ if (kind === 'event') {
902
+ summary.eventBytesWritten += slice.byteLength;
903
+ summary.eventBytesTruncated += bytes - slice.byteLength;
904
+ }
905
+ else {
906
+ summary.stderrBytesWritten += slice.byteLength;
907
+ summary.stderrBytesTruncated += bytes - slice.byteLength;
908
+ }
909
+ }
792
910
  async function createJobPaths(outDir, job, options) {
793
911
  const jobDir = path.join(outDir, job.id);
794
912
  const paths = {
@@ -802,6 +920,8 @@ async function createJobPaths(outDir, job, options) {
802
920
  workspaceProofPath: path.join(jobDir, 'evidence', 'workspace-proof.json'),
803
921
  patchPath: path.join(jobDir, 'evidence', 'changes.patch'),
804
922
  mergeBundlePath: path.join(jobDir, 'evidence', 'merge.json'),
923
+ patchIntentPath: path.join(jobDir, 'evidence', 'patch-intent.json'),
924
+ logSummaryPath: path.join(jobDir, 'evidence', 'log-summary.json'),
805
925
  pidManifestPath: path.resolve(options.cwd ?? process.cwd(), options.pidManifestPath ?? path.join(outDir, 'pids.json'))
806
926
  };
807
927
  await fs.mkdir(paths.evidenceDir, { recursive: true });
@@ -1106,7 +1226,10 @@ export async function collectCodexSwarmRun(input) {
1106
1226
  for (const { mergePath, bundle } of mergeRecords) {
1107
1227
  const patchPath = resolveBundlePatchPath(bundle, mergePath);
1108
1228
  const patchExists = !!patchPath && await pathExists(patchPath);
1109
- const staleAgainstHead = input.checkStale === false ? false : await bundlePatchIsStale(bundle, mergePath, cwd);
1229
+ const staleness = input.checkStale === false
1230
+ ? { stale: false, patchStatus: patchExists ? 'unknown' : 'missing', reasons: ['stale check disabled'] }
1231
+ : await bundlePatchStaleness(bundle, mergePath, cwd);
1232
+ const staleAgainstHead = staleness.stale;
1110
1233
  const bucket = classifyCodexCollectBucket(bundle, staleAgainstHead);
1111
1234
  const branchName = input.branchPrefix ? `${input.branchPrefix}/${slug(bundle.jobId)}` : bundle.branchName;
1112
1235
  const outputDir = path.join(outDir, bucket, slug(bundle.jobId));
@@ -1120,7 +1243,7 @@ export async function collectCodexSwarmRun(input) {
1120
1243
  evidencePaths: uniqueStrings([...bundle.evidencePaths, collectedEvidencePath])
1121
1244
  };
1122
1245
  collectedBundles.push(nextBundle);
1123
- patchStatuses[nextBundle.jobId] = staleAgainstHead ? 'stale' : patchExists ? input.checkStale === false ? 'unknown' : 'applies' : 'missing';
1246
+ patchStatuses[nextBundle.jobId] = staleness.patchStatus;
1124
1247
  await fs.mkdir(outputDir, { recursive: true });
1125
1248
  await fs.writeFile(path.join(outputDir, 'merge.json'), JSON.stringify(nextBundle, null, 2) + '\n');
1126
1249
  if (patchPath && await pathExists(patchPath))
@@ -1131,7 +1254,8 @@ export async function collectCodexSwarmRun(input) {
1131
1254
  bucket,
1132
1255
  mergePath,
1133
1256
  patchPath,
1134
- patchStatus: patchStatuses[nextBundle.jobId]
1257
+ patchStatus: patchStatuses[nextBundle.jobId],
1258
+ staleReasons: staleness.reasons
1135
1259
  });
1136
1260
  evidenceEntries.push(...createCollectedEvidenceEntries(nextBundle, collectedEvidencePath, bucket));
1137
1261
  buckets[bucket].push({ bucket, jobId: bundle.jobId, mergePath, outputDir, bundle: nextBundle });
@@ -1166,6 +1290,12 @@ export async function collectCodexSwarmRun(input) {
1166
1290
  generatedAt,
1167
1291
  metadata: { runDir, outDir }
1168
1292
  });
1293
+ const compactDashboard = createCodexCompactDashboard({
1294
+ runDir,
1295
+ dashboard,
1296
+ semanticImportExpected: input.semanticImportExpected ?? false,
1297
+ generatedAt
1298
+ });
1169
1299
  const summary = {
1170
1300
  total: mergeRecords.length,
1171
1301
  'ready-to-apply': buckets['ready-to-apply'].length,
@@ -1186,6 +1316,7 @@ export async function collectCodexSwarmRun(input) {
1186
1316
  evidenceIndex,
1187
1317
  admission,
1188
1318
  dashboard,
1319
+ compactDashboard,
1189
1320
  summary
1190
1321
  };
1191
1322
  await fs.mkdir(outDir, { recursive: true });
@@ -1195,6 +1326,7 @@ export async function collectCodexSwarmRun(input) {
1195
1326
  await fs.writeFile(path.join(outDir, 'evidence-index.json'), JSON.stringify(evidenceIndex, null, 2) + '\n');
1196
1327
  await fs.writeFile(path.join(outDir, 'merge-admission.json'), JSON.stringify(admission, null, 2) + '\n');
1197
1328
  await fs.writeFile(path.join(outDir, 'coordinator-query.json'), JSON.stringify(dashboard, null, 2) + '\n');
1329
+ await fs.writeFile(path.join(outDir, 'compact-dashboard.json'), JSON.stringify(compactDashboard, null, 2) + '\n');
1198
1330
  return result;
1199
1331
  }
1200
1332
  async function readCodexPidProcesses(file) {
@@ -1256,6 +1388,7 @@ async function copyOrWriteCollectedEvidenceSummary(input) {
1256
1388
  metadata: {
1257
1389
  bucket: input.bucket,
1258
1390
  patchStatus: input.patchStatus,
1391
+ staleReasons: input.staleReasons ?? [],
1259
1392
  autoMergeable: input.bundle.autoMergeable,
1260
1393
  staleAgainstHead: input.bundle.staleAgainstHead,
1261
1394
  reasons: input.bundle.reasons
@@ -1303,6 +1436,58 @@ function createCollectedEvidenceEntries(bundle, collectedEvidencePath, bucket) {
1303
1436
  }
1304
1437
  return entries;
1305
1438
  }
1439
+ function createCodexCompactDashboard(input) {
1440
+ const qualities = new Map(input.dashboard.jobs.map((job) => [
1441
+ job.jobId,
1442
+ summarizeCodexSemanticImportQuality(job.semanticImport, input.semanticImportExpected)
1443
+ ]));
1444
+ const semanticQualities = Array.from(qualities.values());
1445
+ const usefulPatchJobs = input.dashboard.jobs.filter((job) => ((job.disposition === 'auto-mergeable' || job.disposition === 'needs-port')
1446
+ && job.changedPaths.length > 0
1447
+ && job.tests.requiredFailed === 0));
1448
+ const topJobs = [...input.dashboard.jobs]
1449
+ .filter((job) => job.changedPaths.length > 0 || job.evidencePaths.length > 0)
1450
+ .sort((left, right) => right.mergeScore - left.mergeScore || left.jobId.localeCompare(right.jobId))
1451
+ .slice(0, 20)
1452
+ .map((job) => ({
1453
+ jobId: job.jobId,
1454
+ ...(job.lane ? { lane: job.lane } : {}),
1455
+ disposition: job.disposition,
1456
+ mergeScore: job.mergeScore,
1457
+ changedPaths: job.changedPaths.slice(0, 12),
1458
+ semanticImportQuality: qualities.get(job.jobId),
1459
+ staleAgainstHead: job.staleAgainstHead,
1460
+ ...(job.duplicateGroupId ? { duplicateGroupId: job.duplicateGroupId } : {}),
1461
+ evidencePaths: job.evidencePaths.slice(0, 12)
1462
+ }));
1463
+ return {
1464
+ kind: FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_KIND,
1465
+ version: FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_VERSION,
1466
+ generatedAt: input.generatedAt,
1467
+ runDir: input.runDir,
1468
+ total: input.dashboard.summary.jobCount,
1469
+ activeJobs: input.dashboard.jobs.filter((job) => job.liveness === 'running').length,
1470
+ usefulPatchCount: usefulPatchJobs.length,
1471
+ stalePatchCount: input.dashboard.summary.staleAgainstHeadCount,
1472
+ duplicateDiscoveryCount: input.dashboard.duplicateGroups.length,
1473
+ semanticImport: {
1474
+ expected: input.semanticImportExpected,
1475
+ presentCount: semanticQualities.filter((entry) => entry.present).length,
1476
+ emptyCount: semanticQualities.filter((entry) => entry.empty).length,
1477
+ weakCount: semanticQualities.filter((entry) => entry.present && entry.warnings.length > 0).length,
1478
+ symbolCount: semanticQualities.reduce((sum, entry) => sum + entry.symbols, 0),
1479
+ ownershipRegionCount: semanticQualities.reduce((sum, entry) => sum + entry.ownershipRegions, 0),
1480
+ patchHintCount: semanticQualities.reduce((sum, entry) => sum + entry.patchHints, 0)
1481
+ },
1482
+ evidence: {
1483
+ readyToApply: input.dashboard.summary.readyToApplyCount,
1484
+ needsHumanPort: input.dashboard.summary.needsHumanPortCount,
1485
+ failedEvidence: input.dashboard.summary.failedEvidenceCount,
1486
+ averageMergeScore: input.dashboard.summary.averageMergeScore
1487
+ },
1488
+ topJobs
1489
+ };
1490
+ }
1306
1491
  export async function applyCodexSwarmCollection(input) {
1307
1492
  const generatedAt = Date.now();
1308
1493
  const cwd = path.resolve(input.cwd ?? process.cwd());
@@ -1623,12 +1808,132 @@ function summarizePatchScoreSemanticEvidence(bundle) {
1623
1808
  reasons: uniqueStrings(reasons)
1624
1809
  };
1625
1810
  }
1811
+ function summarizeCodexSemanticImportQuality(summary, expected = false) {
1812
+ const selected = nonNegativeNumber(summary?.selected);
1813
+ const eligible = nonNegativeNumber(summary?.eligible);
1814
+ const imported = nonNegativeNumber(summary?.imported);
1815
+ const symbols = nonNegativeNumber(summary?.semanticIndex?.symbols);
1816
+ const ownershipRegions = nonNegativeNumber(summary?.semanticSidecars?.ownershipRegions);
1817
+ const patchHints = nonNegativeNumber(summary?.semanticSidecars?.patchHints);
1818
+ const sourceMapMappings = nonNegativeNumber(summary?.sourceMapMappingCount);
1819
+ const present = !!summary;
1820
+ const empty = present && (nonNegativeNumber(summary?.total) === 0 || selected === 0 && eligible === 0 && imported === 0 && symbols === 0);
1821
+ const warnings = [];
1822
+ if (expected && !present)
1823
+ warnings.push('semantic import expected but missing');
1824
+ if (expected && empty)
1825
+ warnings.push('semantic import expected but empty');
1826
+ if (present && imported === 0)
1827
+ warnings.push('semantic import imported no files');
1828
+ if (present && selected > 0 && symbols === 0)
1829
+ warnings.push('semantic import has no symbols');
1830
+ if (present && selected > 0 && ownershipRegions === 0)
1831
+ warnings.push('semantic import has no ownership regions');
1832
+ if (present && selected > 0 && sourceMapMappings === 0)
1833
+ warnings.push('semantic import has no source-map mappings');
1834
+ return {
1835
+ expected,
1836
+ present,
1837
+ empty,
1838
+ selected,
1839
+ eligible,
1840
+ imported,
1841
+ symbols,
1842
+ ownershipRegions,
1843
+ patchHints,
1844
+ sourceMapMappings,
1845
+ warnings: uniqueStrings(warnings)
1846
+ };
1847
+ }
1626
1848
  function semanticImportSummaryFromBundle(bundle) {
1627
1849
  if (bundle.semanticImport)
1628
1850
  return bundle.semanticImport;
1629
1851
  const metadata = bundle.metadata;
1630
1852
  return metadata?.semanticImport;
1631
1853
  }
1854
+ function semanticImportEnabled(input) {
1855
+ if (input === true)
1856
+ return true;
1857
+ if (!input)
1858
+ return false;
1859
+ return input.enabled !== false;
1860
+ }
1861
+ function normalizeCompactLogOptions(input) {
1862
+ if (input === false)
1863
+ return { enabled: false };
1864
+ if (input === true || input === undefined)
1865
+ return { enabled: true, maxEventBytes: 1_000_000, maxStderrBytes: 256_000 };
1866
+ return {
1867
+ enabled: input.enabled ?? true,
1868
+ maxEventBytes: positiveInteger(input.maxEventBytes, 1_000_000),
1869
+ maxStderrBytes: positiveInteger(input.maxStderrBytes, 256_000)
1870
+ };
1871
+ }
1872
+ function normalizeAdaptiveConcurrencyOptions(input, maxConcurrency) {
1873
+ if (input === false || input === undefined) {
1874
+ return { enabled: false, mode: 'balanced', minConcurrency: 1, maxConcurrency, writePlan: true };
1875
+ }
1876
+ if (input === true) {
1877
+ return { enabled: true, mode: 'balanced', minConcurrency: 1, maxConcurrency, writePlan: true };
1878
+ }
1879
+ return {
1880
+ enabled: input.enabled ?? true,
1881
+ mode: input.mode ?? 'balanced',
1882
+ minConcurrency: Math.max(1, Math.min(maxConcurrency, Math.floor(input.minConcurrency ?? 1))),
1883
+ maxConcurrency: Math.max(1, Math.min(maxConcurrency, Math.floor(input.maxConcurrency ?? maxConcurrency))),
1884
+ writePlan: input.writePlan ?? true
1885
+ };
1886
+ }
1887
+ function createCodexAdaptiveObservations(results) {
1888
+ const observations = [];
1889
+ for (const result of results) {
1890
+ const metadata = result.metadata && typeof result.metadata === 'object' ? result.metadata : {};
1891
+ const logSummary = metadata.logSummary;
1892
+ if (logSummary && (logSummary.eventBytesTruncated > 0 || logSummary.stderrBytesTruncated > 0 || logSummary.eventBytes > 1_000_000 || logSummary.stderrBytes > 256_000)) {
1893
+ observations.push({
1894
+ kind: 'log-noise',
1895
+ severity: logSummary.eventBytesTruncated > 0 || logSummary.stderrBytesTruncated > 0 ? 'warning' : 'info',
1896
+ jobId: result.jobId,
1897
+ value: logSummary.eventBytes + logSummary.stderrBytes,
1898
+ reason: 'worker output exceeded compact log threshold',
1899
+ metadata: logSummary
1900
+ });
1901
+ }
1902
+ if (result.mergeDisposition === 'stale-against-head') {
1903
+ observations.push({ kind: 'stale-patch', severity: 'warning', jobId: result.jobId, reason: 'worker result is stale against head' });
1904
+ }
1905
+ if (result.mergeDisposition === 'discovery-only' || result.mergeReadiness === 'discovery-only') {
1906
+ observations.push({ kind: 'discovery-only-output', severity: 'info', jobId: result.jobId, reason: 'worker produced discovery-only output' });
1907
+ }
1908
+ const semanticQuality = summarizeCodexSemanticImportQuality(result.semanticImport ?? metadata.semanticImport, false);
1909
+ if (semanticQuality.present && semanticQuality.empty) {
1910
+ observations.push({ kind: 'semantic-empty', severity: 'warning', jobId: result.jobId, reason: 'worker semantic sidecar is empty' });
1911
+ }
1912
+ else if (semanticQuality.present && semanticQuality.warnings.length > 0) {
1913
+ observations.push({ kind: 'semantic-weak', severity: 'info', jobId: result.jobId, reasons: semanticQuality.warnings });
1914
+ }
1915
+ }
1916
+ return observations;
1917
+ }
1918
+ function createEmptyCodexLogSummary(paths) {
1919
+ return {
1920
+ eventsPath: paths.eventsPath,
1921
+ stderrPath: paths.stderrPath,
1922
+ eventBytes: 0,
1923
+ stderrBytes: 0,
1924
+ eventBytesWritten: 0,
1925
+ stderrBytesWritten: 0,
1926
+ eventBytesTruncated: 0,
1927
+ stderrBytesTruncated: 0
1928
+ };
1929
+ }
1930
+ function positiveInteger(value, fallback) {
1931
+ const number = Number(value);
1932
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
1933
+ }
1934
+ function firstNonEmptyLine(text) {
1935
+ return text.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1936
+ }
1632
1937
  function numberRecord(value) {
1633
1938
  if (!value || typeof value !== 'object')
1634
1939
  return {};
@@ -1944,24 +2249,65 @@ async function runVerification(commands, cwd) {
1944
2249
  }
1945
2250
  return results;
1946
2251
  }
1947
- async function runScheduledJobPool(plan, concurrency, worker) {
2252
+ async function runScheduledJobPool(plan, input, worker) {
2253
+ const concurrency = Math.max(1, Math.floor(input.concurrency));
2254
+ const adaptiveOptions = normalizeAdaptiveConcurrencyOptions(input.adaptive, concurrency);
1948
2255
  const results = [];
1949
2256
  const active = new Map();
1950
2257
  const leases = [];
1951
2258
  const completed = new Set();
1952
2259
  const resultByJob = new Map();
2260
+ const adaptiveHistory = [];
2261
+ let currentAdaptiveLimits;
1953
2262
  while (resultByJob.size < plan.jobs.length) {
1954
2263
  const run = createSwarmRun({ plan, status: 'running', results });
1955
2264
  run.jobs = run.jobs.map((job) => active.has(job.id) ? { ...job, status: 'running' } : job);
1956
- const schedule = createSwarmSchedule({
2265
+ const adaptivePlan = adaptiveOptions.enabled ? createSwarmAdaptiveLoadPlan({
1957
2266
  plan,
1958
2267
  run,
1959
- maxReadyJobs: Math.max(0, concurrency - active.size)
2268
+ mode: adaptiveOptions.mode,
2269
+ maxLimits: { maxReadyJobs: adaptiveOptions.maxConcurrency },
2270
+ minLimits: { maxReadyJobs: adaptiveOptions.minConcurrency },
2271
+ currentLimits: currentAdaptiveLimits ?? { maxReadyJobs: adaptiveOptions.maxConcurrency },
2272
+ observations: createCodexAdaptiveObservations(results)
2273
+ }) : undefined;
2274
+ if (adaptivePlan) {
2275
+ currentAdaptiveLimits = adaptivePlan.effectiveLimits;
2276
+ adaptiveHistory.push(adaptivePlan);
2277
+ if (adaptiveOptions.writePlan !== false && input.outDir) {
2278
+ await writeJsonAtomic(path.join(input.outDir, 'adaptive-load.json'), {
2279
+ latest: adaptivePlan,
2280
+ history: adaptiveHistory.slice(-50)
2281
+ }).catch(() => { });
2282
+ }
2283
+ await appendFileSwarmEvent(input.eventStream, {
2284
+ type: 'swarm.adaptive-load',
2285
+ runId: run.id,
2286
+ data: {
2287
+ mode: adaptivePlan.mode,
2288
+ effectiveMaxReadyJobs: adaptivePlan.effectiveLimits.maxReadyJobs,
2289
+ bottleneckCount: adaptivePlan.summary.bottleneckCount,
2290
+ decisions: adaptivePlan.decisions.map((decision) => ({
2291
+ action: decision.action,
2292
+ target: decision.target,
2293
+ key: decision.key,
2294
+ previous: decision.previous,
2295
+ next: decision.next,
2296
+ reason: decision.reason
2297
+ }))
2298
+ }
2299
+ });
2300
+ }
2301
+ const effectiveConcurrency = Math.max(1, Math.min(concurrency, adaptivePlan?.effectiveLimits.maxReadyJobs ?? concurrency));
2302
+ const readyWindow = Math.max(0, effectiveConcurrency - active.size);
2303
+ const schedule = createSwarmSchedule({
2304
+ ...(adaptivePlan ? createSwarmScheduleInputFromAdaptiveLoadPlan(plan, adaptivePlan, { run }) : { plan, run }),
2305
+ maxReadyJobs: readyWindow
1960
2306
  });
1961
2307
  const nextLeases = createSwarmLeases({
1962
2308
  schedule,
1963
2309
  workerId: 'frontier-swarm-codex',
1964
- count: Math.max(0, concurrency - active.size),
2310
+ count: readyWindow,
1965
2311
  existingLeases: leases
1966
2312
  });
1967
2313
  for (const lease of nextLeases) {
@@ -2117,6 +2463,15 @@ function selectSemanticImportPaths(changedPaths, options) {
2117
2463
  maxFiles
2118
2464
  };
2119
2465
  }
2466
+ function semanticImportCandidatePaths(job, changedPaths) {
2467
+ const concreteRefs = job.task.sourceRefs.concat(job.task.targetRefs).filter((file) => {
2468
+ const normalized = normalizeWorkspacePath(file);
2469
+ return normalized
2470
+ && !normalized.includes('*')
2471
+ && path.extname(normalized).length > 0;
2472
+ });
2473
+ return uniqueWorkspacePaths([...changedPaths, ...concreteRefs]);
2474
+ }
2120
2475
  function inferSemanticImportLanguage(file, overrides) {
2121
2476
  const ext = path.extname(file).toLowerCase();
2122
2477
  return overrides?.[file] ?? overrides?.[ext] ?? {
@@ -2435,15 +2790,77 @@ async function findFilesByName(root, name) {
2435
2790
  await walk(root);
2436
2791
  return out;
2437
2792
  }
2438
- async function bundlePatchIsStale(bundle, mergePath, cwd) {
2793
+ async function bundlePatchStaleness(bundle, mergePath, cwd) {
2439
2794
  const patchPath = resolveBundlePatchPath(bundle, mergePath);
2440
2795
  if (!patchPath || !await pathExists(patchPath))
2441
- return false;
2796
+ return { stale: false, patchStatus: 'missing', reasons: ['missing patch'] };
2442
2797
  const patch = await fs.readFile(patchPath, 'utf8').catch(() => '');
2443
2798
  if (!patch.trim())
2444
- return false;
2799
+ return { stale: false, patchStatus: 'missing', reasons: ['empty patch'] };
2445
2800
  const result = await runProcess('git', ['apply', '--check', patchPath], { cwd, allowFailure: true });
2446
- return result.status !== 0;
2801
+ if (result.status === 0)
2802
+ return { stale: false, patchStatus: 'applies', reasons: ['patch applies to working tree'] };
2803
+ const cached = await runProcess('git', ['apply', '--check', '--cached', patchPath], { cwd, allowFailure: true });
2804
+ if (cached.status === 0) {
2805
+ return {
2806
+ stale: false,
2807
+ patchStatus: 'dirty-workspace-conflict',
2808
+ reasons: ['patch applies to index but not dirty working tree']
2809
+ };
2810
+ }
2811
+ const baseStatus = await patchBaseHashStatus(patch, cwd);
2812
+ if (baseStatus.known && baseStatus.mismatched === 0) {
2813
+ return {
2814
+ stale: false,
2815
+ patchStatus: 'needs-port',
2816
+ reasons: ['patch base hashes match HEAD but textual apply failed', ...baseStatus.reasons]
2817
+ };
2818
+ }
2819
+ return {
2820
+ stale: true,
2821
+ patchStatus: 'stale',
2822
+ reasons: uniqueStrings(['git apply --check failed', ...baseStatus.reasons, ...tail(result.stderr || result.stdout, 3)])
2823
+ };
2824
+ }
2825
+ async function patchBaseHashStatus(patch, cwd) {
2826
+ const entries = parsePatchBaseHashes(patch);
2827
+ if (entries.length === 0)
2828
+ return { known: false, mismatched: 0, reasons: ['no patch base hashes available'] };
2829
+ let mismatched = 0;
2830
+ const reasons = [];
2831
+ for (const entry of entries) {
2832
+ const head = await runProcess('git', ['rev-parse', `HEAD:${entry.path}`], { cwd, allowFailure: true });
2833
+ if (head.status !== 0) {
2834
+ mismatched += 1;
2835
+ reasons.push(`missing HEAD blob for ${entry.path}`);
2836
+ continue;
2837
+ }
2838
+ const headHash = head.stdout.trim();
2839
+ if (!headHash.startsWith(entry.oldHash)) {
2840
+ mismatched += 1;
2841
+ reasons.push(`base hash mismatch for ${entry.path}`);
2842
+ }
2843
+ }
2844
+ return { known: true, mismatched, reasons };
2845
+ }
2846
+ function parsePatchBaseHashes(patch) {
2847
+ const lines = patch.split(/\r?\n/);
2848
+ const entries = [];
2849
+ let currentPath;
2850
+ for (const line of lines) {
2851
+ if (line.startsWith('diff --git ')) {
2852
+ const parts = line.split(/\s+/);
2853
+ const right = parts[3] ?? parts[2];
2854
+ currentPath = right?.startsWith('b/') ? right.slice(2) : right;
2855
+ continue;
2856
+ }
2857
+ if (!currentPath || !line.startsWith('index '))
2858
+ continue;
2859
+ const match = /^index\s+([0-9a-f]+)\.\.([0-9a-f]+)/i.exec(line);
2860
+ if (match?.[1] && match[1] !== '0000000')
2861
+ entries.push({ path: currentPath, oldHash: match[1] });
2862
+ }
2863
+ return entries;
2447
2864
  }
2448
2865
  function resolveBundlePatchPath(bundle, mergePath) {
2449
2866
  if (!bundle.patchPath)