@shapeshift-labs/frontier-swarm-codex 0.5.41 → 0.5.42

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.
Files changed (140) hide show
  1. package/README.md +1 -1
  2. package/dist/apply.d.ts +3 -0
  3. package/dist/apply.d.ts.map +1 -0
  4. package/dist/apply.js +127 -0
  5. package/dist/apply.js.map +1 -0
  6. package/dist/cli.js +7 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/codex-events.d.ts +15 -0
  9. package/dist/codex-events.d.ts.map +1 -0
  10. package/dist/codex-events.js +108 -0
  11. package/dist/codex-events.js.map +1 -0
  12. package/dist/codex-evidence.d.ts +33 -0
  13. package/dist/codex-evidence.d.ts.map +1 -0
  14. package/dist/codex-evidence.js +143 -0
  15. package/dist/codex-evidence.js.map +1 -0
  16. package/dist/codex-executor.d.ts +5 -0
  17. package/dist/codex-executor.d.ts.map +1 -0
  18. package/dist/codex-executor.js +160 -0
  19. package/dist/codex-executor.js.map +1 -0
  20. package/dist/codex-prompt.d.ts +20 -0
  21. package/dist/codex-prompt.d.ts.map +1 -0
  22. package/dist/codex-prompt.js +238 -0
  23. package/dist/codex-prompt.js.map +1 -0
  24. package/dist/codex-run-scheduler.d.ts +9 -0
  25. package/dist/codex-run-scheduler.d.ts.map +1 -0
  26. package/dist/codex-run-scheduler.js +145 -0
  27. package/dist/codex-run-scheduler.js.map +1 -0
  28. package/dist/codex-run.d.ts +5 -0
  29. package/dist/codex-run.d.ts.map +1 -0
  30. package/dist/codex-run.js +278 -0
  31. package/dist/codex-run.js.map +1 -0
  32. package/dist/codex-workspace-changes.d.ts +28 -0
  33. package/dist/codex-workspace-changes.d.ts.map +1 -0
  34. package/dist/codex-workspace-changes.js +147 -0
  35. package/dist/codex-workspace-changes.js.map +1 -0
  36. package/dist/codex-workspace.d.ts +10 -0
  37. package/dist/codex-workspace.d.ts.map +1 -0
  38. package/dist/codex-workspace.js +210 -0
  39. package/dist/codex-workspace.js.map +1 -0
  40. package/dist/collect-bundles.d.ts +14 -0
  41. package/dist/collect-bundles.d.ts.map +1 -0
  42. package/dist/collect-bundles.js +173 -0
  43. package/dist/collect-bundles.js.map +1 -0
  44. package/dist/collect-evidence.d.ts +13 -0
  45. package/dist/collect-evidence.d.ts.map +1 -0
  46. package/dist/collect-evidence.js +131 -0
  47. package/dist/collect-evidence.js.map +1 -0
  48. package/dist/collect.d.ts +5 -0
  49. package/dist/collect.d.ts.map +1 -0
  50. package/dist/collect.js +189 -0
  51. package/dist/collect.js.map +1 -0
  52. package/dist/common.d.ts +37 -0
  53. package/dist/common.d.ts.map +1 -0
  54. package/dist/common.js +191 -0
  55. package/dist/common.js.map +1 -0
  56. package/dist/constants.d.ts +25 -0
  57. package/dist/constants.d.ts.map +1 -0
  58. package/dist/constants.js +26 -0
  59. package/dist/constants.js.map +1 -0
  60. package/dist/dashboard.d.ts +9 -0
  61. package/dist/dashboard.d.ts.map +1 -0
  62. package/dist/dashboard.js +80 -0
  63. package/dist/dashboard.js.map +1 -0
  64. package/dist/handoff-artifacts.d.ts +4 -0
  65. package/dist/handoff-artifacts.d.ts.map +1 -0
  66. package/dist/handoff-artifacts.js +67 -0
  67. package/dist/handoff-artifacts.js.map +1 -0
  68. package/dist/index.d.ts +14 -843
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +14 -3717
  71. package/dist/index.js.map +1 -1
  72. package/dist/patch-score-semantic.d.ts +4 -0
  73. package/dist/patch-score-semantic.d.ts.map +1 -0
  74. package/dist/patch-score-semantic.js +154 -0
  75. package/dist/patch-score-semantic.js.map +1 -0
  76. package/dist/score.d.ts +3 -0
  77. package/dist/score.d.ts.map +1 -0
  78. package/dist/score.js +219 -0
  79. package/dist/score.js.map +1 -0
  80. package/dist/semantic-import-layers.d.ts +9 -0
  81. package/dist/semantic-import-layers.d.ts.map +1 -0
  82. package/dist/semantic-import-layers.js +143 -0
  83. package/dist/semantic-import-layers.js.map +1 -0
  84. package/dist/semantic-import-paradigm.d.ts +6 -0
  85. package/dist/semantic-import-paradigm.d.ts.map +1 -0
  86. package/dist/semantic-import-paradigm.js +159 -0
  87. package/dist/semantic-import-paradigm.js.map +1 -0
  88. package/dist/semantic-import-proof.d.ts +7 -0
  89. package/dist/semantic-import-proof.d.ts.map +1 -0
  90. package/dist/semantic-import-proof.js +142 -0
  91. package/dist/semantic-import-proof.js.map +1 -0
  92. package/dist/semantic-import-quality.d.ts +6 -0
  93. package/dist/semantic-import-quality.d.ts.map +1 -0
  94. package/dist/semantic-import-quality.js +108 -0
  95. package/dist/semantic-import-quality.js.map +1 -0
  96. package/dist/semantic-import-select.d.ts +36 -0
  97. package/dist/semantic-import-select.d.ts.map +1 -0
  98. package/dist/semantic-import-select.js +132 -0
  99. package/dist/semantic-import-select.js.map +1 -0
  100. package/dist/semantic-import-sidecar.d.ts +7 -0
  101. package/dist/semantic-import-sidecar.d.ts.map +1 -0
  102. package/dist/semantic-import-sidecar.js +169 -0
  103. package/dist/semantic-import-sidecar.js.map +1 -0
  104. package/dist/semantic-import.d.ts +13 -0
  105. package/dist/semantic-import.d.ts.map +1 -0
  106. package/dist/semantic-import.js +122 -0
  107. package/dist/semantic-import.js.map +1 -0
  108. package/dist/trace-summary.d.ts +6 -0
  109. package/dist/trace-summary.d.ts.map +1 -0
  110. package/dist/trace-summary.js +61 -0
  111. package/dist/trace-summary.js.map +1 -0
  112. package/dist/types-collection.d.ts +166 -0
  113. package/dist/types-collection.d.ts.map +1 -0
  114. package/dist/types-collection.js +2 -0
  115. package/dist/types-collection.js.map +1 -0
  116. package/dist/types-evidence.d.ts +158 -0
  117. package/dist/types-evidence.d.ts.map +1 -0
  118. package/dist/types-evidence.js +2 -0
  119. package/dist/types-evidence.js.map +1 -0
  120. package/dist/types-run.d.ts +161 -0
  121. package/dist/types-run.d.ts.map +1 -0
  122. package/dist/types-run.js +2 -0
  123. package/dist/types-run.js.map +1 -0
  124. package/dist/types-semantic.d.ts +192 -0
  125. package/dist/types-semantic.d.ts.map +1 -0
  126. package/dist/types-semantic.js +2 -0
  127. package/dist/types-semantic.js.map +1 -0
  128. package/dist/types-workspace.d.ts +122 -0
  129. package/dist/types-workspace.d.ts.map +1 -0
  130. package/dist/types-workspace.js +2 -0
  131. package/dist/types-workspace.js.map +1 -0
  132. package/dist/types.d.ts +6 -0
  133. package/dist/types.d.ts.map +1 -0
  134. package/dist/types.js +2 -0
  135. package/dist/types.js.map +1 -0
  136. package/dist/workspace-link-repair.d.ts +3 -0
  137. package/dist/workspace-link-repair.d.ts.map +1 -0
  138. package/dist/workspace-link-repair.js +164 -0
  139. package/dist/workspace-link-repair.js.map +1 -0
  140. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,51 +1,16 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import { spawn } from 'node:child_process';
10
- import fs from 'node:fs/promises';
11
- import os from 'node:os';
12
- import path from 'node:path';
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
- export const FRONTIER_SWARM_CODEX_DEFAULT_MODEL = FRONTIER_SWARM_DEFAULT_MODEL;
15
- export const FRONTIER_SWARM_CODEX_DEFAULT_REASONING_EFFORT = FRONTIER_SWARM_DEFAULT_REASONING_EFFORT;
16
- export const FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_KIND = 'frontier.swarm-codex.workspace-manifest';
17
- export const FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_VERSION = 1;
18
- export const FRONTIER_SWARM_CODEX_WORKSPACE_PROOF_KIND = 'frontier.swarm-codex.workspace-proof';
19
- export const FRONTIER_SWARM_CODEX_WORKSPACE_PROOF_VERSION = 1;
20
- export const FRONTIER_SWARM_CODEX_PID_MANIFEST_KIND = 'frontier.swarm-codex.pid-manifest';
21
- export const FRONTIER_SWARM_CODEX_PID_MANIFEST_VERSION = 1;
22
- export const FRONTIER_SWARM_CODEX_COLLECTION_KIND = 'frontier.swarm-codex.collection';
23
- export const FRONTIER_SWARM_CODEX_COLLECTION_VERSION = 1;
24
- export const FRONTIER_SWARM_CODEX_APPLY_LEDGER_KIND = 'frontier.swarm-codex.apply-ledger';
25
- export const FRONTIER_SWARM_CODEX_APPLY_LEDGER_VERSION = 1;
26
- export const FRONTIER_SWARM_CODEX_PATCH_SCORE_KIND = 'frontier.swarm-codex.patch-score';
27
- export const FRONTIER_SWARM_CODEX_PATCH_SCORE_VERSION = 1;
28
- export const FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_KIND = 'frontier.swarm-codex.semantic-imports';
29
- export const FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_VERSION = 1;
30
- export const FRONTIER_SWARM_CODEX_JOB_EVIDENCE_KIND = 'frontier.swarm-codex.job-evidence';
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;
36
- export const FRONTIER_SWARM_CODEX_LINK_REPAIR_KIND = 'frontier.swarm-codex.link-repair';
37
- export const FRONTIER_SWARM_CODEX_LINK_REPAIR_VERSION = 1;
38
- const DEFAULT_WORKSPACE_INCLUDES = ['AGENTS.md', 'package.json', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'config'];
39
- const DEFAULT_WORKSPACE_EXCLUDES = [
40
- '.git',
41
- 'node_modules',
42
- 'dist',
43
- 'coverage',
44
- '.frontier-framework',
45
- 'agent-runs',
46
- 'target'
47
- ];
48
- const pidManifestWriteQueues = new Map();
1
+ import { createSwarmPlan } from '@shapeshift-labs/frontier-swarm';
2
+ import { FRONTIER_SWARM_CODEX_DEFAULT_MODEL, FRONTIER_SWARM_CODEX_DEFAULT_REASONING_EFFORT } from './constants.js';
3
+ import { arrayOfObjects, isObject, readStringArray } from './common.js';
4
+ export { FRONTIER_SWARM_CODEX_APPLY_LEDGER_KIND, FRONTIER_SWARM_CODEX_APPLY_LEDGER_VERSION, FRONTIER_SWARM_CODEX_COLLECTION_KIND, FRONTIER_SWARM_CODEX_COLLECTION_VERSION, FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_KIND, FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_VERSION, FRONTIER_SWARM_CODEX_DEFAULT_MODEL, FRONTIER_SWARM_CODEX_DEFAULT_REASONING_EFFORT, FRONTIER_SWARM_CODEX_JOB_EVIDENCE_KIND, FRONTIER_SWARM_CODEX_JOB_EVIDENCE_VERSION, FRONTIER_SWARM_CODEX_LINK_REPAIR_KIND, FRONTIER_SWARM_CODEX_LINK_REPAIR_VERSION, FRONTIER_SWARM_CODEX_PATCH_INTENT_KIND, FRONTIER_SWARM_CODEX_PATCH_INTENT_VERSION, FRONTIER_SWARM_CODEX_PATCH_SCORE_KIND, FRONTIER_SWARM_CODEX_PATCH_SCORE_VERSION, FRONTIER_SWARM_CODEX_PID_MANIFEST_KIND, FRONTIER_SWARM_CODEX_PID_MANIFEST_VERSION, FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_KIND, FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_VERSION, FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_KIND, FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_VERSION, FRONTIER_SWARM_CODEX_WORKSPACE_PROOF_KIND, FRONTIER_SWARM_CODEX_WORKSPACE_PROOF_VERSION } from './constants.js';
5
+ export { collectCodexSwarmRun } from './collect.js';
6
+ export { applyCodexSwarmCollection } from './apply.js';
7
+ export { scoreCodexSwarmPatches } from './score.js';
8
+ export { runCodexSwarm, runCodexJob } from './codex-run.js';
9
+ export { appendCodexPidManifest, appendFileSwarmEvent, initFileSwarmEventStream, readCodexPidManifest, stopCodexSwarmRun, writeSwarmCoordinatorSnapshot } from './codex-events.js';
10
+ export { buildCodexArgs, createCodexResourceAllocation, normalizeCodexApprovalPolicy, normalizeCodexModelFlag, renderCodexPrompt } from './codex-prompt.js';
11
+ export { createCodexWorkspacePlan, createSwarmWorkspaceManifest, createSwarmWorkspaceProof, prepareCodexWorkspace } from './codex-workspace.js';
12
+ export { spawnCodexExecutor } from './codex-executor.js';
13
+ export { discoverCodexHandoffArtifacts } from './handoff-artifacts.js';
49
14
  export function createCodexSwarmPlan(input) {
50
15
  return createSwarmPlan(coerceCodexSwarmManifestInput(input.manifest), coerceCodexSwarmTasksInput(input.tasks), input.plan ?? {});
51
16
  }
@@ -124,2569 +89,7 @@ export function coerceCodexSwarmTasksInput(value) {
124
89
  };
125
90
  }).filter((task) => task.id.length > 0);
126
91
  }
127
- export async function repairCodexWorkspacePackageLinks(input = {}) {
128
- const root = path.resolve(input.root ?? process.cwd());
129
- const scope = input.scope ?? '@shapeshift-labs';
130
- const write = input.write ?? false;
131
- const replace = input.replace ?? false;
132
- const packageRoots = (input.packageRoots?.length ? input.packageRoots : [path.join(root, 'packages'), path.dirname(root)])
133
- .map((entry) => path.resolve(root, entry));
134
- const excludes = new Set(input.excludePackages ?? []);
135
- const dependencies = input.packages?.length
136
- ? new Map(input.packages.map((name) => [name, undefined]))
137
- : await readWorkspaceScopedDependencies(root, scope);
138
- const localPackages = await discoverLocalWorkspacePackages(packageRoots, scope);
139
- const entries = [];
140
- for (const [packageName, dependencyRange] of Array.from(dependencies.entries()).sort(([a], [b]) => a.localeCompare(b))) {
141
- const linkPath = path.join(root, 'node_modules', ...packageName.split('/'));
142
- if (excludes.has(packageName)) {
143
- entries.push({ packageName, dependencyRange, linkPath, status: 'excluded', reason: 'package excluded from local repair' });
144
- continue;
145
- }
146
- const targetPath = localPackages.get(packageName);
147
- if (!targetPath) {
148
- entries.push({ packageName, dependencyRange, linkPath, status: 'missing-local-package', reason: 'no matching local package was found' });
149
- continue;
150
- }
151
- entries.push(await planOrRepairWorkspacePackageLink({
152
- packageName,
153
- dependencyRange,
154
- linkPath,
155
- targetPath,
156
- write,
157
- replace
158
- }));
159
- }
160
- const result = {
161
- kind: FRONTIER_SWARM_CODEX_LINK_REPAIR_KIND,
162
- version: FRONTIER_SWARM_CODEX_LINK_REPAIR_VERSION,
163
- generatedAt: Date.now(),
164
- root,
165
- scope,
166
- packageRoots,
167
- write,
168
- replace,
169
- entries,
170
- summary: {
171
- total: entries.length,
172
- planned: entries.filter((entry) => entry.status === 'planned').length,
173
- linked: entries.filter((entry) => entry.status === 'linked').length,
174
- replaced: entries.filter((entry) => entry.status === 'replaced').length,
175
- alreadyLinked: entries.filter((entry) => entry.status === 'already-linked').length,
176
- excluded: entries.filter((entry) => entry.status === 'excluded').length,
177
- missingLocalPackage: entries.filter((entry) => entry.status === 'missing-local-package').length,
178
- conflicts: entries.filter((entry) => entry.status === 'conflict').length
179
- },
180
- ...(input.outFile ? { outFile: path.resolve(root, input.outFile) } : {})
181
- };
182
- if (result.outFile) {
183
- await fs.mkdir(path.dirname(result.outFile), { recursive: true });
184
- await fs.writeFile(result.outFile, JSON.stringify(result, null, 2) + '\n');
185
- }
186
- return result;
187
- }
188
- export async function runCodexSwarm(plan, options) {
189
- const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir);
190
- await fs.mkdir(outDir, { recursive: true });
191
- await fs.writeFile(path.join(outDir, 'swarm-plan.json'), JSON.stringify(plan, null, 2) + '\n');
192
- const eventStream = options.eventStream ?? createSwarmEventStream({
193
- runId: plan.runId,
194
- root: path.join(outDir, 'streams'),
195
- lanes: Array.from(new Set(plan.jobs.map((job) => job.lane)))
196
- });
197
- await initFileSwarmEventStream(eventStream);
198
- const pidManifestPath = path.resolve(options.cwd ?? process.cwd(), options.pidManifestPath ?? path.join(outDir, 'pids.json'));
199
- await appendCodexPidManifest(pidManifestPath, { pid: process.pid, role: 'parent', runId: plan.runId, startedAt: Date.now() }, plan.runId);
200
- let run = createSwarmRun({ plan, status: 'running', startedAt: Date.now() });
201
- const startedEvent = { type: 'swarm.started', runId: run.id, at: run.startedAt, data: { jobCount: plan.jobs.length } };
202
- run = recordSwarmEvent(run, startedEvent);
203
- await appendFileSwarmEvent(eventStream, startedEvent);
204
- const runOptions = { ...options, eventStream, pidManifestPath };
205
- const results = await runScheduledJobPool(plan, {
206
- concurrency: Math.max(1, options.maxConcurrency ?? 1),
207
- adaptive: options.adaptiveConcurrency,
208
- outDir,
209
- eventStream
210
- }, (job, lease) => runCodexJob(job, runOptions, outDir, lease));
211
- for (const result of results) {
212
- const job = plan.jobs.find((entry) => entry.id === result.jobId);
213
- if (job) {
214
- await options.onJobFinished?.({ job, result });
215
- await appendFileSwarmEvent(eventStream, {
216
- type: 'agent.finished',
217
- runId: run.id,
218
- jobId: job.id,
219
- taskId: job.taskId,
220
- lane: job.lane,
221
- data: { status: result.status, mergeReadiness: result.mergeReadiness, changedPathCount: result.changedPaths?.length ?? 0 }
222
- });
223
- }
224
- }
225
- for (const result of results)
226
- run = completeSwarmJob(run, result);
227
- const proof = createSwarmProof(run, { validation: plan.validation });
228
- const ok = run.summary.failedCount === 0 && run.summary.blockedCount === 0 && run.summary.ownershipViolationCount === 0;
229
- await appendFileSwarmEvent(eventStream, {
230
- type: 'swarm.finished',
231
- runId: run.id,
232
- data: { ok, summary: run.summary }
233
- });
234
- await fs.writeFile(path.join(outDir, 'swarm-results.json'), JSON.stringify({ ok, outDir, run, proof }, null, 2) + '\n');
235
- await writeSwarmCoordinatorSnapshot(options.coordinatorSnapshotPath ? path.resolve(options.cwd ?? process.cwd(), options.coordinatorSnapshotPath) : path.join(outDir, 'coordinator-dashboard.json'), {
236
- ok,
237
- outDir,
238
- plan,
239
- run,
240
- proof,
241
- eventStream,
242
- pidManifestPath
243
- });
244
- const result = { ok, outDir, plan, run, proof };
245
- await options.onSwarmFinished?.({ result });
246
- return result;
247
- }
248
- export async function runCodexJob(job, options, outDir, lease) {
249
- const paths = await createJobPaths(outDir, job, options);
250
- const workspace = await prepareCodexWorkspace(job, options);
251
- const workspacePlan = createCodexWorkspacePlan(job, options);
252
- const resourceAllocation = createCodexResourceAllocation(job, {
253
- cwd: options.cwd ?? process.cwd(),
254
- outDir,
255
- workspacePath: workspace,
256
- lease
257
- });
258
- if (resourceAllocation.browser?.profileDir)
259
- await fs.mkdir(resourceAllocation.browser.profileDir, { recursive: true });
260
- const hookInput = {
261
- job,
262
- cwd: options.cwd ?? process.cwd(),
263
- outDir,
264
- workspacePath: workspace,
265
- workspacePlan,
266
- paths,
267
- resourceAllocation
268
- };
269
- await options.prepareJobWorkspace?.(hookInput);
270
- const fileSnapshot = shouldSnapshotWorkspaceChanges(workspacePlan, options)
271
- ? await snapshotWorkspaceFiles(workspace)
272
- : undefined;
273
- await fs.writeFile(paths.resourceAllocationPath, JSON.stringify(resourceAllocation, null, 2) + '\n');
274
- const basePrompt = renderCodexPrompt(job, { workspacePath: workspace, paths, resourceAllocation });
275
- const prompt = options.renderJobPrompt
276
- ? await options.renderJobPrompt({ ...hookInput, prompt: basePrompt })
277
- : basePrompt;
278
- await fs.writeFile(paths.promptPath, prompt);
279
- const args = buildCodexArgs(job, { ...options, workspacePath: workspace, paths });
280
- await options.onJobStarted?.({ ...hookInput, prompt, args });
281
- await appendFileSwarmEvent(options.eventStream, {
282
- type: 'agent.scheduled',
283
- jobId: job.id,
284
- taskId: job.taskId,
285
- lane: job.lane,
286
- data: {
287
- workspace: workspacePlan.path,
288
- capabilities: job.capabilities,
289
- resourceRequirements: job.resourceRequirements,
290
- resourceAllocation
291
- }
292
- });
293
- const startedAt = Date.now();
294
- const execution = options.dryRun
295
- ? { exitCode: 0, changedPaths: [] }
296
- : await (options.executor ?? spawnCodexExecutor)({
297
- job,
298
- prompt,
299
- args,
300
- cwd: options.cwd ?? process.cwd(),
301
- workspacePath: workspace,
302
- codexPath: options.codexPath ?? 'codex',
303
- paths,
304
- resourceAllocation,
305
- env: resourceAllocation.env,
306
- timeoutMs: job.compute.timeoutMs ?? options.jobTimeoutMs ?? 7200000,
307
- compactLogs: normalizeCompactLogOptions(options.compactLogs)
308
- });
309
- const logSummary = execution.logSummary ?? createEmptyCodexLogSummary(paths);
310
- if (!execution.logSummary)
311
- await fs.writeFile(paths.logSummaryPath, JSON.stringify(logSummary, null, 2) + '\n');
312
- const collected = execution.changedPaths
313
- ? filterWorkspaceChangedPaths(execution.changedPaths, workspacePlan)
314
- : options.collectGitStatus === false
315
- ? { changedPaths: [], ignoredChangedPaths: [] }
316
- : await collectChangedPaths(workspace, fileSnapshot, workspacePlan);
317
- const rawChangedPaths = collected.changedPaths;
318
- const changedPaths = options.changedPathFilter ? [...options.changedPathFilter(rawChangedPaths, hookInput)] : rawChangedPaths;
319
- const workspaceProof = await createSwarmWorkspaceProof(workspacePlan, { ignoredChangedPaths: collected.ignoredChangedPaths });
320
- await fs.writeFile(paths.workspaceProofPath, JSON.stringify(workspaceProof, null, 2) + '\n');
321
- const ownership = checkSwarmOwnership(job, changedPaths);
322
- const verification = options.runVerification ? await runVerification(job.verification, workspace) : [];
323
- const failedVerification = verification.some((entry) => entry.required !== false && entry.status !== 0);
324
- const status = ownership.ok && execution.exitCode === 0 && !failedVerification ? 'completed' : 'failed';
325
- const patchPath = await writeCodexPatchFile({
326
- workspace,
327
- sourceRoot: path.resolve(options.cwd ?? process.cwd()),
328
- paths,
329
- workspacePlan,
330
- changedPaths
331
- });
332
- const semanticImport = await createCodexSemanticImportSidecar({
333
- job,
334
- workspace,
335
- changedPaths,
336
- evidenceDir: paths.evidenceDir,
337
- options: options.semanticImport
338
- });
339
- const semanticImportSummary = semanticImport?.sidecar.summary;
340
- const handoffArtifacts = await discoverCodexHandoffArtifacts({ root: paths.jobDir });
341
- const evidenceSummaryPath = path.join(paths.evidenceDir, 'evidence.json');
342
- const evidencePaths = uniqueStrings([
343
- paths.evidenceDir,
344
- evidenceSummaryPath,
345
- paths.resourceAllocationPath,
346
- paths.workspaceProofPath,
347
- paths.mergeBundlePath,
348
- ...(patchPath ? [patchPath] : []),
349
- ...(semanticImport ? [semanticImport.path] : []),
350
- paths.patchIntentPath,
351
- paths.logSummaryPath,
352
- ...handoffArtifacts.map((artifact) => artifact.path)
353
- ]);
354
- const result = {
355
- jobId: job.id,
356
- status,
357
- startedAt,
358
- finishedAt: Date.now(),
359
- exitCode: execution.exitCode,
360
- signal: execution.signal,
361
- changedPaths,
362
- changedRegions: job.changedRegions,
363
- ownershipViolations: ownership.violations,
364
- evidencePaths,
365
- ...(patchPath ? { patchPath } : {}),
366
- queueItemIds: [job.taskId],
367
- verification,
368
- ...(semanticImportSummary ? { semanticImport: semanticImportSummary } : {}),
369
- lastMessage: execution.lastMessage,
370
- error: execution.error,
371
- metadata: {
372
- ...(lease ? { leaseId: lease.id, leaseToken: lease.token, fencingToken: lease.fencingToken } : {}),
373
- resourceAllocation,
374
- logSummary,
375
- ...(semanticImportSummary ? { semanticImport: semanticImportSummary } : {}),
376
- codexHandoffArtifacts: handoffArtifacts
377
- }
378
- };
379
- const mergeBundle = createSwarmMergeBundle({
380
- runId: options.eventStream?.runId,
381
- job,
382
- result,
383
- ...(patchPath ? { patchPath } : {}),
384
- evidencePaths: uniqueStrings([
385
- paths.evidenceDir,
386
- evidenceSummaryPath,
387
- paths.resourceAllocationPath,
388
- paths.workspaceProofPath,
389
- paths.patchIntentPath,
390
- paths.logSummaryPath,
391
- ...(semanticImport ? [semanticImport.path] : []),
392
- ...handoffArtifacts.map((artifact) => artifact.path)
393
- ]),
394
- queueItemIds: [job.taskId],
395
- ...(semanticImportSummary ? { semanticImport: semanticImportSummary } : {}),
396
- ...(semanticImportSummary ? { metadata: { semanticImport: semanticImportSummary } } : {})
397
- });
398
- if (semanticImportSummary) {
399
- mergeBundle.semanticImport = semanticImportSummary;
400
- mergeBundle.metadata = {
401
- ...(isObject(mergeBundle.metadata) ? mergeBundle.metadata : {}),
402
- semanticImport: semanticImportSummary
403
- };
404
- }
405
- await fs.writeFile(paths.mergeBundlePath, JSON.stringify(mergeBundle, null, 2) + '\n');
406
- await writeCodexPatchIntent({
407
- file: paths.patchIntentPath,
408
- job,
409
- result,
410
- mergeBundle,
411
- patchPath,
412
- semanticImport: semanticImport?.sidecar,
413
- semanticImportExpected: options.semanticImportExpected ?? semanticImportEnabled(options.semanticImport),
414
- evidencePaths
415
- });
416
- await writeCodexJobEvidenceSummary({
417
- file: evidenceSummaryPath,
418
- job,
419
- result,
420
- mergeBundle,
421
- mergeBundlePath: paths.mergeBundlePath,
422
- patchPath,
423
- patchIntentPath: paths.patchIntentPath,
424
- logSummary,
425
- semanticImportPath: semanticImport?.path,
426
- semanticImport: semanticImport?.sidecar,
427
- handoffArtifacts
428
- });
429
- return result;
430
- }
431
- async function writeCodexJobEvidenceSummary(input) {
432
- const patchHunks = input.patchPath ? await readPatchHunks(input.patchPath) : [];
433
- const sourceCitations = createCodexEvidenceSourceCitations(input.mergeBundle, input.semanticImport);
434
- const evidence = {
435
- kind: FRONTIER_SWARM_CODEX_JOB_EVIDENCE_KIND,
436
- version: FRONTIER_SWARM_CODEX_JOB_EVIDENCE_VERSION,
437
- generatedAt: Date.now(),
438
- jobId: input.job.id,
439
- taskId: input.job.taskId,
440
- lane: input.job.lane,
441
- status: input.result.status ?? 'unknown',
442
- mergeReadiness: input.mergeBundle.mergeReadiness,
443
- disposition: input.mergeBundle.disposition,
444
- riskLevel: input.mergeBundle.riskLevel,
445
- changedPaths: [...input.mergeBundle.changedPaths],
446
- changedRegions: [...input.mergeBundle.changedRegions],
447
- ownershipViolations: [...input.mergeBundle.ownershipViolations],
448
- ...(input.patchPath ? { patchPath: input.patchPath } : {}),
449
- mergeBundlePath: input.mergeBundlePath,
450
- ...(input.patchIntentPath ? { patchIntentPath: input.patchIntentPath } : {}),
451
- ...(input.semanticImportPath ? { semanticImportPath: input.semanticImportPath } : {}),
452
- evidencePaths: uniqueStrings(input.mergeBundle.evidencePaths),
453
- handoffArtifacts: input.handoffArtifacts.map((artifact) => ({ ...artifact })),
454
- commands: {
455
- passed: input.mergeBundle.commandsPassed.map((command) => ({
456
- name: command.name,
457
- command: [...command.command],
458
- ...(command.status !== undefined ? { status: command.status } : {})
459
- })),
460
- failed: input.mergeBundle.commandsFailed.map((command) => ({
461
- name: command.name,
462
- command: [...command.command],
463
- ...(command.status !== undefined ? { status: command.status } : {})
464
- }))
465
- },
466
- patchHunks,
467
- readyToPortHunkCount: input.mergeBundle.disposition === 'needs-port' || input.mergeBundle.disposition === 'auto-mergeable' ? patchHunks.length : 0,
468
- ...(input.semanticImport ? { semanticImport: input.semanticImport.summary } : {}),
469
- sourceCitations,
470
- metadata: {
471
- autoMergeable: input.mergeBundle.autoMergeable,
472
- staleAgainstHead: input.mergeBundle.staleAgainstHead,
473
- ...(input.logSummary ? { logSummary: input.logSummary } : {}),
474
- reasons: input.mergeBundle.reasons
475
- }
476
- };
477
- await fs.writeFile(input.file, JSON.stringify(evidence, null, 2) + '\n');
478
- }
479
- async function writeCodexPatchIntent(input) {
480
- const patchHunks = input.patchPath ? await readPatchHunks(input.patchPath) : [];
481
- const semanticImportQuality = summarizeCodexSemanticImportQuality(input.semanticImport?.summary, input.semanticImportExpected);
482
- const warnings = uniqueStrings([
483
- ...semanticImportQuality.warnings,
484
- ...(input.mergeBundle.staleAgainstHead ? ['stale against coordinator head'] : []),
485
- ...(input.mergeBundle.ownershipViolations.length ? ['ownership violations present'] : []),
486
- ...(input.mergeBundle.commandsFailed.length ? ['verification commands failed'] : []),
487
- ...(input.mergeBundle.disposition === 'discovery-only' ? ['discovery-only output'] : [])
488
- ]);
489
- const intent = {
490
- kind: FRONTIER_SWARM_CODEX_PATCH_INTENT_KIND,
491
- version: FRONTIER_SWARM_CODEX_PATCH_INTENT_VERSION,
492
- generatedAt: Date.now(),
493
- jobId: input.job.id,
494
- taskId: input.job.taskId,
495
- lane: input.job.lane,
496
- changedPaths: [...input.mergeBundle.changedPaths],
497
- changedRegions: [...input.mergeBundle.changedRegions],
498
- intent: input.mergeBundle.changedPaths.length
499
- ? `Patch ${input.mergeBundle.changedPaths.slice(0, 5).join(', ')}`
500
- : 'No source patch produced',
501
- why: input.result.lastMessage ? firstNonEmptyLine(input.result.lastMessage) ?? input.job.task.objective : input.job.task.objective,
502
- riskLevel: input.mergeBundle.riskLevel,
503
- mergeReadiness: input.mergeBundle.mergeReadiness,
504
- disposition: input.mergeBundle.disposition,
505
- safeToPortManually: input.mergeBundle.commandsFailed.length === 0
506
- && input.mergeBundle.ownershipViolations.length === 0
507
- && !input.mergeBundle.staleAgainstHead
508
- && input.mergeBundle.disposition !== 'rejected'
509
- && input.mergeBundle.disposition !== 'blocked',
510
- verification: input.mergeBundle.commandsPassed.concat(input.mergeBundle.commandsFailed).map((command) => ({
511
- name: command.name,
512
- command: [...command.command],
513
- ...(command.status !== undefined ? { status: command.status } : {}),
514
- required: command.required
515
- })),
516
- evidencePaths: uniqueStrings(input.evidencePaths),
517
- semanticImportQuality,
518
- patchHunks,
519
- warnings
520
- };
521
- await fs.writeFile(input.file, JSON.stringify(intent, null, 2) + '\n');
522
- }
523
- async function readPatchHunks(file) {
524
- const text = await fs.readFile(file, 'utf8').catch(() => '');
525
- if (!text.trim())
526
- return [];
527
- const hunks = [];
528
- let currentFile;
529
- for (const line of text.split(/\r?\n/)) {
530
- if (line.startsWith('+++ ')) {
531
- currentFile = line.slice(4).replace(/^b\//, '').trim();
532
- continue;
533
- }
534
- if (!line.startsWith('@@'))
535
- continue;
536
- const match = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?/.exec(line);
537
- hunks.push({
538
- ...(currentFile ? { file: currentFile } : {}),
539
- header: line,
540
- ...(match ? {
541
- oldStart: Number(match[1]),
542
- oldLines: Number(match[2] ?? '1'),
543
- newStart: Number(match[3]),
544
- newLines: Number(match[4] ?? '1')
545
- } : {})
546
- });
547
- }
548
- return hunks;
549
- }
550
- function createCodexEvidenceSourceCitations(bundle, semanticImport) {
551
- const citations = [
552
- ...bundle.changedPaths.map((file) => ({ path: file, kind: 'changed-source' })),
553
- ...(semanticImport?.records ?? []).map((record) => ({
554
- path: record.path,
555
- kind: 'semantic-import',
556
- ...(record.language ? { language: record.language } : {}),
557
- ...(record.universalAstHash ? { hash: record.universalAstHash } : {})
558
- }))
559
- ];
560
- const seen = new Set();
561
- return citations.filter((citation) => {
562
- const key = `${citation.kind}:${citation.path}:${citation.language ?? ''}:${citation.hash ?? ''}`;
563
- if (seen.has(key))
564
- return false;
565
- seen.add(key);
566
- return true;
567
- });
568
- }
569
- async function createCodexSemanticImportSidecar(input) {
570
- const options = normalizeSemanticImportOptions(input.options);
571
- if (!options)
572
- return undefined;
573
- const selection = selectSemanticImportPaths(semanticImportCandidatePaths(input.job, input.changedPaths), options);
574
- const selected = selection.selected;
575
- const records = [];
576
- const importPath = path.join(input.evidenceDir, 'semantic-imports.json');
577
- if (!selected.length) {
578
- const sidecar = createSemanticImportSidecar(input.job, records, selection);
579
- await fs.writeFile(importPath, JSON.stringify(sidecar, null, 2) + '\n');
580
- return { path: importPath, sidecar };
581
- }
582
- const api = await loadFrontierLangForSemanticImport();
583
- if (!api.ok) {
584
- for (const file of selected) {
585
- records.push({
586
- path: file.path,
587
- language: file.language,
588
- status: 'error',
589
- reason: 'frontier-lang-unavailable',
590
- error: api.error
591
- });
592
- }
593
- const sidecar = createSemanticImportSidecar(input.job, records, selection);
594
- await fs.writeFile(importPath, JSON.stringify(sidecar, null, 2) + '\n');
595
- return { path: importPath, sidecar };
596
- }
597
- for (const file of selected) {
598
- const absolute = path.join(input.workspace, file.path);
599
- const stat = await fs.stat(absolute).catch(() => undefined);
600
- if (!stat?.isFile()) {
601
- records.push({ path: file.path, language: file.language, status: 'skipped', reason: 'not-a-file' });
602
- continue;
603
- }
604
- if (stat.size > options.maxBytes) {
605
- records.push({ path: file.path, language: file.language, status: 'skipped', reason: 'too-large', bytes: stat.size });
606
- continue;
607
- }
608
- try {
609
- const sourceText = await fs.readFile(absolute, 'utf8');
610
- const importResult = api.importNativeSource({
611
- language: file.language,
612
- sourcePath: file.path,
613
- sourceText,
614
- parser: 'source-text',
615
- metadata: {
616
- swarmJobId: input.job.id,
617
- swarmTaskId: input.job.taskId,
618
- swarmLane: input.job.lane
619
- }
620
- });
621
- const mergeCandidate = api.createSemanticMergeCandidateFromImport({ importResult });
622
- const semanticSidecar = api.createSemanticImportSidecar
623
- ? api.createSemanticImportSidecar(importResult, {
624
- targetPath: file.path,
625
- metadata: {
626
- swarmJobId: input.job.id,
627
- swarmTaskId: input.job.taskId,
628
- swarmLane: input.job.lane
629
- }
630
- })
631
- : undefined;
632
- const sourceProjection = api.projectNativeImportToSource
633
- ? api.projectNativeImportToSource(importResult, { sourceText, sourcePath: file.path })
634
- : undefined;
635
- const nativeCompile = api.compileNativeSource
636
- ? api.compileNativeSource(importResult, { target: file.language, sourceText, sourcePath: file.path, emitOnBlocked: true })
637
- : undefined;
638
- const sourceMaps = Array.isArray(importResult?.sourceMaps)
639
- ? importResult.sourceMaps
640
- : Array.isArray(importResult?.universalAst?.sourceMaps)
641
- ? importResult.universalAst.sourceMaps
642
- : [];
643
- records.push({
644
- path: file.path,
645
- language: file.language,
646
- status: 'imported',
647
- bytes: stat.size,
648
- importId: importResult?.id,
649
- universalAstHash: api.hashUniversalAstEnvelope && importResult?.universalAst
650
- ? api.hashUniversalAstEnvelope(importResult.universalAst)
651
- : undefined,
652
- nativeAstId: importResult?.nativeAst?.id,
653
- nativeSourceId: importResult?.nativeSource?.id,
654
- sourceMapCount: sourceMaps.length,
655
- sourceMapMappingCount: sourceMaps.reduce((sum, sourceMap) => sum + (Array.isArray(sourceMap?.mappings) ? sourceMap.mappings.length : 0), 0),
656
- evidenceCount: Array.isArray(importResult?.evidence) ? importResult.evidence.length : 0,
657
- lossCount: Array.isArray(importResult?.losses) ? importResult.losses.length : 0,
658
- losses: summarizeSemanticLosses(importResult?.losses),
659
- semanticIndex: summarizeSemanticIndex(importResult?.semanticIndex),
660
- semanticSidecar: summarizeLangSemanticImportSidecar(semanticSidecar),
661
- universalAstLayers: summarizeUniversalAstLayers(importResult?.universalAst, semanticSidecar),
662
- proofSpec: summarizeProofSpec(importResult?.universalAst?.proof, semanticSidecar),
663
- paradigmSemantics: summarizeParadigmSemantics(importResult?.universalAst?.paradigmSemantics, semanticSidecar),
664
- sourceProjection: summarizeNativeSourceProjection(sourceProjection),
665
- nativeCompile: summarizeNativeSourceCompile(nativeCompile),
666
- mergeCandidate: summarizeSemanticMergeCandidate(mergeCandidate)
667
- });
668
- }
669
- catch (error) {
670
- records.push({
671
- path: file.path,
672
- language: file.language,
673
- status: 'error',
674
- bytes: stat.size,
675
- error: error instanceof Error ? error.message : String(error)
676
- });
677
- }
678
- }
679
- const sidecar = createSemanticImportSidecar(input.job, records, selection);
680
- await fs.writeFile(importPath, JSON.stringify(sidecar, null, 2) + '\n');
681
- return { path: importPath, sidecar };
682
- }
683
- export async function discoverCodexHandoffArtifacts(input) {
684
- const root = path.resolve(input.root);
685
- const maxDepth = Math.max(0, Math.floor(input.maxDepth ?? 3));
686
- const maxArtifacts = Math.max(1, Math.floor(input.maxArtifacts ?? 64));
687
- const artifacts = [];
688
- const visit = async (dir, depth) => {
689
- if (artifacts.length >= maxArtifacts || depth > maxDepth)
690
- return;
691
- let entries;
692
- try {
693
- entries = await fs.readdir(dir, { withFileTypes: true });
694
- }
695
- catch {
696
- return;
697
- }
698
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
699
- if (artifacts.length >= maxArtifacts)
700
- return;
701
- if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === 'dist')
702
- continue;
703
- const full = path.join(dir, entry.name);
704
- if (entry.isDirectory()) {
705
- await visit(full, depth + 1);
706
- continue;
707
- }
708
- if (!entry.isFile())
709
- continue;
710
- const kind = classifyCodexHandoffArtifact(full);
711
- if (!kind)
712
- continue;
713
- const stat = await fs.stat(full).catch(() => undefined);
714
- artifacts.push({
715
- path: full,
716
- kind,
717
- ...(stat ? { bytes: stat.size } : {})
718
- });
719
- }
720
- };
721
- await visit(root, 0);
722
- return artifacts.sort((left, right) => left.path.localeCompare(right.path));
723
- }
724
- export function buildCodexArgs(job, input) {
725
- const model = resolveCodexModelFlag(job, input);
726
- const effort = resolveCodexReasoningEffort(job, input);
727
- const sandbox = job.compute.sandbox ?? input.sandbox ?? 'workspace-write';
728
- const approval = normalizeCodexApprovalPolicy(input.approval);
729
- const args = [
730
- ...(approval ? ['--ask-for-approval', approval] : []),
731
- 'exec',
732
- '--cd',
733
- input.workspacePath,
734
- '--add-dir',
735
- path.resolve(input.cwd ?? process.cwd(), input.outDir),
736
- '--sandbox',
737
- sandbox,
738
- '--json',
739
- '--output-last-message',
740
- input.paths.lastMessagePath
741
- ];
742
- if (model)
743
- args.push('--model', model);
744
- if (effort)
745
- args.push('-c', `model_reasoning_effort="${effort}"`);
746
- if (shouldSkipGitRepoCheck(input))
747
- args.push('--skip-git-repo-check');
748
- for (const dir of input.addDirs ?? [])
749
- args.push('--add-dir', dir);
750
- const profile = job.compute.profile ?? input.profile;
751
- if (profile)
752
- args.push('--profile', profile);
753
- if (input.ephemeral ?? true)
754
- args.push('--ephemeral');
755
- args.push('-');
756
- return args;
757
- }
758
- export function normalizeCodexModelFlag(model) {
759
- if (model === false || model == null)
760
- return undefined;
761
- const value = String(model).trim();
762
- if (!value)
763
- return undefined;
764
- const normalized = value.toLowerCase();
765
- if (normalized === 'auto' || normalized === 'default' || normalized === 'config' || normalized === 'config-default') {
766
- return undefined;
767
- }
768
- return value;
769
- }
770
- export function normalizeCodexApprovalPolicy(approval) {
771
- if (approval === false || approval == null)
772
- return undefined;
773
- const value = String(approval).trim().toLowerCase().replaceAll('_', '-');
774
- if (!value || value === 'default' || value === 'config-default')
775
- return undefined;
776
- if (value === 'never' || value === 'none' || value === 'off' || value === 'false' || value === 'full-auto')
777
- return 'never';
778
- if (value === 'untrusted')
779
- return 'untrusted';
780
- if (value === 'on-failure')
781
- return 'on-failure';
782
- if (value === 'on-request' || value === 'request' || value === 'manual')
783
- return 'on-request';
784
- throw new Error(`unsupported Codex approval policy "${approval}"; expected untrusted, on-request, on-failure, never, full-auto, none, or default`);
785
- }
786
- function resolveCodexModelFlag(job, input) {
787
- const explicit = normalizeCodexModelFlag(input.model);
788
- if (explicit || input.model === false)
789
- return explicit;
790
- const policy = input.modelPolicy ?? (input.forwardPlanModel ? 'plan' : 'config-default');
791
- if (policy === 'plan')
792
- return normalizeCodexModelFlag(job.compute.model ?? FRONTIER_SWARM_CODEX_DEFAULT_MODEL);
793
- return undefined;
794
- }
795
- function resolveCodexReasoningEffort(job, input) {
796
- if (input.reasoningEffort === false)
797
- return undefined;
798
- if (typeof input.reasoningEffort === 'string') {
799
- const explicit = input.reasoningEffort.trim();
800
- return explicit && explicit !== 'default' && explicit !== 'config-default' ? explicit : undefined;
801
- }
802
- const policy = input.modelPolicy ?? (input.forwardPlanModel || input.forwardPlanReasoningEffort ? 'plan' : 'config-default');
803
- if (policy !== 'plan')
804
- return undefined;
805
- const effort = job.compute.reasoningEffort ?? FRONTIER_SWARM_CODEX_DEFAULT_REASONING_EFFORT;
806
- return effort ? String(effort).trim() : undefined;
807
- }
808
- export function createCodexResourceAllocation(job, input) {
809
- const requirements = job.resourceRequirements;
810
- const capabilities = uniqueStrings([...(job.capabilities ?? []), ...(requirements?.capabilities ?? [])]);
811
- const resources = { ...(requirements?.resources ?? {}) };
812
- const env = {
813
- FRONTIER_SWARM_JOB_ID: job.id,
814
- FRONTIER_SWARM_TASK_ID: job.taskId,
815
- FRONTIER_SWARM_LANE: job.lane,
816
- FRONTIER_SWARM_CAPABILITIES: capabilities.join(',')
817
- };
818
- const browser = requirements?.browser;
819
- if (!browser)
820
- return { capabilities, resources, env };
821
- const portPool = uniqueWorkspacePaths(browser.portPool ?? []);
822
- const port = portPool.length ? portPool[resourceSlot(job, input.lease, portPool.length)] : undefined;
823
- const profileDir = resolveBrowserProfileDir(job, browser.profileDir, browser.profileDirPrefix, input.cwd ?? process.cwd());
824
- const browserAllocation = {
825
- required: browser.required,
826
- portPool,
827
- ...(port ? { port } : {}),
828
- ...(profileDir ? { profileDir } : {}),
829
- ...(browser.headless !== undefined ? { headless: browser.headless } : {})
830
- };
831
- env.FRONTIER_SWARM_BROWSER_REQUIRED = String(browser.required);
832
- if (port) {
833
- env.FRONTIER_SWARM_BROWSER_PORT = port;
834
- env.PORT = port;
835
- }
836
- if (profileDir)
837
- env.FRONTIER_SWARM_BROWSER_PROFILE_DIR = profileDir;
838
- if (browser.headless !== undefined)
839
- env.FRONTIER_SWARM_BROWSER_HEADLESS = String(browser.headless);
840
- env.FRONTIER_SWARM_RESOURCE_ALLOCATION = JSON.stringify({ capabilities, resources, browser: browserAllocation });
841
- return {
842
- capabilities,
843
- resources,
844
- env,
845
- browser: browserAllocation
846
- };
847
- }
848
- export function renderCodexPrompt(job, input) {
849
- const resourceAllocation = input.resourceAllocation ?? createCodexResourceAllocation(job, { outDir: input.paths.jobDir, workspacePath: input.workspacePath });
850
- return [
851
- '# Frontier Swarm Codex Job',
852
- '',
853
- `Job: ${job.id}`,
854
- `Task: ${job.taskId}`,
855
- `Lane: ${job.lane}`,
856
- `Layer: ${job.layer ?? 'none'}`,
857
- `Compute: ${job.compute.id}`,
858
- `Workspace: ${input.workspacePath}`,
859
- '',
860
- '## Ownership',
861
- '',
862
- 'Allowed write globs:',
863
- ...bullets(job.allowedWrites),
864
- '',
865
- 'Shared read-only globs:',
866
- ...bullets(job.sharedReadOnly),
867
- '',
868
- 'Never edit without parent assignment:',
869
- ...bullets(job.neverEdit),
870
- '',
871
- '## Task',
872
- '',
873
- job.task.objective,
874
- '',
875
- 'Dependencies:',
876
- ...bullets(job.dependsOn),
877
- '',
878
- 'Budget:',
879
- ...bullets(formatBudget(job)),
880
- '',
881
- 'Resource allocation:',
882
- ...bullets(formatResourceAllocation(resourceAllocation)),
883
- '',
884
- 'Source refs:',
885
- ...bullets(job.task.sourceRefs),
886
- '',
887
- 'Target refs:',
888
- ...bullets(job.task.targetRefs),
889
- '',
890
- 'Acceptance:',
891
- ...bullets(job.acceptance),
892
- '',
893
- 'Verification commands:',
894
- ...bullets(job.verification.map(formatCommand)),
895
- '',
896
- '## Evidence',
897
- '',
898
- `Write evidence under ${input.paths.evidenceDir}.`,
899
- 'Final response must include changed files, commands run, evidence paths, remaining gaps, and whether changed paths stayed inside allowed write globs.',
900
- '',
901
- 'Raw task JSON:',
902
- '',
903
- JSON.stringify(job.task, null, 2)
904
- ].join('\n') + '\n';
905
- }
906
- export async function spawnCodexExecutor(input) {
907
- await fs.writeFile(input.paths.eventsPath, '');
908
- await fs.writeFile(input.paths.stderrPath, '');
909
- const logOptions = normalizeCompactLogOptions(input.compactLogs);
910
- const eventLimit = logOptions.enabled === false ? Number.POSITIVE_INFINITY : logOptions.maxEventBytes ?? 1_000_000;
911
- const stderrLimit = logOptions.enabled === false ? Number.POSITIVE_INFINITY : logOptions.maxStderrBytes ?? 256_000;
912
- const logSummary = {
913
- eventsPath: input.paths.eventsPath,
914
- stderrPath: input.paths.stderrPath,
915
- eventBytes: 0,
916
- stderrBytes: 0,
917
- eventBytesWritten: 0,
918
- stderrBytesWritten: 0,
919
- eventBytesTruncated: 0,
920
- stderrBytesTruncated: 0
921
- };
922
- return new Promise((resolve) => {
923
- const child = spawn(input.codexPath, input.args, {
924
- cwd: input.cwd,
925
- stdio: ['pipe', 'pipe', 'pipe'],
926
- env: { ...process.env, ...input.env }
927
- });
928
- if (child.pid) {
929
- appendCodexPidManifest(input.paths.pidManifestPath, {
930
- pid: child.pid,
931
- role: 'codex',
932
- jobId: input.job.id,
933
- startedAt: Date.now(),
934
- command: [input.codexPath, ...input.args]
935
- }).catch(() => { });
936
- }
937
- const timer = setTimeout(() => child.kill('SIGTERM'), input.timeoutMs);
938
- child.stdout.on('data', (chunk) => appendLimitedLogChunk(input.paths.eventsPath, chunk, eventLimit, logSummary, 'event').catch(() => { }));
939
- child.stderr.on('data', (chunk) => appendLimitedLogChunk(input.paths.stderrPath, chunk, stderrLimit, logSummary, 'stderr').catch(() => { }));
940
- child.stdin.end(input.prompt);
941
- child.on('close', async (code, signal) => {
942
- clearTimeout(timer);
943
- await fs.writeFile(input.paths.logSummaryPath, JSON.stringify(logSummary, null, 2) + '\n').catch(() => { });
944
- resolve({
945
- exitCode: code ?? 1,
946
- ...(signal ? { signal } : {}),
947
- lastMessage: await readOptionalText(input.paths.lastMessagePath),
948
- logSummary
949
- });
950
- });
951
- child.on('error', (error) => {
952
- clearTimeout(timer);
953
- fs.writeFile(input.paths.logSummaryPath, JSON.stringify(logSummary, null, 2) + '\n').catch(() => { });
954
- resolve({ exitCode: 1, logSummary, error });
955
- });
956
- });
957
- }
958
- async function appendLimitedLogChunk(file, chunk, limit, summary, kind) {
959
- const bytes = chunk.byteLength;
960
- if (kind === 'event')
961
- summary.eventBytes += bytes;
962
- else
963
- summary.stderrBytes += bytes;
964
- const written = kind === 'event' ? summary.eventBytesWritten : summary.stderrBytesWritten;
965
- const available = Math.max(0, limit - written);
966
- if (available <= 0) {
967
- if (kind === 'event')
968
- summary.eventBytesTruncated += bytes;
969
- else
970
- summary.stderrBytesTruncated += bytes;
971
- return;
972
- }
973
- const slice = bytes > available ? chunk.subarray(0, available) : chunk;
974
- await fs.appendFile(file, slice);
975
- if (kind === 'event') {
976
- summary.eventBytesWritten += slice.byteLength;
977
- summary.eventBytesTruncated += bytes - slice.byteLength;
978
- }
979
- else {
980
- summary.stderrBytesWritten += slice.byteLength;
981
- summary.stderrBytesTruncated += bytes - slice.byteLength;
982
- }
983
- }
984
- async function createJobPaths(outDir, job, options) {
985
- const jobDir = path.join(outDir, job.id);
986
- const paths = {
987
- jobDir,
988
- promptPath: path.join(jobDir, 'prompt.md'),
989
- eventsPath: path.join(jobDir, 'codex-events.jsonl'),
990
- stderrPath: path.join(jobDir, 'codex-stderr.log'),
991
- lastMessagePath: path.join(jobDir, 'last-message.md'),
992
- evidenceDir: path.join(jobDir, 'evidence'),
993
- resourceAllocationPath: path.join(jobDir, 'evidence', 'resource-allocation.json'),
994
- workspaceProofPath: path.join(jobDir, 'evidence', 'workspace-proof.json'),
995
- patchPath: path.join(jobDir, 'evidence', 'changes.patch'),
996
- mergeBundlePath: path.join(jobDir, 'evidence', 'merge.json'),
997
- patchIntentPath: path.join(jobDir, 'evidence', 'patch-intent.json'),
998
- logSummaryPath: path.join(jobDir, 'evidence', 'log-summary.json'),
999
- pidManifestPath: path.resolve(options.cwd ?? process.cwd(), options.pidManifestPath ?? path.join(outDir, 'pids.json'))
1000
- };
1001
- await fs.mkdir(paths.evidenceDir, { recursive: true });
1002
- return paths;
1003
- }
1004
- export async function prepareCodexWorkspace(job, options) {
1005
- const cwd = path.resolve(options.cwd ?? process.cwd());
1006
- const plan = createCodexWorkspacePlan(job, options);
1007
- if (plan.mode === 'current')
1008
- return plan.path;
1009
- if (plan.mode === 'git-worktree') {
1010
- if (await pathExists(plan.path))
1011
- return plan.path;
1012
- if (options.workspace?.create === false)
1013
- throw new Error(`missing worktree for ${job.id}: ${plan.path}`);
1014
- await fs.mkdir(path.dirname(plan.path), { recursive: true });
1015
- await runProcess('git', ['worktree', 'add', '--detach', plan.path, 'HEAD'], { cwd });
1016
- return plan.path;
1017
- }
1018
- if (await pathExists(plan.path)) {
1019
- if (!plan.replace)
1020
- return plan.path;
1021
- assertGeneratedWorkspacePath(plan);
1022
- await fs.rm(plan.path, { recursive: true, force: true });
1023
- }
1024
- await fs.mkdir(plan.path, { recursive: true });
1025
- for (const include of plan.includes)
1026
- await copyWorkspacePath(cwd, plan.path, include, plan.excludes);
1027
- for (const include of plan.artifactIncludes)
1028
- await copyWorkspacePath(cwd, plan.path, include, []);
1029
- for (const linkPath of plan.linkPaths)
1030
- await linkWorkspacePath(cwd, plan.path, linkPath);
1031
- if (plan.linkNodeModules)
1032
- await linkWorkspacePath(cwd, plan.path, 'node_modules');
1033
- return plan.path;
1034
- }
1035
- export function createCodexWorkspacePlan(job, options) {
1036
- const cwd = path.resolve(options.cwd ?? process.cwd());
1037
- const workspace = options.workspace ?? { mode: 'current' };
1038
- const mode = workspace.mode ?? 'current';
1039
- const root = path.resolve(cwd, workspace.root ?? path.join('agent-worktrees', 'frontier-swarm-codex'));
1040
- const rawTask = readRawTask(job);
1041
- if (mode === 'current') {
1042
- const currentPath = path.resolve(cwd, job.worktreePath ?? '.');
1043
- return {
1044
- mode,
1045
- root,
1046
- path: currentPath,
1047
- includes: [],
1048
- excludes: [],
1049
- artifactIncludes: [],
1050
- linkPaths: [],
1051
- requiredIncludes: [],
1052
- optionalIncludes: [],
1053
- strategy: workspace.strategy ?? 'fs-cp',
1054
- ...(workspace.guardRoot ? { guardRoot: path.resolve(cwd, workspace.guardRoot) } : {}),
1055
- linkNodeModules: false,
1056
- replace: false,
1057
- skipGitRepoCheck: workspace.skipGitRepoCheck ?? false
1058
- };
1059
- }
1060
- const includes = uniqueWorkspacePaths([
1061
- ...DEFAULT_WORKSPACE_INCLUDES,
1062
- ...readStringArray(workspace.includes),
1063
- ...readStringArray(rawTask.snapshotIncludes),
1064
- ...readStringArray(rawTask.files),
1065
- ...job.task.sourceRefs,
1066
- ...job.task.targetRefs
1067
- ]);
1068
- const excludes = uniqueWorkspacePaths([
1069
- ...DEFAULT_WORKSPACE_EXCLUDES,
1070
- ...readStringArray(workspace.excludes),
1071
- ...readStringArray(rawTask.snapshotExcludes)
1072
- ]);
1073
- const artifactIncludes = uniqueWorkspacePaths([
1074
- ...readStringArray(workspace.artifactIncludes),
1075
- ...readStringArray(rawTask.snapshotArtifactIncludes)
1076
- ]);
1077
- const linkPaths = uniqueWorkspacePaths([
1078
- ...readStringArray(workspace.linkPaths),
1079
- ...readStringArray(rawTask.snapshotLinkPaths),
1080
- ...readStringArray(rawTask.linkPaths)
1081
- ]);
1082
- const requiredIncludes = uniqueWorkspacePaths([
1083
- ...readStringArray(workspace.requiredIncludes),
1084
- ...readStringArray(rawTask.requiredIncludes),
1085
- ...readStringArray(rawTask.snapshotRequiredIncludes)
1086
- ]);
1087
- const optionalIncludes = uniqueWorkspacePaths([
1088
- ...readStringArray(workspace.optionalIncludes),
1089
- ...readStringArray(rawTask.optionalIncludes),
1090
- ...readStringArray(rawTask.snapshotOptionalIncludes)
1091
- ]);
1092
- return {
1093
- mode,
1094
- root,
1095
- path: path.resolve(root, job.id),
1096
- includes,
1097
- excludes,
1098
- artifactIncludes,
1099
- linkPaths,
1100
- requiredIncludes,
1101
- optionalIncludes,
1102
- strategy: workspace.strategy ?? 'fs-cp',
1103
- guardRoot: path.resolve(cwd, workspace.guardRoot ?? workspace.root ?? path.join('agent-worktrees', 'frontier-swarm-codex')),
1104
- linkNodeModules: workspace.linkNodeModules ?? (mode !== 'git-worktree'),
1105
- replace: workspace.replace ?? false,
1106
- skipGitRepoCheck: workspace.skipGitRepoCheck ?? (mode === 'copy' || mode === 'snapshot')
1107
- };
1108
- }
1109
- export function createSwarmWorkspaceManifest(plan) {
1110
- return {
1111
- kind: FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_KIND,
1112
- version: FRONTIER_SWARM_CODEX_WORKSPACE_MANIFEST_VERSION,
1113
- id: 'codex-workspace:' + stableHash([plan.mode, plan.root, plan.path, plan.includes, plan.linkPaths]),
1114
- mode: plan.mode,
1115
- root: plan.root,
1116
- path: plan.path,
1117
- includes: [...plan.includes],
1118
- excludes: [...plan.excludes],
1119
- artifactIncludes: [...plan.artifactIncludes],
1120
- linkPaths: [...plan.linkPaths],
1121
- requiredIncludes: [...plan.requiredIncludes],
1122
- optionalIncludes: [...plan.optionalIncludes],
1123
- strategy: plan.strategy,
1124
- ...(plan.guardRoot ? { guardRoot: plan.guardRoot } : {}),
1125
- linkNodeModules: plan.linkNodeModules,
1126
- skipGitRepoCheck: plan.skipGitRepoCheck
1127
- };
1128
- }
1129
- export async function createSwarmWorkspaceProof(plan, input = {}) {
1130
- const generatedAt = input.generatedAt ?? Date.now();
1131
- const manifest = createSwarmWorkspaceManifest(plan);
1132
- const copiedCandidates = uniqueWorkspacePaths([...plan.includes, ...plan.artifactIncludes, ...plan.requiredIncludes]);
1133
- const optionalCandidates = uniqueWorkspacePaths(plan.optionalIncludes);
1134
- const copiedPaths = [];
1135
- const missingRequired = [];
1136
- const missingOptional = [];
1137
- for (const include of copiedCandidates) {
1138
- if (await pathExists(path.join(plan.path, include)))
1139
- copiedPaths.push(include);
1140
- else if (plan.requiredIncludes.includes(include))
1141
- missingRequired.push(include);
1142
- }
1143
- for (const include of optionalCandidates) {
1144
- if (await pathExists(path.join(plan.path, include)))
1145
- copiedPaths.push(include);
1146
- else
1147
- missingOptional.push(include);
1148
- }
1149
- const linkedPaths = [];
1150
- for (const linkPath of uniqueWorkspacePaths([...plan.linkPaths, ...(plan.linkNodeModules ? ['node_modules'] : [])])) {
1151
- const stat = await fs.lstat(path.join(plan.path, linkPath)).catch(() => undefined);
1152
- if (stat?.isSymbolicLink())
1153
- linkedPaths.push(linkPath);
1154
- }
1155
- const ignoredChangedPaths = uniqueWorkspacePaths(input.ignoredChangedPaths ?? []);
1156
- return {
1157
- kind: FRONTIER_SWARM_CODEX_WORKSPACE_PROOF_KIND,
1158
- version: FRONTIER_SWARM_CODEX_WORKSPACE_PROOF_VERSION,
1159
- id: 'codex-workspace-proof:' + stableHash([manifest.id, copiedPaths, linkedPaths, missingRequired, missingOptional, generatedAt]),
1160
- generatedAt,
1161
- manifest,
1162
- copiedPaths: uniqueWorkspacePaths(copiedPaths),
1163
- linkedPaths,
1164
- missingRequired,
1165
- missingOptional,
1166
- ignoredChangedPaths,
1167
- summary: {
1168
- copiedCount: uniqueWorkspacePaths(copiedPaths).length,
1169
- linkedCount: linkedPaths.length,
1170
- missingRequiredCount: missingRequired.length,
1171
- missingOptionalCount: missingOptional.length,
1172
- ignoredChangedPathCount: ignoredChangedPaths.length
1173
- }
1174
- };
1175
- }
1176
- export async function initFileSwarmEventStream(stream) {
1177
- if (!stream)
1178
- return;
1179
- const mailboxes = [stream.global, ...Object.values(stream.lanes)];
1180
- await Promise.all(mailboxes.map(async (mailbox) => {
1181
- if (!mailbox.path)
1182
- return;
1183
- await fs.mkdir(path.dirname(mailbox.path), { recursive: true });
1184
- await fs.writeFile(mailbox.path, '');
1185
- }));
1186
- }
1187
- export async function appendFileSwarmEvent(stream, event) {
1188
- if (!stream)
1189
- return;
1190
- const line = JSON.stringify({ at: Date.now(), ...event }) + '\n';
1191
- const paths = routeSwarmEventToMailboxes(stream, event)
1192
- .map((mailbox) => mailbox.path)
1193
- .filter((mailboxPath) => !!mailboxPath);
1194
- await Promise.all(paths.map(async (mailboxPath) => {
1195
- await fs.mkdir(path.dirname(mailboxPath), { recursive: true });
1196
- await fs.appendFile(mailboxPath, line);
1197
- }));
1198
- }
1199
- export async function writeSwarmCoordinatorSnapshot(file, input) {
1200
- const processes = input.pidManifestPath ? await readCodexPidProcesses(input.pidManifestPath).catch(() => []) : [];
1201
- const dashboard = createSwarmCoordinatorDashboard({
1202
- plan: input.plan,
1203
- run: input.run,
1204
- processes,
1205
- metadata: {
1206
- ok: input.ok,
1207
- outDir: input.outDir,
1208
- eventStream: input.eventStream ?? null,
1209
- pidManifestPath: input.pidManifestPath ?? null,
1210
- proof: input.proof
1211
- }
1212
- });
1213
- await fs.mkdir(path.dirname(file), { recursive: true });
1214
- await fs.writeFile(file, JSON.stringify(dashboard, null, 2) + '\n');
1215
- }
1216
- export async function appendCodexPidManifest(file, entry, runId) {
1217
- const absolute = path.resolve(file);
1218
- const previous = pidManifestWriteQueues.get(absolute) ?? Promise.resolve();
1219
- let next;
1220
- next = previous
1221
- .catch(() => { })
1222
- .then(() => appendCodexPidManifestUnlocked(absolute, entry, runId))
1223
- .finally(() => {
1224
- if (pidManifestWriteQueues.get(absolute) === next)
1225
- pidManifestWriteQueues.delete(absolute);
1226
- });
1227
- pidManifestWriteQueues.set(absolute, next);
1228
- return next;
1229
- }
1230
- async function appendCodexPidManifestUnlocked(file, entry, runId) {
1231
- const manifest = await readCodexPidManifest(file).catch(() => ({
1232
- kind: FRONTIER_SWARM_CODEX_PID_MANIFEST_KIND,
1233
- version: FRONTIER_SWARM_CODEX_PID_MANIFEST_VERSION,
1234
- ...(runId ? { runId } : {}),
1235
- entries: []
1236
- }));
1237
- const entries = manifest.entries.filter((existing) => existing.pid !== entry.pid || existing.jobId !== entry.jobId);
1238
- entries.push(entry);
1239
- await fs.mkdir(path.dirname(file), { recursive: true });
1240
- await writeJsonAtomic(file, { ...manifest, ...(runId ? { runId } : {}), entries });
1241
- }
1242
- export async function readCodexPidManifest(file) {
1243
- return JSON.parse(await fs.readFile(file, 'utf8'));
1244
- }
1245
- export async function stopCodexSwarmRun(input) {
1246
- const signal = input.signal ?? 'SIGTERM';
1247
- const pidManifestPath = await resolvePidManifestPath(input.run);
1248
- const manifest = await readCodexPidManifest(pidManifestPath);
1249
- const stopped = [];
1250
- const missing = [];
1251
- const errors = [];
1252
- for (const entry of manifest.entries.filter((item) => item.pid !== process.pid).sort((left, right) => right.startedAt - left.startedAt)) {
1253
- try {
1254
- process.kill(entry.pid, signal);
1255
- stopped.push(entry.pid);
1256
- }
1257
- catch (error) {
1258
- const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
1259
- if (code === 'ESRCH')
1260
- missing.push(entry.pid);
1261
- else
1262
- errors.push({ pid: entry.pid, error: error instanceof Error ? error.message : String(error) });
1263
- }
1264
- }
1265
- return { ok: errors.length === 0, pidManifestPath, signal, stopped, missing, errors };
1266
- }
1267
- export async function collectCodexSwarmRun(input) {
1268
- const generatedAt = Date.now();
1269
- const cwd = path.resolve(input.cwd ?? process.cwd());
1270
- const runDir = await resolveRunDirectory(input.run);
1271
- const outDir = path.resolve(cwd, input.outDir ?? path.join(runDir, 'collected'));
1272
- const buckets = {
1273
- 'ready-to-apply': [],
1274
- 'needs-human-port': [],
1275
- 'failed-evidence': [],
1276
- 'stale-against-head': []
1277
- };
1278
- const collectedBundles = [];
1279
- const evidenceEntries = [];
1280
- const patchStatuses = {};
1281
- const processes = await readCodexPidProcesses(path.join(runDir, 'pids.json')).catch(() => []);
1282
- const mergePaths = (await findFilesByName(runDir, 'merge.json'))
1283
- .filter((mergePath) => !pathHasIgnoredSegment(path.relative(runDir, mergePath), [
1284
- 'collected',
1285
- 'patch-scores',
1286
- 'ready-to-apply',
1287
- 'needs-human-port',
1288
- 'failed-evidence',
1289
- 'stale-against-head'
1290
- ]));
1291
- const mergeRecordsByJob = new Map();
1292
- for (const mergePath of mergePaths.sort()) {
1293
- const bundle = normalizeCollectedMergeBundle(JSON.parse(await fs.readFile(mergePath, 'utf8')), mergePath);
1294
- const existing = mergeRecordsByJob.get(bundle.jobId);
1295
- const next = { mergePath, bundle };
1296
- if (!existing || mergeRecordScore(next) > mergeRecordScore(existing))
1297
- mergeRecordsByJob.set(bundle.jobId, next);
1298
- }
1299
- const mergeRecords = Array.from(mergeRecordsByJob.values()).sort((left, right) => left.bundle.jobId.localeCompare(right.bundle.jobId));
1300
- for (const { mergePath, bundle } of mergeRecords) {
1301
- const patchPath = resolveBundlePatchPath(bundle, mergePath);
1302
- const patchExists = !!patchPath && await pathExists(patchPath);
1303
- const staleness = input.checkStale === false
1304
- ? { stale: false, patchStatus: patchExists ? 'unknown' : 'missing', reasons: ['stale check disabled'] }
1305
- : await bundlePatchStaleness(bundle, mergePath, cwd);
1306
- const staleAgainstHead = staleness.stale;
1307
- const bucket = classifyCodexCollectBucket(bundle, staleAgainstHead);
1308
- const branchName = input.branchPrefix ? `${input.branchPrefix}/${slug(bundle.jobId)}` : bundle.branchName;
1309
- const outputDir = path.join(outDir, bucket, slug(bundle.jobId));
1310
- const collectedEvidencePath = path.join(outputDir, 'evidence.json');
1311
- const collectReasons = staleness.patchStatus === 'applies'
1312
- ? bundle.reasons
1313
- : uniqueStrings([...bundle.reasons, ...staleness.reasons]);
1314
- const nextBundle = {
1315
- ...bundle,
1316
- ...(branchName ? { branchName } : {}),
1317
- staleAgainstHead: bundle.staleAgainstHead || staleAgainstHead,
1318
- disposition: staleAgainstHead ? 'stale-against-head' : bundle.disposition,
1319
- autoMergeable: bucket === 'ready-to-apply' && bundle.autoMergeable,
1320
- reasons: collectReasons,
1321
- metadata: {
1322
- ...(isObject(bundle.metadata) ? bundle.metadata : {}),
1323
- collect: {
1324
- patchStatus: staleness.patchStatus,
1325
- staleReasons: staleness.reasons
1326
- }
1327
- },
1328
- evidencePaths: uniqueStrings([...bundle.evidencePaths, collectedEvidencePath])
1329
- };
1330
- collectedBundles.push(nextBundle);
1331
- patchStatuses[nextBundle.jobId] = staleness.patchStatus;
1332
- await fs.mkdir(outputDir, { recursive: true });
1333
- await fs.writeFile(path.join(outputDir, 'merge.json'), JSON.stringify(nextBundle, null, 2) + '\n');
1334
- if (patchPath && await pathExists(patchPath))
1335
- await fs.copyFile(patchPath, path.join(outputDir, 'changes.patch')).catch(() => { });
1336
- await copyOrWriteCollectedEvidenceSummary({
1337
- file: collectedEvidencePath,
1338
- bundle: nextBundle,
1339
- bucket,
1340
- mergePath,
1341
- patchPath,
1342
- patchStatus: patchStatuses[nextBundle.jobId],
1343
- staleReasons: staleness.reasons
1344
- });
1345
- evidenceEntries.push(...createCollectedEvidenceEntries(nextBundle, collectedEvidencePath, bucket));
1346
- buckets[bucket].push({ bucket, jobId: bundle.jobId, mergePath, outputDir, bundle: nextBundle });
1347
- }
1348
- const mergeIndex = createSwarmMergeIndex({
1349
- runId: path.basename(runDir),
1350
- bundles: collectedBundles,
1351
- patchStatuses
1352
- });
1353
- const queueOverlay = createSwarmQueueOverlay({
1354
- runId: path.basename(runDir),
1355
- bundles: collectedBundles
1356
- });
1357
- const evidenceIndex = createSwarmEvidenceIndex({
1358
- id: `codex-evidence-index:${path.basename(runDir)}`,
1359
- entries: evidenceEntries,
1360
- generatedAt
1361
- });
1362
- const admission = createSwarmMergeAdmission({
1363
- index: mergeIndex,
1364
- maxReady: Math.max(mergeIndex.summary.readyToApplyCount, 1),
1365
- allowRisks: ['low', 'medium', 'unknown'],
1366
- generatedAt
1367
- });
1368
- const dashboard = createSwarmCoordinatorDashboard({
1369
- bundles: collectedBundles,
1370
- mergeIndex,
1371
- queueOverlay,
1372
- evidenceIndex,
1373
- admission,
1374
- processes,
1375
- generatedAt,
1376
- metadata: { runDir, outDir }
1377
- });
1378
- const compactDashboard = createCodexCompactDashboard({
1379
- runDir,
1380
- dashboard,
1381
- semanticImportExpected: input.semanticImportExpected ?? false,
1382
- generatedAt
1383
- });
1384
- const summary = {
1385
- total: mergeRecords.length,
1386
- 'ready-to-apply': buckets['ready-to-apply'].length,
1387
- 'needs-human-port': buckets['needs-human-port'].length,
1388
- 'failed-evidence': buckets['failed-evidence'].length,
1389
- 'stale-against-head': buckets['stale-against-head'].length
1390
- };
1391
- const result = {
1392
- kind: FRONTIER_SWARM_CODEX_COLLECTION_KIND,
1393
- version: FRONTIER_SWARM_CODEX_COLLECTION_VERSION,
1394
- ok: summary['failed-evidence'] === 0 && summary['stale-against-head'] === 0,
1395
- runDir,
1396
- outDir,
1397
- generatedAt,
1398
- buckets,
1399
- mergeIndex,
1400
- queueOverlay,
1401
- evidenceIndex,
1402
- admission,
1403
- dashboard,
1404
- compactDashboard,
1405
- summary
1406
- };
1407
- await fs.mkdir(outDir, { recursive: true });
1408
- await fs.writeFile(path.join(outDir, 'collection.json'), JSON.stringify(result, null, 2) + '\n');
1409
- await fs.writeFile(path.join(outDir, 'merge-index.json'), JSON.stringify(mergeIndex, null, 2) + '\n');
1410
- await fs.writeFile(path.join(outDir, 'queue-overlay.json'), JSON.stringify(queueOverlay, null, 2) + '\n');
1411
- await fs.writeFile(path.join(outDir, 'evidence-index.json'), JSON.stringify(evidenceIndex, null, 2) + '\n');
1412
- await fs.writeFile(path.join(outDir, 'merge-admission.json'), JSON.stringify(admission, null, 2) + '\n');
1413
- await fs.writeFile(path.join(outDir, 'coordinator-query.json'), JSON.stringify(dashboard, null, 2) + '\n');
1414
- await fs.writeFile(path.join(outDir, 'compact-dashboard.json'), JSON.stringify(compactDashboard, null, 2) + '\n');
1415
- return result;
1416
- }
1417
- async function readCodexPidProcesses(file) {
1418
- const manifest = await readCodexPidManifest(file);
1419
- return Promise.all(manifest.entries.map(async (entry) => ({
1420
- pid: entry.pid,
1421
- role: entry.role,
1422
- ...(entry.jobId ? { jobId: entry.jobId } : {}),
1423
- ...(entry.runId ? { runId: entry.runId } : {}),
1424
- status: await pidIsAlive(entry.pid) ? 'running' : 'missing',
1425
- startedAt: entry.startedAt,
1426
- ...(entry.command ? { command: entry.command } : {})
1427
- })));
1428
- }
1429
- async function pidIsAlive(pid) {
1430
- try {
1431
- process.kill(pid, 0);
1432
- return true;
1433
- }
1434
- catch {
1435
- return false;
1436
- }
1437
- }
1438
- async function copyOrWriteCollectedEvidenceSummary(input) {
1439
- const existing = input.bundle.evidencePaths.find((entry) => path.basename(entry) === 'evidence.json' && entry !== input.file);
1440
- const traceSummary = codexBundleTraceSummary(input.bundle);
1441
- await fs.mkdir(path.dirname(input.file), { recursive: true });
1442
- if (existing && await pathExists(existing)) {
1443
- await fs.copyFile(existing, input.file).catch(() => { });
1444
- if (await pathExists(input.file)) {
1445
- if (traceSummary.shardCount)
1446
- await augmentCollectedEvidenceTraceSummary(input.file, traceSummary);
1447
- return;
1448
- }
1449
- }
1450
- const patchHunks = input.patchPath ? await readPatchHunks(input.patchPath) : [];
1451
- const evidence = {
1452
- kind: FRONTIER_SWARM_CODEX_JOB_EVIDENCE_KIND,
1453
- version: FRONTIER_SWARM_CODEX_JOB_EVIDENCE_VERSION,
1454
- generatedAt: Date.now(),
1455
- jobId: input.bundle.jobId,
1456
- taskId: input.bundle.taskId ?? input.bundle.jobId,
1457
- lane: input.bundle.lane ?? 'unknown',
1458
- status: input.bundle.status,
1459
- mergeReadiness: input.bundle.mergeReadiness,
1460
- disposition: input.bundle.disposition,
1461
- riskLevel: input.bundle.riskLevel,
1462
- changedPaths: [...input.bundle.changedPaths],
1463
- changedRegions: [...input.bundle.changedRegions],
1464
- ownershipViolations: [...input.bundle.ownershipViolations],
1465
- ...(input.patchPath ? { patchPath: input.patchPath } : {}),
1466
- mergeBundlePath: input.mergePath,
1467
- evidencePaths: uniqueStrings(input.bundle.evidencePaths),
1468
- handoffArtifacts: input.bundle.evidencePaths.map((entry) => ({ path: entry, kind: classifyCodexHandoffArtifact(entry) ?? 'evidence' })),
1469
- commands: {
1470
- passed: input.bundle.commandsPassed.map((command) => ({ name: command.name, command: [...command.command], ...(command.status !== undefined ? { status: command.status } : {}) })),
1471
- failed: input.bundle.commandsFailed.map((command) => ({ name: command.name, command: [...command.command], ...(command.status !== undefined ? { status: command.status } : {}) }))
1472
- },
1473
- patchHunks,
1474
- readyToPortHunkCount: input.bucket === 'needs-human-port' || input.bucket === 'ready-to-apply' ? patchHunks.length : 0,
1475
- ...(input.bundle.semanticImport ? { semanticImport: input.bundle.semanticImport } : {}),
1476
- ...(traceSummary.shardCount ? { traceSummary } : {}),
1477
- sourceCitations: createCodexEvidenceSourceCitations(input.bundle),
1478
- metadata: {
1479
- bucket: input.bucket,
1480
- patchStatus: input.patchStatus,
1481
- staleReasons: input.staleReasons ?? [],
1482
- autoMergeable: input.bundle.autoMergeable,
1483
- staleAgainstHead: input.bundle.staleAgainstHead,
1484
- reasons: input.bundle.reasons
1485
- }
1486
- };
1487
- await fs.writeFile(input.file, JSON.stringify(evidence, null, 2) + '\n');
1488
- }
1489
- async function augmentCollectedEvidenceTraceSummary(file, traceSummary) {
1490
- try {
1491
- const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
1492
- if (!isObject(parsed))
1493
- return;
1494
- parsed.traceSummary = traceSummary;
1495
- await fs.writeFile(file, JSON.stringify(parsed, null, 2) + '\n');
1496
- }
1497
- catch {
1498
- // Keep collection best-effort when a worker evidence file is not JSON.
1499
- }
1500
- }
1501
- function createCollectedEvidenceEntries(bundle, collectedEvidencePath, bucket) {
1502
- const confidence = bucket === 'ready-to-apply' ? 0.95 : bucket === 'needs-human-port' ? 0.7 : bucket === 'failed-evidence' ? 0.25 : 0.2;
1503
- const universalAstLayers = semanticImportUniversalAstLayerSummary(bundle.semanticImport);
1504
- const traceSummary = codexBundleTraceSummary(bundle);
1505
- const entries = [{
1506
- jobId: bundle.jobId,
1507
- queueItemId: bundle.queueItemIds[0],
1508
- lane: bundle.lane,
1509
- topic: 'merge-admission',
1510
- path: collectedEvidencePath,
1511
- kind: 'evidence',
1512
- status: bucket,
1513
- confidence,
1514
- tags: ['coordinator-query', bucket],
1515
- facets: {
1516
- bucket,
1517
- disposition: bundle.disposition,
1518
- riskLevel: bundle.riskLevel,
1519
- autoMergeable: bundle.autoMergeable,
1520
- staleAgainstHead: bundle.staleAgainstHead,
1521
- semanticSymbols: bundle.semanticImport?.semanticIndex.symbols ?? 0,
1522
- semanticRegions: bundle.semanticImport?.semanticSidecars.ownershipRegions ?? 0,
1523
- universalAstLayers: universalAstLayers.total,
1524
- universalAstLayerNames: universalAstLayers.names.join(','),
1525
- proofSpecObligations: semanticImportProofSpecSummary(bundle.semanticImport).obligations,
1526
- proofSpecFailedObligations: semanticImportProofSpecSummary(bundle.semanticImport).failed,
1527
- paradigmSemanticsRecords: semanticImportParadigmSemanticsSummary(bundle.semanticImport).total,
1528
- paradigmSemanticsGroups: semanticImportParadigmSemanticsSummary(bundle.semanticImport).groups.length,
1529
- paradigmSemanticsLoweringRecords: semanticImportParadigmSemanticsSummary(bundle.semanticImport).loweringRecords,
1530
- traceShards: traceSummary.shardCount,
1531
- traceDivergences: traceSummary.divergenceCount,
1532
- traceOpenDivergences: traceSummary.openDivergenceCount,
1533
- traceRowWindows: traceSummary.rowWindowCount,
1534
- traceHypotheses: traceSummary.hypothesisCount,
1535
- traceExecutableOwnershipRegions: traceSummary.executableOwnershipRegionCount,
1536
- traceFocusedTests: traceSummary.focusedTestCount,
1537
- traceReferenceEvidence: traceSummary.referenceEvidenceCount
1538
- }
1539
- }];
1540
- for (const file of bundle.evidencePaths) {
1541
- if (file === collectedEvidencePath)
1542
- continue;
1543
- entries.push({
1544
- jobId: bundle.jobId,
1545
- queueItemId: bundle.queueItemIds[0],
1546
- lane: bundle.lane,
1547
- topic: path.basename(file),
1548
- path: file,
1549
- kind: classifyCodexHandoffArtifact(file) ?? 'evidence',
1550
- status: bucket,
1551
- confidence,
1552
- tags: [bucket],
1553
- facets: { bucket, disposition: bundle.disposition }
1554
- });
1555
- }
1556
- return entries;
1557
- }
1558
- function createCodexCompactDashboard(input) {
1559
- const qualities = new Map(input.dashboard.jobs.map((job) => [
1560
- job.jobId,
1561
- summarizeCodexSemanticImportQuality(job.semanticImport, input.semanticImportExpected)
1562
- ]));
1563
- const semanticQualities = Array.from(qualities.values());
1564
- const traceSummaries = input.dashboard.jobs
1565
- .map((job) => codexJobTraceSummary(job))
1566
- .filter((entry) => Boolean(entry));
1567
- const traceSummary = summarizeCodexTraceSummaries(traceSummaries);
1568
- const usefulPatchJobs = input.dashboard.jobs.filter((job) => ((job.disposition === 'auto-mergeable' || job.disposition === 'needs-port')
1569
- && job.changedPaths.length > 0
1570
- && job.tests.requiredFailed === 0));
1571
- const topJobs = [...input.dashboard.jobs]
1572
- .filter((job) => job.changedPaths.length > 0 || job.evidencePaths.length > 0)
1573
- .sort((left, right) => right.mergeScore - left.mergeScore || left.jobId.localeCompare(right.jobId))
1574
- .slice(0, 20)
1575
- .map((job) => ({
1576
- jobId: job.jobId,
1577
- ...(job.lane ? { lane: job.lane } : {}),
1578
- disposition: job.disposition,
1579
- mergeScore: job.mergeScore,
1580
- changedPaths: job.changedPaths.slice(0, 12),
1581
- semanticImportQuality: qualities.get(job.jobId),
1582
- ...(codexJobTraceSummary(job) ? { traceSummary: codexJobTraceSummary(job) } : {}),
1583
- staleAgainstHead: job.staleAgainstHead,
1584
- ...(job.duplicateGroupId ? { duplicateGroupId: job.duplicateGroupId } : {}),
1585
- evidencePaths: job.evidencePaths.slice(0, 12)
1586
- }));
1587
- return {
1588
- kind: FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_KIND,
1589
- version: FRONTIER_SWARM_CODEX_COMPACT_DASHBOARD_VERSION,
1590
- generatedAt: input.generatedAt,
1591
- runDir: input.runDir,
1592
- total: input.dashboard.summary.jobCount,
1593
- activeJobs: input.dashboard.jobs.filter((job) => job.liveness === 'running').length,
1594
- usefulPatchCount: usefulPatchJobs.length,
1595
- stalePatchCount: input.dashboard.summary.staleAgainstHeadCount,
1596
- duplicateDiscoveryCount: input.dashboard.duplicateGroups.length,
1597
- semanticImport: {
1598
- expected: input.semanticImportExpected,
1599
- presentCount: semanticQualities.filter((entry) => entry.present).length,
1600
- emptyCount: semanticQualities.filter((entry) => entry.empty).length,
1601
- weakCount: semanticQualities.filter((entry) => entry.present && entry.warnings.length > 0).length,
1602
- symbolCount: semanticQualities.reduce((sum, entry) => sum + entry.symbols, 0),
1603
- ownershipRegionCount: semanticQualities.reduce((sum, entry) => sum + entry.ownershipRegions, 0),
1604
- patchHintCount: semanticQualities.reduce((sum, entry) => sum + entry.patchHints, 0),
1605
- universalAstLayerCount: semanticQualities.reduce((sum, entry) => sum + entry.universalAstLayers, 0),
1606
- universalAstLayerNames: uniqueStrings(semanticQualities.flatMap((entry) => entry.universalAstLayerNames)),
1607
- proofSpecObligations: semanticQualities.reduce((sum, entry) => sum + entry.proofSpecObligations, 0),
1608
- proofSpecFailedObligations: semanticQualities.reduce((sum, entry) => sum + entry.proofSpecFailedObligations, 0),
1609
- paradigmSemanticsRecords: semanticQualities.reduce((sum, entry) => sum + entry.paradigmSemanticsRecords, 0),
1610
- paradigmSemanticsGroups: semanticQualities.reduce((sum, entry) => sum + entry.paradigmSemanticsGroups, 0),
1611
- paradigmSemanticsLoweringRecords: semanticQualities.reduce((sum, entry) => sum + entry.paradigmSemanticsLoweringRecords, 0)
1612
- },
1613
- trace: {
1614
- shardCount: traceSummary.shardCount,
1615
- jobsWithTraceShards: traceSummaries.filter((entry) => entry.shardCount > 0).length,
1616
- rowWindowCount: traceSummary.rowWindowCount,
1617
- hypothesisCount: traceSummary.hypothesisCount,
1618
- executableOwnershipRegionCount: traceSummary.executableOwnershipRegionCount,
1619
- focusedTestCount: traceSummary.focusedTestCount,
1620
- referenceEvidenceCount: traceSummary.referenceEvidenceCount,
1621
- divergenceCount: traceSummary.divergenceCount,
1622
- openDivergenceCount: traceSummary.openDivergenceCount
1623
- },
1624
- evidence: {
1625
- readyToApply: input.dashboard.summary.readyToApplyCount,
1626
- needsHumanPort: input.dashboard.summary.needsHumanPortCount,
1627
- failedEvidence: input.dashboard.summary.failedEvidenceCount,
1628
- averageMergeScore: input.dashboard.summary.averageMergeScore
1629
- },
1630
- topJobs
1631
- };
1632
- }
1633
- export async function applyCodexSwarmCollection(input) {
1634
- const generatedAt = Date.now();
1635
- const cwd = path.resolve(input.cwd ?? process.cwd());
1636
- const dryRun = input.dryRun ?? true;
1637
- if (!input.collection && !input.run)
1638
- throw new Error('apply requires --collection <dir> or --run <run-dir>');
1639
- const collectionDir = input.collection
1640
- ? path.resolve(cwd, input.collection)
1641
- : (await collectCodexSwarmRun({ run: String(input.run ?? ''), cwd, outDir: input.outDir })).outDir;
1642
- const outDir = path.resolve(cwd, input.outDir ?? path.join(collectionDir, 'apply-ledger'));
1643
- if (!dryRun && !input.allowDirty) {
1644
- const dirty = await gitDirty(cwd);
1645
- if (dirty.length)
1646
- throw new Error(`refusing to apply into dirty worktree; pass allowDirty to override (${dirty.slice(0, 8).join(', ')})`);
1647
- }
1648
- const bucket = input.bucket ?? 'ready-to-apply';
1649
- const roots = bucket === 'all'
1650
- ? ['ready-to-apply', 'needs-human-port', 'failed-evidence', 'stale-against-head'].map((entry) => path.join(collectionDir, entry))
1651
- : [path.join(collectionDir, bucket)];
1652
- const wanted = new Set(input.jobIds ?? []);
1653
- const mergePaths = (await Promise.all(roots.map((root) => findFilesByName(root, 'merge.json')))).flat().sort();
1654
- const entries = [];
1655
- for (const mergePath of mergePaths.slice(0, input.limit ? Math.max(0, Math.floor(input.limit)) : undefined)) {
1656
- const bundle = JSON.parse(await fs.readFile(mergePath, 'utf8'));
1657
- if (wanted.size && !wanted.has(bundle.jobId))
1658
- continue;
1659
- entries.push(await applyCodexMergeBundle({
1660
- cwd,
1661
- bundle,
1662
- mergePath,
1663
- dryRun,
1664
- commit: input.commit ?? false,
1665
- branchPrefix: input.branchPrefix
1666
- }));
1667
- }
1668
- const summary = {
1669
- total: entries.length,
1670
- checked: entries.filter((entry) => entry.status === 'checked').length,
1671
- applied: entries.filter((entry) => entry.status === 'applied').length,
1672
- committed: entries.filter((entry) => entry.status === 'committed').length,
1673
- skipped: entries.filter((entry) => entry.status === 'skipped').length,
1674
- failed: entries.filter((entry) => entry.status === 'failed').length
1675
- };
1676
- const result = {
1677
- kind: FRONTIER_SWARM_CODEX_APPLY_LEDGER_KIND,
1678
- version: FRONTIER_SWARM_CODEX_APPLY_LEDGER_VERSION,
1679
- ok: summary.failed === 0,
1680
- cwd,
1681
- collectionDir,
1682
- outDir,
1683
- generatedAt,
1684
- dryRun,
1685
- entries,
1686
- summary
1687
- };
1688
- await fs.mkdir(outDir, { recursive: true });
1689
- await fs.writeFile(path.join(outDir, 'apply-ledger.json'), JSON.stringify(result, null, 2) + '\n');
1690
- return result;
1691
- }
1692
- export async function scoreCodexSwarmPatches(input) {
1693
- const generatedAt = Date.now();
1694
- const cwd = path.resolve(input.cwd ?? process.cwd());
1695
- if (!input.collection && !input.run)
1696
- throw new Error('score requires --collection <dir> or --run <run-dir>');
1697
- const collectionDir = input.collection
1698
- ? path.resolve(cwd, input.collection)
1699
- : (await collectCodexSwarmRun({ run: String(input.run ?? ''), cwd, outDir: input.outDir })).outDir;
1700
- const outDir = path.resolve(cwd, input.outDir ?? path.join(collectionDir, 'patch-scores'));
1701
- const bucket = input.bucket ?? 'all';
1702
- const roots = bucket === 'all'
1703
- ? ['ready-to-apply', 'needs-human-port', 'failed-evidence', 'stale-against-head'].map((entry) => path.join(collectionDir, entry))
1704
- : [path.join(collectionDir, bucket)];
1705
- const wanted = new Set(input.jobIds ?? []);
1706
- const mergePaths = (await Promise.all(roots.map((root) => findFilesByName(root, 'merge.json')))).flat().sort();
1707
- const entries = [];
1708
- for (const mergePath of mergePaths.slice(0, input.limit ? Math.max(0, Math.floor(input.limit)) : undefined)) {
1709
- const bundle = JSON.parse(await fs.readFile(mergePath, 'utf8'));
1710
- if (wanted.size && !wanted.has(bundle.jobId))
1711
- continue;
1712
- entries.push(await scoreCodexMergeBundle({ cwd, mergePath, bundle, outDir, input }));
1713
- }
1714
- const statuses = ['accepted-clean', 'accepted-needs-port', 'conflict', 'test-fail', 'stale', 'evidence-only'];
1715
- const summary = Object.fromEntries(statuses.map((status) => [status, entries.filter((entry) => entry.status === status).length]));
1716
- const result = {
1717
- kind: FRONTIER_SWARM_CODEX_PATCH_SCORE_KIND,
1718
- version: FRONTIER_SWARM_CODEX_PATCH_SCORE_VERSION,
1719
- ok: entries.every((entry) => entry.status === 'accepted-clean' || entry.status === 'accepted-needs-port' || entry.status === 'evidence-only'),
1720
- cwd,
1721
- collectionDir,
1722
- outDir,
1723
- generatedAt,
1724
- entries: entries.sort((left, right) => right.score - left.score || left.jobId.localeCompare(right.jobId)),
1725
- summary: { ...summary, total: entries.length }
1726
- };
1727
- await fs.mkdir(outDir, { recursive: true });
1728
- await fs.writeFile(path.join(outDir, 'patch-score.json'), JSON.stringify(result, null, 2) + '\n');
1729
- return result;
1730
- }
1731
- async function applyCodexMergeBundle(input) {
1732
- const commands = [];
1733
- const patchPath = await resolveApplyPatchPath(input.bundle, input.mergePath);
1734
- const branchName = input.branchPrefix ? `${input.branchPrefix}/${slug(input.bundle.jobId)}` : input.bundle.branchName;
1735
- const base = {
1736
- jobId: input.bundle.jobId,
1737
- bundlePath: input.mergePath,
1738
- ...(patchPath ? { patchPath } : {}),
1739
- ...(branchName ? { branchName } : {}),
1740
- dryRun: input.dryRun,
1741
- commands
1742
- };
1743
- if (!patchPath) {
1744
- return {
1745
- ...base,
1746
- status: input.bundle.disposition === 'discovery-only' ? 'skipped' : 'failed',
1747
- error: 'missing patch'
1748
- };
1749
- }
1750
- const check = await runLoggedProcess('git', ['apply', '--check', patchPath], input.cwd);
1751
- commands.push(check);
1752
- if (check.status !== 0)
1753
- return { ...base, status: 'failed', error: 'git apply --check failed' };
1754
- if (input.dryRun)
1755
- return { ...base, status: 'checked' };
1756
- if (branchName) {
1757
- const branch = await runLoggedProcess('git', ['switch', '-c', branchName], input.cwd);
1758
- commands.push(branch);
1759
- if (branch.status !== 0)
1760
- return { ...base, status: 'failed', error: 'git switch -c failed' };
1761
- }
1762
- const apply = await runLoggedProcess('git', ['apply', patchPath], input.cwd);
1763
- commands.push(apply);
1764
- if (apply.status !== 0)
1765
- return { ...base, status: 'failed', error: 'git apply failed' };
1766
- if (!input.commit)
1767
- return { ...base, status: 'applied' };
1768
- const add = await runLoggedProcess('git', ['add', '--', ...input.bundle.changedPaths], input.cwd);
1769
- commands.push(add);
1770
- if (add.status !== 0)
1771
- return { ...base, status: 'failed', error: 'git add failed' };
1772
- const commit = await runLoggedProcess('git', ['commit', '-m', `Apply swarm bundle ${input.bundle.jobId}`], input.cwd);
1773
- commands.push(commit);
1774
- if (commit.status !== 0)
1775
- return { ...base, status: 'failed', error: 'git commit failed' };
1776
- const rev = await runLoggedProcess('git', ['rev-parse', 'HEAD'], input.cwd);
1777
- commands.push(rev);
1778
- return {
1779
- ...base,
1780
- status: 'committed',
1781
- commit: rev.stdoutTail[0]
1782
- };
1783
- }
1784
- async function scoreCodexMergeBundle(input) {
1785
- const commands = [];
1786
- const patchPath = await resolveApplyPatchPath(input.bundle, input.mergePath);
1787
- const semanticEvidence = summarizePatchScoreSemanticEvidence(input.bundle);
1788
- const base = {
1789
- jobId: input.bundle.jobId,
1790
- bundlePath: input.mergePath,
1791
- ...(patchPath ? { patchPath } : {}),
1792
- changedPaths: [...input.bundle.changedPaths],
1793
- semanticEvidence,
1794
- commands
1795
- };
1796
- if (!patchPath || input.bundle.disposition === 'discovery-only' || input.bundle.changedPaths.length === 0) {
1797
- return {
1798
- ...base,
1799
- status: 'evidence-only',
1800
- score: clampPatchScore(20 + Math.min(0, semanticEvidence.scoreAdjustment)),
1801
- reasons: uniqueStrings(['no patch to apply', ...semanticEvidence.reasons])
1802
- };
1803
- }
1804
- if (input.bundle.staleAgainstHead || input.bundle.disposition === 'stale-against-head') {
1805
- return { ...base, status: 'stale', score: 0, reasons: uniqueStrings(['stale-against-head', ...semanticEvidence.reasons]) };
1806
- }
1807
- const workspacePath = await createScoreWorkspace(input.cwd, input.bundle.jobId, input.input);
1808
- try {
1809
- const check = await runLoggedProcess('git', ['apply', '--check', patchPath], workspacePath);
1810
- commands.push(check);
1811
- if (check.status !== 0) {
1812
- return { ...base, workspacePath, status: 'conflict', score: 0, reasons: uniqueStrings(['git apply --check failed', ...semanticEvidence.reasons]) };
1813
- }
1814
- const apply = await runLoggedProcess('git', ['apply', patchPath], workspacePath);
1815
- commands.push(apply);
1816
- if (apply.status !== 0)
1817
- return { ...base, workspacePath, status: 'conflict', score: 0, reasons: uniqueStrings(['git apply failed', ...semanticEvidence.reasons]) };
1818
- const gates = scoreCommands(input.bundle, input.input);
1819
- for (const gate of gates) {
1820
- const run = await runLoggedProcess(gate.command, gate.args, gate.cwd ? path.resolve(workspacePath, gate.cwd) : workspacePath);
1821
- commands.push(run);
1822
- if (run.status !== 0 && gate.required !== false) {
1823
- return {
1824
- ...base,
1825
- workspacePath,
1826
- status: 'test-fail',
1827
- score: clampPatchScore(10 + Math.min(0, semanticEvidence.scoreAdjustment)),
1828
- reasons: uniqueStrings([`gate failed: ${gate.name}`, ...semanticEvidence.reasons])
1829
- };
1830
- }
1831
- }
1832
- const clean = input.bundle.disposition === 'auto-mergeable' && input.bundle.autoMergeable && semanticEvidence.cleanEligible;
1833
- const reasons = uniqueStrings([
1834
- ...(input.bundle.disposition === 'auto-mergeable' && input.bundle.autoMergeable ? [] : ['patch applies but bundle is not auto-mergeable']),
1835
- ...semanticEvidence.reasons
1836
- ]);
1837
- return {
1838
- ...base,
1839
- workspacePath,
1840
- status: clean ? 'accepted-clean' : 'accepted-needs-port',
1841
- score: clampPatchScore((clean ? 100 : 70) + semanticEvidence.scoreAdjustment),
1842
- reasons
1843
- };
1844
- }
1845
- finally {
1846
- if (!input.input.keepWorkspaces)
1847
- await fs.rm(workspacePath, { recursive: true, force: true }).catch(() => { });
1848
- }
1849
- }
1850
- function summarizePatchScoreSemanticEvidence(bundle) {
1851
- const summary = semanticImportSummaryFromBundle(bundle);
1852
- const changed = bundle.changedPaths.length > 0;
1853
- const reasons = [];
1854
- let scoreAdjustment = 0;
1855
- let cleanEligible = true;
1856
- if (!summary) {
1857
- if (changed) {
1858
- reasons.push('missing semantic import sidecar');
1859
- scoreAdjustment -= 10;
1860
- cleanEligible = false;
1861
- }
1862
- return {
1863
- present: false,
1864
- total: 0,
1865
- imported: 0,
1866
- errors: 0,
1867
- sourceMapMappings: 0,
1868
- semanticSymbols: 0,
1869
- ownershipRegions: 0,
1870
- patchHints: 0,
1871
- universalAstLayers: 0,
1872
- universalAstLayerNames: [],
1873
- proofSpecObligations: 0,
1874
- proofSpecFailedObligations: 0,
1875
- paradigmSemanticsRecords: 0,
1876
- paradigmSemanticsGroups: 0,
1877
- paradigmSemanticsLoweringRecords: 0,
1878
- readiness: {},
1879
- lossesBySeverity: {},
1880
- scoreAdjustment,
1881
- cleanEligible,
1882
- reasons
1883
- };
1884
- }
1885
- const readiness = numberRecord(summary.readiness);
1886
- const lossesBySeverity = numberRecord(summary.lossesBySeverity);
1887
- const total = nonNegativeNumber(summary.total);
1888
- const imported = nonNegativeNumber(summary.imported);
1889
- const selected = nonNegativeNumber(summary.selected);
1890
- const errors = nonNegativeNumber(summary.errors);
1891
- const sourceMapMappings = nonNegativeNumber(summary.sourceMapMappingCount);
1892
- const semanticSymbols = nonNegativeNumber(summary.semanticIndex?.symbols);
1893
- const ownershipRegions = nonNegativeNumber(summary.semanticSidecars?.ownershipRegions);
1894
- const patchHints = nonNegativeNumber(summary.semanticSidecars?.patchHints);
1895
- const universalAstLayerSummary = semanticImportUniversalAstLayerSummary(summary);
1896
- const universalAstLayers = universalAstLayerSummary.total;
1897
- const universalAstLayerNames = universalAstLayerSummary.names;
1898
- const proofSpec = semanticImportProofSpecSummary(summary);
1899
- const paradigmSemantics = semanticImportParadigmSemanticsSummary(summary);
1900
- const errorLosses = nonNegativeNumber(lossesBySeverity.error);
1901
- const warningLosses = nonNegativeNumber(lossesBySeverity.warning);
1902
- const blocked = nonNegativeNumber(readiness.blocked);
1903
- const needsReview = nonNegativeNumber(readiness['needs-review']);
1904
- if (errors > 0) {
1905
- reasons.push(`semantic import errors: ${errors}`);
1906
- scoreAdjustment -= 25;
1907
- cleanEligible = false;
1908
- }
1909
- if (errorLosses > 0) {
1910
- reasons.push(`semantic error losses: ${errorLosses}`);
1911
- scoreAdjustment -= 25;
1912
- cleanEligible = false;
1913
- }
1914
- if (blocked > 0) {
1915
- reasons.push(`blocked semantic imports: ${blocked}`);
1916
- scoreAdjustment -= 20;
1917
- cleanEligible = false;
1918
- }
1919
- if (total > 0 && imported === 0) {
1920
- reasons.push('semantic sidecar imported no files');
1921
- scoreAdjustment -= 15;
1922
- cleanEligible = false;
1923
- }
1924
- if (selected > 0 && semanticSymbols === 0) {
1925
- reasons.push('semantic sidecar has no symbols');
1926
- scoreAdjustment -= 10;
1927
- cleanEligible = false;
1928
- }
1929
- if (selected > 0 && ownershipRegions === 0) {
1930
- reasons.push('semantic sidecar has no ownership regions');
1931
- scoreAdjustment -= 10;
1932
- cleanEligible = false;
1933
- }
1934
- if (selected > 0 && sourceMapMappings === 0) {
1935
- reasons.push('semantic sidecar has no source-map mappings');
1936
- scoreAdjustment -= 5;
1937
- cleanEligible = false;
1938
- }
1939
- if (selected > 0 && universalAstLayers === 0) {
1940
- reasons.push('semantic sidecar has no universal AST layers');
1941
- scoreAdjustment -= 5;
1942
- cleanEligible = false;
1943
- }
1944
- if (warningLosses > 0 || needsReview > 0) {
1945
- reasons.push('semantic evidence needs review');
1946
- scoreAdjustment -= Math.min(10, warningLosses + needsReview);
1947
- cleanEligible = false;
1948
- }
1949
- if (proofSpec.failed > 0) {
1950
- reasons.push(`failed proof obligations: ${proofSpec.failed}`);
1951
- scoreAdjustment -= 30;
1952
- cleanEligible = false;
1953
- }
1954
- if (proofSpec.stale > 0) {
1955
- reasons.push(`stale proof obligations: ${proofSpec.stale}`);
1956
- scoreAdjustment -= 20;
1957
- cleanEligible = false;
1958
- }
1959
- if (proofSpec.open > 0 || proofSpec.unknown > 0) {
1960
- reasons.push('proof evidence needs review');
1961
- scoreAdjustment -= Math.min(10, proofSpec.open + proofSpec.unknown);
1962
- cleanEligible = false;
1963
- }
1964
- if (sourceMapMappings > 0 && semanticSymbols > 0 && ownershipRegions > 0 && universalAstLayers > 0) {
1965
- scoreAdjustment += 10;
1966
- }
1967
- if (patchHints > 0)
1968
- scoreAdjustment += 5;
1969
- if (proofSpec.discharged > 0 && proofSpec.failed === 0 && proofSpec.stale === 0 && proofSpec.open === 0 && proofSpec.unknown === 0) {
1970
- scoreAdjustment += 5;
1971
- }
1972
- if (paradigmSemantics.total > 0) {
1973
- scoreAdjustment += 3;
1974
- }
1975
- return {
1976
- present: true,
1977
- total,
1978
- imported,
1979
- errors,
1980
- sourceMapMappings,
1981
- semanticSymbols,
1982
- ownershipRegions,
1983
- patchHints,
1984
- universalAstLayers,
1985
- universalAstLayerNames,
1986
- proofSpecObligations: proofSpec.obligations,
1987
- proofSpecFailedObligations: proofSpec.failed,
1988
- paradigmSemanticsRecords: paradigmSemantics.total,
1989
- paradigmSemanticsGroups: paradigmSemantics.groups.length,
1990
- paradigmSemanticsLoweringRecords: paradigmSemantics.loweringRecords,
1991
- readiness,
1992
- lossesBySeverity,
1993
- scoreAdjustment: Math.max(-60, Math.min(15, scoreAdjustment)),
1994
- cleanEligible,
1995
- reasons: uniqueStrings(reasons)
1996
- };
1997
- }
1998
- function summarizeCodexSemanticImportQuality(summary, expected = false) {
1999
- const selected = nonNegativeNumber(summary?.selected);
2000
- const eligible = nonNegativeNumber(summary?.eligible);
2001
- const imported = nonNegativeNumber(summary?.imported);
2002
- const symbols = nonNegativeNumber(summary?.semanticIndex?.symbols);
2003
- const ownershipRegions = nonNegativeNumber(summary?.semanticSidecars?.ownershipRegions);
2004
- const patchHints = nonNegativeNumber(summary?.semanticSidecars?.patchHints);
2005
- const sourceMapMappings = nonNegativeNumber(summary?.sourceMapMappingCount);
2006
- const universalAstLayerSummary = semanticImportUniversalAstLayerSummary(summary);
2007
- const universalAstLayers = universalAstLayerSummary.total;
2008
- const universalAstLayerNames = universalAstLayerSummary.names;
2009
- const proofSpec = semanticImportProofSpecSummary(summary);
2010
- const paradigmSemantics = semanticImportParadigmSemanticsSummary(summary);
2011
- const present = !!summary;
2012
- const empty = present && (nonNegativeNumber(summary?.total) === 0 || selected === 0 && eligible === 0 && imported === 0 && symbols === 0);
2013
- const warnings = [];
2014
- if (expected && !present)
2015
- warnings.push('semantic import expected but missing');
2016
- if (expected && empty)
2017
- warnings.push('semantic import expected but empty');
2018
- if (present && imported === 0)
2019
- warnings.push('semantic import imported no files');
2020
- if (present && selected > 0 && symbols === 0)
2021
- warnings.push('semantic import has no symbols');
2022
- if (present && selected > 0 && ownershipRegions === 0)
2023
- warnings.push('semantic import has no ownership regions');
2024
- if (present && selected > 0 && sourceMapMappings === 0)
2025
- warnings.push('semantic import has no source-map mappings');
2026
- if (present && selected > 0 && universalAstLayers === 0)
2027
- warnings.push('semantic import has no universal AST layers');
2028
- if (present && proofSpec.failed > 0)
2029
- warnings.push('semantic import has failed proof obligations');
2030
- if (present && proofSpec.stale > 0)
2031
- warnings.push('semantic import has stale proof obligations');
2032
- return {
2033
- expected,
2034
- present,
2035
- empty,
2036
- selected,
2037
- eligible,
2038
- imported,
2039
- symbols,
2040
- ownershipRegions,
2041
- patchHints,
2042
- sourceMapMappings,
2043
- universalAstLayers,
2044
- universalAstLayerNames,
2045
- proofSpecObligations: proofSpec.obligations,
2046
- proofSpecFailedObligations: proofSpec.failed,
2047
- paradigmSemanticsRecords: paradigmSemantics.total,
2048
- paradigmSemanticsGroups: paradigmSemantics.groups.length,
2049
- paradigmSemanticsLoweringRecords: paradigmSemantics.loweringRecords,
2050
- warnings: uniqueStrings(warnings)
2051
- };
2052
- }
2053
- function codexJobTraceSummary(job) {
2054
- if (!isObject(job) || !isObject(job.traceSummary))
2055
- return undefined;
2056
- return normalizeCodexTraceSummary(job.traceSummary);
2057
- }
2058
- function codexBundleTraceSummary(bundle) {
2059
- const traceShards = isObject(bundle) && Array.isArray(bundle.traceShards)
2060
- ? bundle.traceShards
2061
- : [];
2062
- return summarizeCodexTraceShards(traceShards);
2063
- }
2064
- function summarizeCodexTraceSummaries(summaries) {
2065
- return {
2066
- shardCount: summaries.reduce((sum, entry) => sum + entry.shardCount, 0),
2067
- rowWindowCount: summaries.reduce((sum, entry) => sum + entry.rowWindowCount, 0),
2068
- hypothesisCount: summaries.reduce((sum, entry) => sum + entry.hypothesisCount, 0),
2069
- executableOwnershipRegionCount: summaries.reduce((sum, entry) => sum + entry.executableOwnershipRegionCount, 0),
2070
- focusedTestCount: summaries.reduce((sum, entry) => sum + entry.focusedTestCount, 0),
2071
- referenceEvidenceCount: summaries.reduce((sum, entry) => sum + entry.referenceEvidenceCount, 0),
2072
- divergenceCount: summaries.reduce((sum, entry) => sum + entry.divergenceCount, 0),
2073
- openDivergenceCount: summaries.reduce((sum, entry) => sum + entry.openDivergenceCount, 0)
2074
- };
2075
- }
2076
- function normalizeCodexTraceSummary(input) {
2077
- return {
2078
- shardCount: nonNegativeNumber(input.shardCount),
2079
- rowWindowCount: nonNegativeNumber(input.rowWindowCount),
2080
- hypothesisCount: nonNegativeNumber(input.hypothesisCount),
2081
- executableOwnershipRegionCount: nonNegativeNumber(input.executableOwnershipRegionCount),
2082
- focusedTestCount: nonNegativeNumber(input.focusedTestCount),
2083
- referenceEvidenceCount: nonNegativeNumber(input.referenceEvidenceCount),
2084
- divergenceCount: nonNegativeNumber(input.divergenceCount),
2085
- openDivergenceCount: nonNegativeNumber(input.openDivergenceCount)
2086
- };
2087
- }
2088
- function summarizeCodexTraceShards(shards) {
2089
- return {
2090
- shardCount: shards.length,
2091
- rowWindowCount: shards.reduce((sum, shard) => sum + traceArrayLength(shard, 'rowWindows'), 0),
2092
- hypothesisCount: shards.reduce((sum, shard) => sum + traceArrayLength(shard, 'hypotheses'), 0),
2093
- executableOwnershipRegionCount: shards.reduce((sum, shard) => sum + traceArrayLength(shard, 'executableOwnershipRegions'), 0),
2094
- focusedTestCount: shards.reduce((sum, shard) => sum + traceArrayLength(shard, 'focusedTests'), 0),
2095
- referenceEvidenceCount: shards.reduce((sum, shard) => sum + traceArrayLength(shard, 'referenceEvidence'), 0),
2096
- divergenceCount: shards.filter((shard) => isObject(shard) && Boolean(shard.divergence)).length,
2097
- openDivergenceCount: shards.filter(traceShardHasOpenDivergence).length
2098
- };
2099
- }
2100
- function traceArrayLength(shard, key) {
2101
- return isObject(shard) && Array.isArray(shard[key]) ? shard[key].length : 0;
2102
- }
2103
- function traceShardHasOpenDivergence(shard) {
2104
- if (!isObject(shard))
2105
- return false;
2106
- const divergence = isObject(shard.divergence) ? shard.divergence : undefined;
2107
- return shard.status === 'failed'
2108
- || divergence?.status === 'failed'
2109
- || divergence?.severity === 'error'
2110
- || divergence?.severity === 'critical';
2111
- }
2112
- function semanticImportProofSpecSummary(summary) {
2113
- const input = isObject(summary) && isObject(summary.proofSpec) ? summary.proofSpec : undefined;
2114
- return input ? normalizeProofSpecSummary(input) : emptyProofSpecSummary();
2115
- }
2116
- function semanticImportUniversalAstLayerSummary(summary) {
2117
- const input = isObject(summary) && isObject(summary.universalAstLayers) ? summary.universalAstLayers : undefined;
2118
- const names = Array.isArray(input?.names)
2119
- ? uniqueStrings(input.names.map((entry) => String(entry)).filter(Boolean))
2120
- : [];
2121
- const ids = Array.isArray(input?.ids)
2122
- ? uniqueStrings(input.ids.map((entry) => String(entry)).filter(Boolean))
2123
- : [];
2124
- const byName = isObject(input?.byName) ? numberRecord(input.byName) : {};
2125
- const total = nonNegativeNumber(input?.total ?? ids.length);
2126
- return {
2127
- total,
2128
- names,
2129
- ids,
2130
- byName,
2131
- empty: input?.empty === true || total === 0
2132
- };
2133
- }
2134
- function semanticImportParadigmSemanticsSummary(summary) {
2135
- const input = isObject(summary) && isObject(summary.paradigmSemantics)
2136
- ? summary.paradigmSemantics
2137
- : undefined;
2138
- return input ? normalizeParadigmSemanticsSummary(input) : emptyParadigmSemanticsSummary();
2139
- }
2140
- function semanticImportSummaryFromBundle(bundle) {
2141
- const metadata = bundle.metadata;
2142
- return richerSemanticImportSummary(bundle.semanticImport, metadata?.semanticImport);
2143
- }
2144
- function richerSemanticImportSummary(first, second) {
2145
- if (!first)
2146
- return second;
2147
- if (!second)
2148
- return first;
2149
- return semanticImportSummaryRichness(second) > semanticImportSummaryRichness(first) ? second : first;
2150
- }
2151
- function semanticImportSummaryRichness(summary) {
2152
- if (!summary)
2153
- return 0;
2154
- return [
2155
- summary.total,
2156
- summary.selected,
2157
- summary.imported,
2158
- summary.sourceMapMappingCount,
2159
- summary.semanticIndex?.symbols,
2160
- summary.semanticSidecars?.ownershipRegions,
2161
- summary.semanticSidecars?.patchHints,
2162
- semanticImportUniversalAstLayerSummary(summary).total,
2163
- semanticImportProofSpecSummary(summary).total,
2164
- semanticImportParadigmSemanticsSummary(summary).total
2165
- ].reduce((sum, value) => sum + nonNegativeNumber(value), 0);
2166
- }
2167
- function semanticImportEnabled(input) {
2168
- if (input === true)
2169
- return true;
2170
- if (!input)
2171
- return false;
2172
- return input.enabled !== false;
2173
- }
2174
- function normalizeCompactLogOptions(input) {
2175
- if (input === false)
2176
- return { enabled: false };
2177
- if (input === true || input === undefined)
2178
- return { enabled: true, maxEventBytes: 1_000_000, maxStderrBytes: 256_000 };
2179
- return {
2180
- enabled: input.enabled ?? true,
2181
- maxEventBytes: positiveInteger(input.maxEventBytes, 1_000_000),
2182
- maxStderrBytes: positiveInteger(input.maxStderrBytes, 256_000)
2183
- };
2184
- }
2185
- function normalizeAdaptiveConcurrencyOptions(input, maxConcurrency) {
2186
- if (input === false || input === undefined) {
2187
- return { enabled: false, mode: 'balanced', minConcurrency: 1, maxConcurrency, writePlan: true };
2188
- }
2189
- if (input === true) {
2190
- return { enabled: true, mode: 'balanced', minConcurrency: 1, maxConcurrency, writePlan: true };
2191
- }
2192
- return {
2193
- enabled: input.enabled ?? true,
2194
- mode: input.mode ?? 'balanced',
2195
- minConcurrency: Math.max(1, Math.min(maxConcurrency, Math.floor(input.minConcurrency ?? 1))),
2196
- maxConcurrency: Math.max(1, Math.min(maxConcurrency, Math.floor(input.maxConcurrency ?? maxConcurrency))),
2197
- writePlan: input.writePlan ?? true
2198
- };
2199
- }
2200
- function createCodexAdaptiveObservations(results) {
2201
- const observations = [];
2202
- for (const result of results) {
2203
- const metadata = result.metadata && typeof result.metadata === 'object' ? result.metadata : {};
2204
- const logSummary = metadata.logSummary;
2205
- if (logSummary && (logSummary.eventBytesTruncated > 0 || logSummary.stderrBytesTruncated > 0 || logSummary.eventBytes > 1_000_000 || logSummary.stderrBytes > 256_000)) {
2206
- observations.push({
2207
- kind: 'log-noise',
2208
- severity: logSummary.eventBytesTruncated > 0 || logSummary.stderrBytesTruncated > 0 ? 'warning' : 'info',
2209
- jobId: result.jobId,
2210
- value: logSummary.eventBytes + logSummary.stderrBytes,
2211
- reason: 'worker output exceeded compact log threshold',
2212
- metadata: logSummary
2213
- });
2214
- }
2215
- if (result.mergeDisposition === 'stale-against-head') {
2216
- observations.push({ kind: 'stale-patch', severity: 'warning', jobId: result.jobId, reason: 'worker result is stale against head' });
2217
- }
2218
- if (result.mergeDisposition === 'discovery-only' || result.mergeReadiness === 'discovery-only') {
2219
- observations.push({ kind: 'discovery-only-output', severity: 'info', jobId: result.jobId, reason: 'worker produced discovery-only output' });
2220
- }
2221
- const semanticQuality = summarizeCodexSemanticImportQuality(result.semanticImport ?? metadata.semanticImport, false);
2222
- if (semanticQuality.present && semanticQuality.empty) {
2223
- observations.push({ kind: 'semantic-empty', severity: 'warning', jobId: result.jobId, reason: 'worker semantic sidecar is empty' });
2224
- }
2225
- else if (semanticQuality.present && semanticQuality.warnings.length > 0) {
2226
- observations.push({ kind: 'semantic-weak', severity: 'info', jobId: result.jobId, reasons: semanticQuality.warnings });
2227
- }
2228
- }
2229
- return observations;
2230
- }
2231
- function createEmptyCodexLogSummary(paths) {
2232
- return {
2233
- eventsPath: paths.eventsPath,
2234
- stderrPath: paths.stderrPath,
2235
- eventBytes: 0,
2236
- stderrBytes: 0,
2237
- eventBytesWritten: 0,
2238
- stderrBytesWritten: 0,
2239
- eventBytesTruncated: 0,
2240
- stderrBytesTruncated: 0
2241
- };
2242
- }
2243
- function positiveInteger(value, fallback) {
2244
- const number = Number(value);
2245
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
2246
- }
2247
- function firstNonEmptyLine(text) {
2248
- return text.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
2249
- }
2250
- function numberRecord(value) {
2251
- if (!value || typeof value !== 'object')
2252
- return {};
2253
- const result = {};
2254
- for (const [key, entry] of Object.entries(value)) {
2255
- result[key] = nonNegativeNumber(entry);
2256
- }
2257
- return result;
2258
- }
2259
- function nonNegativeNumber(value) {
2260
- const number = Number(value ?? 0);
2261
- return Number.isFinite(number) && number > 0 ? number : 0;
2262
- }
2263
- function clampPatchScore(score) {
2264
- if (!Number.isFinite(score))
2265
- return 0;
2266
- return Math.max(0, Math.min(100, Math.round(score)));
2267
- }
2268
- async function createScoreWorkspace(cwd, jobId, input) {
2269
- const workspacePath = await fs.mkdtemp(path.join(os.tmpdir(), `frontier-swarm-score-${slug(jobId)}-`));
2270
- const excludes = uniqueWorkspacePaths([
2271
- '.git',
2272
- 'node_modules',
2273
- 'dist',
2274
- 'coverage',
2275
- 'agent-runs',
2276
- '.frontier-framework',
2277
- ...(input.workspaceExcludes ?? [])
2278
- ]);
2279
- const includes = uniqueWorkspacePaths(input.workspaceIncludes ?? []);
2280
- if (includes.length) {
2281
- for (const include of includes)
2282
- await copyWorkspacePath(cwd, workspacePath, include, excludes);
2283
- }
2284
- else {
2285
- await fs.cp(cwd, workspacePath, {
2286
- recursive: true,
2287
- force: true,
2288
- filter: (source) => {
2289
- if (source === cwd)
2290
- return true;
2291
- const relative = path.relative(cwd, source).replace(/\\/g, '/');
2292
- if (!relative)
2293
- return true;
2294
- if (pathHasIgnoredSegment(relative, excludes))
2295
- return false;
2296
- return !excludes.some((entry) => relative === entry || relative.startsWith(entry.replace(/\/$/, '') + '/'));
2297
- }
2298
- });
2299
- }
2300
- return workspacePath;
2301
- }
2302
- function scoreCommands(bundle, input) {
2303
- const focused = normalizeScoreCommands(input.focusedCommands ?? []);
2304
- const global = bundle.changedPaths.some((file) => (input.globalGlobs ?? []).some((glob) => matchesGlob(file, glob)))
2305
- ? normalizeScoreCommands(input.globalCommands ?? [])
2306
- : [];
2307
- return [...focused, ...global];
2308
- }
2309
- function normalizeScoreCommands(input) {
2310
- return input.map((entry) => {
2311
- if (typeof entry === 'string')
2312
- return { name: entry, command: 'sh', args: ['-c', entry], required: true };
2313
- return {
2314
- name: entry.name,
2315
- command: entry.command,
2316
- args: [...entry.args],
2317
- required: entry.required,
2318
- ...(entry.cwd ? { cwd: entry.cwd } : {}),
2319
- ...(entry.metadata ? { metadata: entry.metadata } : {})
2320
- };
2321
- }).filter((entry) => entry.command.length > 0);
2322
- }
2323
- async function resolveApplyPatchPath(bundle, mergePath) {
2324
- const sibling = path.join(path.dirname(mergePath), 'changes.patch');
2325
- if (await pathExists(sibling))
2326
- return sibling;
2327
- const patchPath = resolveBundlePatchPath(bundle, mergePath);
2328
- if (patchPath && await pathExists(patchPath))
2329
- return patchPath;
2330
- return undefined;
2331
- }
2332
- async function runLoggedProcess(command, args, cwd) {
2333
- const result = await runProcess(command, args, { cwd, allowFailure: true });
2334
- return {
2335
- command: [command, ...args],
2336
- status: result.status,
2337
- stdoutTail: tail(result.stdout),
2338
- stderrTail: tail(result.stderr)
2339
- };
2340
- }
2341
- async function writeJsonAtomic(file, value) {
2342
- const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
2343
- await fs.writeFile(tmp, JSON.stringify(value, null, 2) + '\n');
2344
- await fs.rename(tmp, file);
2345
- }
2346
- async function gitDirty(cwd) {
2347
- const result = await runProcess('git', ['status', '--porcelain'], { cwd, allowFailure: true });
2348
- if (result.status !== 0)
2349
- return [];
2350
- return result.stdout.split(/\r?\n/).filter(Boolean).map((line) => line.slice(3));
2351
- }
2352
- async function copyWorkspacePath(cwd, workspacePath, include, excludes) {
2353
- const relative = normalizeWorkspacePath(include);
2354
- if (!relative)
2355
- return;
2356
- const from = path.resolve(cwd, relative);
2357
- const to = path.resolve(workspacePath, relative);
2358
- if (!await pathExists(from))
2359
- return;
2360
- await fs.mkdir(path.dirname(to), { recursive: true });
2361
- await fs.cp(from, to, {
2362
- recursive: true,
2363
- force: true,
2364
- filter: (source) => !isExcluded(cwd, source, excludes)
2365
- });
2366
- }
2367
- async function linkWorkspacePath(cwd, workspacePath, include) {
2368
- const relative = normalizeWorkspacePath(include);
2369
- if (!relative)
2370
- return;
2371
- const from = path.resolve(cwd, relative);
2372
- const to = path.resolve(workspacePath, relative);
2373
- if (!await pathExists(from) || await pathExists(to))
2374
- return;
2375
- await fs.mkdir(path.dirname(to), { recursive: true });
2376
- const stat = await fs.lstat(from);
2377
- await fs.symlink(from, to, stat.isDirectory() ? 'dir' : 'file').catch(() => { });
2378
- }
2379
- function shouldSnapshotWorkspaceChanges(plan, options) {
2380
- return options.collectGitStatus !== false && (plan.mode === 'copy' || plan.mode === 'snapshot');
2381
- }
2382
- function shouldSkipGitRepoCheck(input) {
2383
- const workspace = input.workspace;
2384
- if (!workspace)
2385
- return false;
2386
- if (workspace.skipGitRepoCheck !== undefined)
2387
- return workspace.skipGitRepoCheck;
2388
- return workspace.mode === 'copy' || workspace.mode === 'snapshot';
2389
- }
2390
- function assertGeneratedWorkspacePath(plan) {
2391
- const relative = path.relative(plan.guardRoot ?? plan.root, plan.path);
2392
- if (relative.startsWith('..') || path.isAbsolute(relative) || relative === '') {
2393
- throw new Error(`Refusing to replace workspace outside generated root: ${plan.path}`);
2394
- }
2395
- }
2396
- function readRawTask(job) {
2397
- const metadata = isObject(job.task.metadata) ? job.task.metadata : {};
2398
- return isObject(metadata.source) ? metadata.source : {};
2399
- }
2400
- function normalizeWorkspacePath(value) {
2401
- const clean = value.replace(/\\/g, '/').replace(/\/+$/, '');
2402
- if (!clean || clean.includes('\0') || clean.includes('*') || path.isAbsolute(clean))
2403
- return undefined;
2404
- const normalized = path.normalize(clean).replace(/\\/g, '/');
2405
- if (normalized === '.' || normalized.startsWith('..') || path.isAbsolute(normalized))
2406
- return undefined;
2407
- return normalized;
2408
- }
2409
- function uniqueWorkspacePaths(values) {
2410
- const out = [];
2411
- const seen = new Set();
2412
- for (const value of values) {
2413
- const normalized = normalizeWorkspacePath(value);
2414
- if (!normalized || seen.has(normalized))
2415
- continue;
2416
- seen.add(normalized);
2417
- out.push(normalized);
2418
- }
2419
- return out;
2420
- }
2421
- async function gitChangedPaths(cwd) {
2422
- const result = await runProcess('git', ['status', '--porcelain'], { cwd, allowFailure: true });
2423
- if (result.status !== 0)
2424
- return [];
2425
- return result.stdout.split(/\r?\n/).filter(Boolean).flatMap((line) => {
2426
- const value = line.slice(3);
2427
- return value.includes(' -> ') ? value.split(' -> ') : [value];
2428
- });
2429
- }
2430
- async function collectChangedPaths(cwd, baseline, plan) {
2431
- if (!baseline)
2432
- return filterWorkspaceChangedPaths(await gitChangedPaths(cwd), plan);
2433
- const after = await snapshotWorkspaceFiles(cwd);
2434
- return filterWorkspaceChangedPaths(diffWorkspaceFiles(baseline, after), plan);
2435
- }
2436
- async function writeCodexPatchFile(input) {
2437
- await fs.mkdir(path.dirname(input.paths.patchPath), { recursive: true });
2438
- const changedPaths = uniqueWorkspacePaths(input.changedPaths);
2439
- if (changedPaths.length === 0) {
2440
- await fs.writeFile(input.paths.patchPath, '');
2441
- return undefined;
2442
- }
2443
- const diff = input.workspacePlan.mode === 'current' || input.workspacePlan.mode === 'git-worktree'
2444
- ? await gitDiffPatch(input.workspace, changedPaths)
2445
- : await noIndexWorkspacePatch(input.sourceRoot, input.workspace, changedPaths);
2446
- await fs.writeFile(input.paths.patchPath, diff);
2447
- return diff.trim().length ? input.paths.patchPath : undefined;
2448
- }
2449
- async function gitDiffPatch(workspace, changedPaths) {
2450
- const result = await runProcess('git', ['diff', '--', ...changedPaths], { cwd: workspace, allowFailure: true });
2451
- return result.stdout;
2452
- }
2453
- async function noIndexWorkspacePatch(sourceRoot, workspace, changedPaths) {
2454
- const chunks = [];
2455
- for (const file of changedPaths) {
2456
- const source = path.join(sourceRoot, file);
2457
- const target = path.join(workspace, file);
2458
- const sourceExists = await pathExists(source);
2459
- const targetExists = await pathExists(target);
2460
- if (!sourceExists && !targetExists)
2461
- continue;
2462
- const left = sourceExists ? source : '/dev/null';
2463
- const right = targetExists ? target : '/dev/null';
2464
- const result = await runProcess('git', ['diff', '--no-index', '--', left, right], { cwd: sourceRoot, allowFailure: true });
2465
- if (result.stdout.trim())
2466
- chunks.push(result.stdout);
2467
- }
2468
- return chunks.join('\n');
2469
- }
2470
- function filterWorkspaceChangedPaths(paths, plan) {
2471
- const changedPaths = [];
2472
- const ignoredChangedPaths = [];
2473
- for (const file of uniqueWorkspacePaths(paths)) {
2474
- if (isIgnoredWorkspaceChangedPath(file, plan))
2475
- ignoredChangedPaths.push(file);
2476
- else
2477
- changedPaths.push(file);
2478
- }
2479
- return { changedPaths, ignoredChangedPaths };
2480
- }
2481
- function isIgnoredWorkspaceChangedPath(file, plan) {
2482
- if (plan.mode !== 'copy' && plan.mode !== 'snapshot')
2483
- return false;
2484
- if (pathHasIgnoredSegment(file, ['node_modules', 'dist', 'coverage', '.frontier-framework', 'agent-runs']))
2485
- return true;
2486
- const ignored = [
2487
- ...plan.excludes,
2488
- ...plan.artifactIncludes,
2489
- ...plan.linkPaths,
2490
- ...(plan.linkNodeModules ? ['node_modules'] : []),
2491
- 'agent-runs',
2492
- '.frontier-framework',
2493
- 'dist',
2494
- 'coverage'
2495
- ];
2496
- return ignored.some((entry) => file === entry || file.startsWith(entry.replace(/\/$/, '') + '/'));
2497
- }
2498
- function pathHasIgnoredSegment(file, segments) {
2499
- const parts = file.replace(/\\/g, '/').split('/').filter(Boolean);
2500
- return parts.some((part) => segments.includes(part));
2501
- }
2502
- async function snapshotWorkspaceFiles(root) {
2503
- const snapshot = new Map();
2504
- await walkWorkspaceFiles(root, root, snapshot);
2505
- return snapshot;
2506
- }
2507
- async function walkWorkspaceFiles(root, current, snapshot) {
2508
- let entries;
2509
- try {
2510
- entries = await fs.readdir(current);
2511
- }
2512
- catch {
2513
- return;
2514
- }
2515
- for (const entry of entries) {
2516
- const absolute = path.join(current, entry);
2517
- const relative = path.relative(root, absolute).replace(/\\/g, '/');
2518
- const stat = await fs.lstat(absolute).catch(() => undefined);
2519
- if (!stat)
2520
- continue;
2521
- if (stat.isSymbolicLink()) {
2522
- const target = await fs.readlink(absolute).catch(() => '');
2523
- snapshot.set(relative, `link:${target}`);
2524
- continue;
2525
- }
2526
- if (stat.isDirectory()) {
2527
- await walkWorkspaceFiles(root, absolute, snapshot);
2528
- continue;
2529
- }
2530
- if (stat.isFile())
2531
- snapshot.set(relative, `${stat.size}:${stat.mtimeMs}`);
2532
- }
2533
- }
2534
- function diffWorkspaceFiles(before, after) {
2535
- const changed = new Set();
2536
- for (const [file, marker] of after) {
2537
- if (before.get(file) !== marker)
2538
- changed.add(file);
2539
- }
2540
- for (const file of before.keys()) {
2541
- if (!after.has(file))
2542
- changed.add(file);
2543
- }
2544
- return Array.from(changed).sort();
2545
- }
2546
- async function runVerification(commands, cwd) {
2547
- const results = [];
2548
- for (const command of commands) {
2549
- const startedAt = Date.now();
2550
- const run = await runProcess(command.command, command.args, { cwd, allowFailure: true });
2551
- results.push({
2552
- name: command.name,
2553
- command: [command.command, ...command.args],
2554
- status: run.status,
2555
- durationMs: Date.now() - startedAt,
2556
- stdoutTail: tail(run.stdout),
2557
- stderrTail: tail(run.stderr),
2558
- required: command.required
2559
- });
2560
- if (run.status !== 0 && command.required)
2561
- break;
2562
- }
2563
- return results;
2564
- }
2565
- async function runScheduledJobPool(plan, input, worker) {
2566
- const concurrency = Math.max(1, Math.floor(input.concurrency));
2567
- const adaptiveOptions = normalizeAdaptiveConcurrencyOptions(input.adaptive, concurrency);
2568
- const results = [];
2569
- const active = new Map();
2570
- const leases = [];
2571
- const completed = new Set();
2572
- const resultByJob = new Map();
2573
- const adaptiveHistory = [];
2574
- let currentAdaptiveLimits;
2575
- while (resultByJob.size < plan.jobs.length) {
2576
- const run = createSwarmRun({ plan, status: 'running', results });
2577
- run.jobs = run.jobs.map((job) => active.has(job.id) ? { ...job, status: 'running' } : job);
2578
- const adaptivePlan = adaptiveOptions.enabled ? createSwarmAdaptiveLoadPlan({
2579
- plan,
2580
- run,
2581
- mode: adaptiveOptions.mode,
2582
- maxLimits: { maxReadyJobs: adaptiveOptions.maxConcurrency },
2583
- minLimits: { maxReadyJobs: adaptiveOptions.minConcurrency },
2584
- currentLimits: currentAdaptiveLimits ?? { maxReadyJobs: adaptiveOptions.maxConcurrency },
2585
- observations: createCodexAdaptiveObservations(results)
2586
- }) : undefined;
2587
- if (adaptivePlan) {
2588
- currentAdaptiveLimits = adaptivePlan.effectiveLimits;
2589
- adaptiveHistory.push(adaptivePlan);
2590
- if (adaptiveOptions.writePlan !== false && input.outDir) {
2591
- await writeJsonAtomic(path.join(input.outDir, 'adaptive-load.json'), {
2592
- latest: adaptivePlan,
2593
- history: adaptiveHistory.slice(-50)
2594
- }).catch(() => { });
2595
- }
2596
- await appendFileSwarmEvent(input.eventStream, {
2597
- type: 'swarm.adaptive-load',
2598
- runId: run.id,
2599
- data: {
2600
- mode: adaptivePlan.mode,
2601
- effectiveMaxReadyJobs: adaptivePlan.effectiveLimits.maxReadyJobs,
2602
- bottleneckCount: adaptivePlan.summary.bottleneckCount,
2603
- decisions: adaptivePlan.decisions.map((decision) => ({
2604
- action: decision.action,
2605
- target: decision.target,
2606
- key: decision.key,
2607
- previous: decision.previous,
2608
- next: decision.next,
2609
- reason: decision.reason
2610
- }))
2611
- }
2612
- });
2613
- }
2614
- const effectiveConcurrency = Math.max(1, Math.min(concurrency, adaptivePlan?.effectiveLimits.maxReadyJobs ?? concurrency));
2615
- const readyWindow = Math.max(0, effectiveConcurrency - active.size);
2616
- const schedule = createSwarmSchedule({
2617
- ...(adaptivePlan ? createSwarmScheduleInputFromAdaptiveLoadPlan(plan, adaptivePlan, { run }) : { plan, run }),
2618
- maxReadyJobs: readyWindow
2619
- });
2620
- const nextLeases = createSwarmLeases({
2621
- schedule,
2622
- workerId: 'frontier-swarm-codex',
2623
- count: readyWindow,
2624
- existingLeases: leases
2625
- });
2626
- for (const lease of nextLeases) {
2627
- const job = plan.jobs.find((entry) => entry.id === lease.jobId);
2628
- if (!job || active.has(job.id) || completed.has(job.id))
2629
- continue;
2630
- leases.push(lease);
2631
- active.set(job.id, worker(job, lease));
2632
- }
2633
- if (active.size === 0) {
2634
- for (const blocked of schedule.blocked) {
2635
- if (resultByJob.has(blocked.jobId))
2636
- continue;
2637
- const result = {
2638
- jobId: blocked.jobId,
2639
- status: 'blocked',
2640
- startedAt: Date.now(),
2641
- finishedAt: Date.now(),
2642
- error: blocked.reasons.join(', '),
2643
- metadata: { waitingFor: blocked.waitingFor, reasons: blocked.reasons }
2644
- };
2645
- results.push(result);
2646
- resultByJob.set(result.jobId, result);
2647
- }
2648
- break;
2649
- }
2650
- const settled = await Promise.race(Array.from(active.entries()).map(async ([jobId, promise]) => ({ jobId, result: await promise })));
2651
- active.delete(settled.jobId);
2652
- completed.add(settled.jobId);
2653
- results.push(settled.result);
2654
- resultByJob.set(settled.jobId, settled.result);
2655
- }
2656
- return plan.jobs.map((job) => resultByJob.get(job.id)).filter((result) => !!result);
2657
- }
2658
- async function runJobPool(jobs, concurrency, worker) {
2659
- const results = [];
2660
- const pending = jobs.map((job, index) => ({ job, index }));
2661
- const activeKeys = new Set();
2662
- let active = 0;
2663
- await new Promise((resolve) => {
2664
- const schedule = () => {
2665
- if (pending.length === 0 && active === 0)
2666
- resolve();
2667
- while (active < concurrency && pending.length > 0) {
2668
- const nextIndex = pending.findIndex((entry) => !activeKeys.has(entry.job.concurrencyKey));
2669
- if (nextIndex < 0)
2670
- return;
2671
- const [next] = pending.splice(nextIndex, 1);
2672
- const concurrencyKey = next.job.concurrencyKey;
2673
- active += 1;
2674
- activeKeys.add(concurrencyKey);
2675
- worker(next.job).then((result) => {
2676
- results[next.index] = result;
2677
- }).catch((error) => {
2678
- results[next.index] = { jobId: next.job.id, status: 'failed', error };
2679
- }).finally(() => {
2680
- active -= 1;
2681
- activeKeys.delete(concurrencyKey);
2682
- schedule();
2683
- });
2684
- }
2685
- };
2686
- schedule();
2687
- });
2688
- return results;
2689
- }
92
+ export { repairCodexWorkspacePackageLinks } from './workspace-link-repair.js';
2690
93
  function readCompute(value) {
2691
94
  if (Array.isArray(value) && value.length > 0)
2692
95
  return value;
@@ -2697,1110 +100,4 @@ function readCompute(value) {
2697
100
  reasoningEffort: FRONTIER_SWARM_CODEX_DEFAULT_REASONING_EFFORT
2698
101
  }];
2699
102
  }
2700
- function formatCommand(command) {
2701
- return [command.command, ...command.args].join(' ') + (command.required ? '' : ' (optional)');
2702
- }
2703
- function bullets(values) {
2704
- return values.length ? values.map((value) => `- ${value}`) : ['- none'];
2705
- }
2706
- function formatBudget(job) {
2707
- if (!job.budget)
2708
- return ['none'];
2709
- return [
2710
- job.budget.maxCostUsd === undefined ? undefined : `maxCostUsd=${job.budget.maxCostUsd}`,
2711
- job.budget.maxInputTokens === undefined ? undefined : `maxInputTokens=${job.budget.maxInputTokens}`,
2712
- job.budget.maxOutputTokens === undefined ? undefined : `maxOutputTokens=${job.budget.maxOutputTokens}`,
2713
- job.budget.maxDurationMs === undefined ? undefined : `maxDurationMs=${job.budget.maxDurationMs}`,
2714
- `maxRetries=${job.budget.maxRetries}`
2715
- ].filter((value) => !!value);
2716
- }
2717
- function formatResourceAllocation(allocation) {
2718
- const entries = [
2719
- allocation.capabilities.length ? `capabilities=${allocation.capabilities.join(',')}` : undefined,
2720
- Object.keys(allocation.resources).length ? `resources=${JSON.stringify(allocation.resources)}` : undefined,
2721
- allocation.browser ? `browser.required=${allocation.browser.required}` : undefined,
2722
- allocation.browser?.port ? `browser.port=${allocation.browser.port}` : undefined,
2723
- allocation.browser?.profileDir ? `browser.profileDir=${allocation.browser.profileDir}` : undefined,
2724
- allocation.browser?.headless === undefined ? undefined : `browser.headless=${allocation.browser.headless}`,
2725
- Object.keys(allocation.env).length ? `env=${Object.keys(allocation.env).sort().join(',')}` : undefined
2726
- ].filter((value) => !!value);
2727
- return entries.length ? entries : ['none'];
2728
- }
2729
- function resourceSlot(job, lease, count) {
2730
- if (count <= 1)
2731
- return 0;
2732
- const seed = lease ? lease.fencingToken - 1 : Number.parseInt(stableHash(job.id).slice(0, 8), 16);
2733
- return Math.abs(seed) % count;
2734
- }
2735
- function resolveBrowserProfileDir(job, profileDir, profileDirPrefix, cwd) {
2736
- const raw = profileDir ?? (profileDirPrefix ? path.join(profileDirPrefix, safePathSegment(job.id)) : undefined);
2737
- if (!raw)
2738
- return undefined;
2739
- return path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
2740
- }
2741
- function safePathSegment(value) {
2742
- return value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'job';
2743
- }
2744
- function normalizeSemanticImportOptions(input) {
2745
- if (input === false || input === undefined)
2746
- return undefined;
2747
- const options = input === true ? {} : input;
2748
- if (options.enabled === false)
2749
- return undefined;
2750
- return {
2751
- ...options,
2752
- enabled: true,
2753
- maxFiles: Math.max(0, Math.floor(options.maxFiles ?? 24)),
2754
- maxBytes: Math.max(0, Math.floor(options.maxBytes ?? 512 * 1024))
2755
- };
2756
- }
2757
- function selectSemanticImportPaths(changedPaths, options) {
2758
- const eligible = [];
2759
- for (const file of uniqueWorkspacePaths(changedPaths)) {
2760
- if (pathHasIgnoredSegment(file, ['node_modules', 'dist', 'coverage', 'agent-runs', '.frontier-framework']))
2761
- continue;
2762
- if (options.include?.length && !options.include.some((glob) => matchesGlob(file, glob)))
2763
- continue;
2764
- if (options.exclude?.some((glob) => matchesGlob(file, glob)))
2765
- continue;
2766
- const language = inferSemanticImportLanguage(file, options.languages);
2767
- if (!language)
2768
- continue;
2769
- eligible.push({ path: file, language });
2770
- }
2771
- const maxFiles = Math.max(0, options.maxFiles);
2772
- return {
2773
- selected: eligible.slice(0, maxFiles),
2774
- eligibleCount: eligible.length,
2775
- omittedCount: Math.max(0, eligible.length - maxFiles),
2776
- maxFiles
2777
- };
2778
- }
2779
- function semanticImportCandidatePaths(job, changedPaths) {
2780
- const concreteRefs = job.task.sourceRefs.concat(job.task.targetRefs).filter((file) => {
2781
- const normalized = normalizeWorkspacePath(file);
2782
- return normalized
2783
- && !normalized.includes('*')
2784
- && path.extname(normalized).length > 0;
2785
- });
2786
- return uniqueWorkspacePaths([...changedPaths, ...concreteRefs]);
2787
- }
2788
- function inferSemanticImportLanguage(file, overrides) {
2789
- const ext = path.extname(file).toLowerCase();
2790
- return overrides?.[file] ?? overrides?.[ext] ?? {
2791
- '.ts': 'typescript',
2792
- '.tsx': 'typescript',
2793
- '.js': 'javascript',
2794
- '.jsx': 'javascript',
2795
- '.mjs': 'javascript',
2796
- '.cjs': 'javascript',
2797
- '.rs': 'rust',
2798
- '.py': 'python',
2799
- '.c': 'c',
2800
- '.h': 'c',
2801
- '.cc': 'cpp',
2802
- '.cpp': 'cpp',
2803
- '.hpp': 'cpp',
2804
- '.hh': 'cpp',
2805
- '.go': 'go',
2806
- '.java': 'java',
2807
- '.kt': 'kotlin',
2808
- '.kts': 'kotlin',
2809
- '.swift': 'swift',
2810
- '.cs': 'csharp',
2811
- '.wasm': 'wasm',
2812
- '.wat': 'wasm',
2813
- '.php': 'php',
2814
- '.rb': 'ruby',
2815
- '.rake': 'ruby'
2816
- }[ext];
2817
- }
2818
- async function loadFrontierLangForSemanticImport() {
2819
- try {
2820
- const packageName = '@shapeshift-labs/frontier-lang';
2821
- const api = await import(__rewriteRelativeImportExtension(packageName));
2822
- if (typeof api.importNativeSource !== 'function' || typeof api.createSemanticMergeCandidateFromImport !== 'function') {
2823
- return { ok: false, error: 'frontier-lang missing importNativeSource/createSemanticMergeCandidateFromImport exports' };
2824
- }
2825
- return {
2826
- ok: true,
2827
- importNativeSource: api.importNativeSource,
2828
- createSemanticMergeCandidateFromImport: api.createSemanticMergeCandidateFromImport,
2829
- ...(typeof api.createSemanticImportSidecar === 'function' ? { createSemanticImportSidecar: api.createSemanticImportSidecar } : {}),
2830
- ...(typeof api.projectNativeImportToSource === 'function' ? { projectNativeImportToSource: api.projectNativeImportToSource } : {}),
2831
- ...(typeof api.compileNativeSource === 'function' ? { compileNativeSource: api.compileNativeSource } : {}),
2832
- ...(typeof api.hashUniversalAstEnvelope === 'function' ? { hashUniversalAstEnvelope: api.hashUniversalAstEnvelope } : {})
2833
- };
2834
- }
2835
- catch (error) {
2836
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
2837
- }
2838
- }
2839
- function createSemanticImportSidecar(job, records, selection) {
2840
- const semanticIndex = records.reduce((totals, record) => {
2841
- totals.documents += record.semanticIndex?.documents ?? 0;
2842
- totals.symbols += record.semanticIndex?.symbols ?? 0;
2843
- totals.occurrences += record.semanticIndex?.occurrences ?? 0;
2844
- totals.relations += record.semanticIndex?.relations ?? 0;
2845
- totals.facts += record.semanticIndex?.facts ?? 0;
2846
- return totals;
2847
- }, { documents: 0, symbols: 0, occurrences: 0, relations: 0, facts: 0 });
2848
- const semanticSidecars = records.reduce((totals, record) => {
2849
- const summary = record.semanticSidecar;
2850
- if (!summary)
2851
- return totals;
2852
- totals.total += 1;
2853
- totals.symbols += summary.symbols ?? 0;
2854
- totals.ownershipRegions += summary.ownershipRegions ?? 0;
2855
- totals.patchHints += summary.patchHints ?? 0;
2856
- if (summary.emptySemanticIndex)
2857
- totals.empty += 1;
2858
- return totals;
2859
- }, { total: 0, symbols: 0, ownershipRegions: 0, patchHints: 0, empty: 0 });
2860
- const universalAstLayers = summarizeSemanticImportUniversalAstLayers(records);
2861
- const paradigmSemantics = summarizeSemanticImportParadigmSemantics(records);
2862
- const sourceProjections = records.reduce((totals, record) => {
2863
- const summary = record.sourceProjection;
2864
- if (!summary)
2865
- return totals;
2866
- totals.total += 1;
2867
- if (summary.mode === 'preserved-source')
2868
- totals.preserved += 1;
2869
- if (summary.mode === 'native-source-stubs')
2870
- totals.stubs += 1;
2871
- if (summary.readiness === 'ready' || summary.readiness === 'ready-with-losses')
2872
- totals.ready += 1;
2873
- else if (summary.readiness === 'blocked')
2874
- totals.blocked += 1;
2875
- else
2876
- totals.needsReview += 1;
2877
- return totals;
2878
- }, { total: 0, preserved: 0, stubs: 0, ready: 0, needsReview: 0, blocked: 0 });
2879
- const nativeCompiles = records.reduce((totals, record) => {
2880
- const summary = record.nativeCompile;
2881
- if (!summary)
2882
- return totals;
2883
- totals.total += 1;
2884
- if (summary.ok)
2885
- totals.emitted += 1;
2886
- if (summary.outputMode === 'preserved-source')
2887
- totals.preserved += 1;
2888
- if (summary.outputMode === 'target-stubs')
2889
- totals.targetStubs += 1;
2890
- if (summary.readiness === 'ready' || summary.readiness === 'ready-with-losses')
2891
- totals.ready += 1;
2892
- else if (summary.readiness === 'blocked')
2893
- totals.blocked += 1;
2894
- else
2895
- totals.needsReview += 1;
2896
- return totals;
2897
- }, { total: 0, emitted: 0, preserved: 0, targetStubs: 0, ready: 0, needsReview: 0, blocked: 0 });
2898
- const proofSpec = summarizeSemanticImportProofSpec(records);
2899
- const lossesBySeverity = {};
2900
- const readiness = {};
2901
- for (const record of records) {
2902
- for (const loss of Array.isArray(record.losses) ? record.losses : []) {
2903
- const severity = String(loss?.severity ?? 'unknown');
2904
- lossesBySeverity[severity] = (lossesBySeverity[severity] ?? 0) + 1;
2905
- }
2906
- const candidate = record.mergeCandidate;
2907
- if (candidate?.readiness !== undefined) {
2908
- const key = String(candidate.readiness);
2909
- readiness[key] = (readiness[key] ?? 0) + 1;
2910
- }
2911
- }
2912
- return {
2913
- kind: FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_KIND,
2914
- version: FRONTIER_SWARM_CODEX_SEMANTIC_IMPORT_VERSION,
2915
- generatedAt: Date.now(),
2916
- jobId: job.id,
2917
- taskId: job.taskId,
2918
- records,
2919
- summary: {
2920
- total: records.length,
2921
- selected: selection?.selected.length ?? records.length,
2922
- eligible: selection?.eligibleCount ?? records.length,
2923
- omitted: selection?.omittedCount ?? 0,
2924
- maxFiles: selection?.maxFiles ?? records.length,
2925
- imported: records.filter((record) => record.status === 'imported').length,
2926
- skipped: records.filter((record) => record.status === 'skipped').length,
2927
- errors: records.filter((record) => record.status === 'error').length,
2928
- sourceMapCount: records.reduce((sum, record) => sum + (record.sourceMapCount ?? 0), 0),
2929
- sourceMapMappingCount: records.reduce((sum, record) => sum + (record.sourceMapMappingCount ?? 0), 0),
2930
- lossCount: records.reduce((sum, record) => sum + (record.lossCount ?? 0), 0),
2931
- lossesBySeverity,
2932
- semanticIndex,
2933
- semanticSidecars,
2934
- universalAstLayers,
2935
- proofSpec,
2936
- paradigmSemantics,
2937
- sourceProjections,
2938
- nativeCompiles,
2939
- readiness
2940
- }
2941
- };
2942
- }
2943
- function summarizeSemanticIndex(value) {
2944
- if (!value || typeof value !== 'object')
2945
- return undefined;
2946
- return {
2947
- documents: Array.isArray(value.documents) ? value.documents.length : 0,
2948
- symbols: Array.isArray(value.symbols) ? value.symbols.length : 0,
2949
- occurrences: Array.isArray(value.occurrences) ? value.occurrences.length : 0,
2950
- relations: Array.isArray(value.relations) ? value.relations.length : 0,
2951
- facts: Array.isArray(value.facts) ? value.facts.length : 0
2952
- };
2953
- }
2954
- function summarizeLangSemanticImportSidecar(value) {
2955
- if (!value || typeof value !== 'object')
2956
- return undefined;
2957
- return {
2958
- kind: value.kind,
2959
- id: value.id,
2960
- imports: value.summary?.imports,
2961
- symbols: value.summary?.symbols,
2962
- ownershipRegions: value.summary?.ownershipRegions,
2963
- sourceMapMappings: value.summary?.sourceMapMappings,
2964
- universalAstLayers: value.summary?.universalAstLayers ?? value.universalAstLayers?.total,
2965
- universalAstLayerNames: Array.isArray(value.summary?.universalAstLayerNames)
2966
- ? value.summary.universalAstLayerNames
2967
- : Array.isArray(value.universalAstLayers?.names)
2968
- ? value.universalAstLayers.names
2969
- : [],
2970
- proofSpec: summarizeProofSpec(undefined, value),
2971
- paradigmSemantics: summarizeParadigmSemantics(undefined, value),
2972
- readiness: value.summary?.readiness,
2973
- emptySemanticIndex: value.summary?.emptySemanticIndex,
2974
- patchHints: Array.isArray(value.patchHints) ? value.patchHints.length : 0,
2975
- sampleOwnershipRegions: Array.isArray(value.ownershipRegions)
2976
- ? value.ownershipRegions.slice(0, 12).map((region) => ({
2977
- id: region?.id,
2978
- key: region?.key,
2979
- sourcePath: region?.sourcePath,
2980
- symbolName: region?.symbolName,
2981
- symbolKind: region?.symbolKind,
2982
- sourceSpan: region?.sourceSpan,
2983
- precision: region?.precision
2984
- }))
2985
- : []
2986
- };
2987
- }
2988
- const paradigmSemanticsSummaryGroups = [
2989
- 'bindingScopes',
2990
- 'bindings',
2991
- 'patterns',
2992
- 'typeConstraints',
2993
- 'evaluationModels',
2994
- 'memoryLocations',
2995
- 'effectRegions',
2996
- 'controlRegions',
2997
- 'logicPrograms',
2998
- 'actorSystems',
2999
- 'stackEffects',
3000
- 'arrayShapes',
3001
- 'numericKernels',
3002
- 'dataflowNetworks',
3003
- 'clockModels',
3004
- 'objectModels',
3005
- 'macroExpansions',
3006
- 'reflectionBoundaries',
3007
- 'loweringRecords'
3008
- ];
3009
- function summarizeSemanticImportParadigmSemantics(records) {
3010
- const summary = emptyParadigmSemanticsSummary();
3011
- for (const record of records) {
3012
- mergeParadigmSemanticsSummary(summary, record.paradigmSemantics);
3013
- }
3014
- summary.empty = summary.total === 0;
3015
- return summary;
3016
- }
3017
- function summarizeParadigmSemantics(paradigmSemantics, semanticSidecar) {
3018
- const sidecarSummary = semanticSidecar?.paradigmSemantics ?? semanticSidecar?.summary?.paradigmSemantics;
3019
- if (sidecarSummary && typeof sidecarSummary === 'object' && hasParadigmSemanticsSummaryShape(sidecarSummary)) {
3020
- return normalizeParadigmSemanticsSummary(sidecarSummary);
3021
- }
3022
- const raw = paradigmSemantics && typeof paradigmSemantics === 'object' ? paradigmSemantics : {};
3023
- const summary = emptyParadigmSemanticsSummary();
3024
- summary.ids = uniqueStrings([raw.id].filter(Boolean).map(String));
3025
- for (const group of paradigmSemanticsSummaryGroups) {
3026
- const records = Array.isArray(raw[group]) ? raw[group] : [];
3027
- summary[group] += records.length;
3028
- summary.total += records.length;
3029
- if (records.length > 0)
3030
- summary.byGroup[group] = (summary.byGroup[group] ?? 0) + records.length;
3031
- for (const record of records) {
3032
- if (record?.id)
3033
- summary.ids.push(String(record.id));
3034
- if (record?.kind) {
3035
- const kind = String(record.kind);
3036
- summary.kinds.push(kind);
3037
- summary.byKind[kind] = (summary.byKind[kind] ?? 0) + 1;
3038
- }
3039
- }
3040
- }
3041
- summary.evidence = Array.isArray(raw.evidence) ? raw.evidence.length : 0;
3042
- summary.ids = uniqueStrings([
3043
- ...summary.ids,
3044
- ...(Array.isArray(raw.evidence) ? raw.evidence.map((record) => record?.id).filter(Boolean).map(String) : [])
3045
- ]);
3046
- summary.groups = uniqueStrings(Object.keys(summary.byGroup));
3047
- summary.kinds = uniqueStrings(summary.kinds);
3048
- fillParadigmSemanticsBooleans(summary);
3049
- summary.empty = summary.total === 0;
3050
- return summary;
3051
- }
3052
- function normalizeParadigmSemanticsSummary(input) {
3053
- const summary = emptyParadigmSemanticsSummary();
3054
- mergeParadigmSemanticsSummary(summary, input);
3055
- summary.empty = summary.total === 0;
3056
- return summary;
3057
- }
3058
- function hasParadigmSemanticsSummaryShape(value) {
3059
- return typeof value.total === 'number' ||
3060
- typeof value.loweringRecords === 'number' ||
3061
- typeof value.logicPrograms === 'number' ||
3062
- typeof value.stackEffects === 'number';
3063
- }
3064
- function emptyParadigmSemanticsSummary() {
3065
- return {
3066
- total: 0,
3067
- ids: [],
3068
- groups: [],
3069
- kinds: [],
3070
- evidence: 0,
3071
- bindingScopes: 0,
3072
- bindings: 0,
3073
- patterns: 0,
3074
- typeConstraints: 0,
3075
- evaluationModels: 0,
3076
- memoryLocations: 0,
3077
- effectRegions: 0,
3078
- controlRegions: 0,
3079
- logicPrograms: 0,
3080
- actorSystems: 0,
3081
- stackEffects: 0,
3082
- arrayShapes: 0,
3083
- numericKernels: 0,
3084
- dataflowNetworks: 0,
3085
- clockModels: 0,
3086
- objectModels: 0,
3087
- macroExpansions: 0,
3088
- reflectionBoundaries: 0,
3089
- loweringRecords: 0,
3090
- byGroup: {},
3091
- byKind: {},
3092
- hasRuntimeSemantics: false,
3093
- hasLogicSemantics: false,
3094
- hasStackSemantics: false,
3095
- hasArraySemantics: false,
3096
- hasMacroOrReflection: false,
3097
- hasLowering: false,
3098
- empty: true
3099
- };
3100
- }
3101
- function mergeParadigmSemanticsSummary(target, input) {
3102
- if (!input || typeof input !== 'object')
3103
- return;
3104
- const declaredTotal = nonNegativeNumber(input.total);
3105
- let groupedTotal = 0;
3106
- target.evidence += nonNegativeNumber(input.evidence);
3107
- for (const group of paradigmSemanticsSummaryGroups) {
3108
- const count = nonNegativeNumber(input[group]);
3109
- target[group] += count;
3110
- groupedTotal += count;
3111
- }
3112
- target.total += declaredTotal || groupedTotal;
3113
- target.ids = uniqueStrings([...target.ids, ...proofStringList(input.ids)]);
3114
- target.groups = uniqueStrings([...target.groups, ...proofStringList(input.groups)]);
3115
- target.kinds = uniqueStrings([...target.kinds, ...proofStringList(input.kinds)]);
3116
- for (const [group, count] of Object.entries(numberRecord(input.byGroup))) {
3117
- target.byGroup[group] = (target.byGroup[group] ?? 0) + count;
3118
- }
3119
- for (const [kind, count] of Object.entries(numberRecord(input.byKind))) {
3120
- target.byKind[kind] = (target.byKind[kind] ?? 0) + count;
3121
- }
3122
- target.hasRuntimeSemantics ||= input.hasRuntimeSemantics === true;
3123
- target.hasLogicSemantics ||= input.hasLogicSemantics === true;
3124
- target.hasStackSemantics ||= input.hasStackSemantics === true;
3125
- target.hasArraySemantics ||= input.hasArraySemantics === true;
3126
- target.hasMacroOrReflection ||= input.hasMacroOrReflection === true;
3127
- target.hasLowering ||= input.hasLowering === true;
3128
- fillParadigmSemanticsBooleans(target);
3129
- }
3130
- function fillParadigmSemanticsBooleans(summary) {
3131
- summary.hasRuntimeSemantics ||= summary.evaluationModels > 0 || summary.memoryLocations > 0 || summary.effectRegions > 0 || summary.controlRegions > 0 || summary.actorSystems > 0 || summary.clockModels > 0;
3132
- summary.hasLogicSemantics ||= summary.logicPrograms > 0;
3133
- summary.hasStackSemantics ||= summary.stackEffects > 0;
3134
- summary.hasArraySemantics ||= summary.arrayShapes > 0 || summary.numericKernels > 0;
3135
- summary.hasMacroOrReflection ||= summary.macroExpansions > 0 || summary.reflectionBoundaries > 0;
3136
- summary.hasLowering ||= summary.loweringRecords > 0;
3137
- }
3138
- function summarizeSemanticImportProofSpec(records) {
3139
- const summary = emptyProofSpecSummary();
3140
- for (const record of records) {
3141
- mergeProofSpecSummary(summary, record.proofSpec);
3142
- }
3143
- summary.empty = summary.total === 0;
3144
- return summary;
3145
- }
3146
- function summarizeProofSpec(proof, semanticSidecar) {
3147
- const sidecarProof = semanticSidecar?.proofSpec ?? semanticSidecar?.summary?.proofSpec;
3148
- if (sidecarProof && typeof sidecarProof === 'object' && hasProofSpecSummaryShape(sidecarProof)) {
3149
- return normalizeProofSpecSummary(sidecarProof);
3150
- }
3151
- const raw = proof && typeof proof === 'object' ? proof : {};
3152
- const contracts = Array.isArray(raw.contracts) ? raw.contracts : [];
3153
- const refinements = Array.isArray(raw.refinements) ? raw.refinements : [];
3154
- const invariants = Array.isArray(raw.invariants) ? raw.invariants : [];
3155
- const termination = Array.isArray(raw.termination) ? raw.termination : [];
3156
- const temporal = Array.isArray(raw.temporal) ? raw.temporal : [];
3157
- const obligations = Array.isArray(raw.obligations) ? raw.obligations : [];
3158
- const artifacts = Array.isArray(raw.artifacts) ? raw.artifacts : [];
3159
- const assumptions = Array.isArray(raw.assumptions) ? raw.assumptions : [];
3160
- const evidence = Array.isArray(raw.evidence) ? raw.evidence : [];
3161
- const allContracts = [...contracts, ...refinements, ...invariants, ...termination, ...temporal];
3162
- const byStatus = {};
3163
- const byContractKind = {};
3164
- const byArtifactKind = {};
3165
- for (const obligation of obligations) {
3166
- const status = String(obligation?.status ?? 'unknown');
3167
- byStatus[status] = (byStatus[status] ?? 0) + 1;
3168
- }
3169
- for (const contract of allContracts) {
3170
- const kind = String(contract?.kind ?? 'unknown');
3171
- byContractKind[kind] = (byContractKind[kind] ?? 0) + 1;
3172
- }
3173
- for (const artifact of artifacts) {
3174
- const kind = String(artifact?.kind ?? 'unknown');
3175
- byArtifactKind[kind] = (byArtifactKind[kind] ?? 0) + 1;
3176
- }
3177
- const total = allContracts.length + obligations.length + artifacts.length + assumptions.length;
3178
- return {
3179
- total,
3180
- ids: uniqueStrings([
3181
- raw.id,
3182
- ...allContracts.map((record) => record?.id),
3183
- ...obligations.map((record) => record?.id),
3184
- ...artifacts.map((record) => record?.id),
3185
- ...assumptions.map((record) => record?.id),
3186
- ...evidence.map((record) => record?.id)
3187
- ].filter(Boolean).map(String)),
3188
- contracts: contracts.length,
3189
- refinements: refinements.length,
3190
- invariants: invariants.length,
3191
- termination: termination.length,
3192
- temporal: temporal.length,
3193
- obligations: obligations.length,
3194
- artifacts: artifacts.length,
3195
- assumptions: assumptions.length,
3196
- evidence: evidence.length,
3197
- discharged: byStatus.discharged ?? 0,
3198
- failed: byStatus.failed ?? 0,
3199
- open: byStatus.open ?? 0,
3200
- unknown: byStatus.unknown ?? 0,
3201
- stale: byStatus.stale ?? 0,
3202
- assumed: byStatus.assumed ?? 0,
3203
- contractKinds: uniqueStrings(Object.keys(byContractKind)),
3204
- artifactKinds: uniqueStrings(Object.keys(byArtifactKind)),
3205
- byStatus,
3206
- byContractKind,
3207
- byArtifactKind,
3208
- empty: total === 0
3209
- };
3210
- }
3211
- function hasProofSpecSummaryShape(value) {
3212
- return typeof value.total === 'number' ||
3213
- typeof value.obligations === 'number' ||
3214
- typeof value.contracts === 'number' ||
3215
- typeof value.failed === 'number';
3216
- }
3217
- function normalizeProofSpecSummary(value) {
3218
- const summary = emptyProofSpecSummary();
3219
- mergeProofSpecSummary(summary, value);
3220
- summary.empty = summary.total === 0;
3221
- return summary;
3222
- }
3223
- function emptyProofSpecSummary() {
3224
- return {
3225
- total: 0,
3226
- ids: [],
3227
- contracts: 0,
3228
- refinements: 0,
3229
- invariants: 0,
3230
- termination: 0,
3231
- temporal: 0,
3232
- obligations: 0,
3233
- artifacts: 0,
3234
- assumptions: 0,
3235
- evidence: 0,
3236
- discharged: 0,
3237
- failed: 0,
3238
- open: 0,
3239
- unknown: 0,
3240
- stale: 0,
3241
- assumed: 0,
3242
- contractKinds: [],
3243
- artifactKinds: [],
3244
- byStatus: {},
3245
- byContractKind: {},
3246
- byArtifactKind: {},
3247
- empty: true
3248
- };
3249
- }
3250
- function mergeProofSpecSummary(target, input) {
3251
- if (!input || typeof input !== 'object')
3252
- return;
3253
- for (const key of ['total', 'contracts', 'refinements', 'invariants', 'termination', 'temporal', 'obligations', 'artifacts', 'assumptions', 'evidence', 'discharged', 'failed', 'open', 'unknown', 'stale', 'assumed']) {
3254
- target[key] += nonNegativeNumber(input[key]);
3255
- }
3256
- target.ids = uniqueStrings([...target.ids, ...proofStringList(input.ids)]);
3257
- target.contractKinds = uniqueStrings([...target.contractKinds, ...proofStringList(input.contractKinds)]);
3258
- target.artifactKinds = uniqueStrings([...target.artifactKinds, ...proofStringList(input.artifactKinds)]);
3259
- for (const [status, count] of Object.entries(numberRecord(input.byStatus))) {
3260
- target.byStatus[status] = (target.byStatus[status] ?? 0) + count;
3261
- }
3262
- for (const [kind, count] of Object.entries(numberRecord(input.byContractKind))) {
3263
- target.byContractKind[kind] = (target.byContractKind[kind] ?? 0) + count;
3264
- }
3265
- for (const [kind, count] of Object.entries(numberRecord(input.byArtifactKind))) {
3266
- target.byArtifactKind[kind] = (target.byArtifactKind[kind] ?? 0) + count;
3267
- }
3268
- }
3269
- function proofStringList(value) {
3270
- if (!Array.isArray(value))
3271
- return [];
3272
- return value.map((entry) => String(entry)).filter(Boolean);
3273
- }
3274
- function summarizeSemanticImportUniversalAstLayers(records) {
3275
- const byName = {};
3276
- const names = [];
3277
- const ids = [];
3278
- for (const record of records) {
3279
- for (const name of record.universalAstLayers?.names ?? []) {
3280
- names.push(name);
3281
- byName[name] = (byName[name] ?? 0) + 1;
3282
- }
3283
- ids.push(...(record.universalAstLayers?.ids ?? []));
3284
- }
3285
- const uniqueNames = uniqueStrings(names);
3286
- const uniqueIds = uniqueStrings(ids);
3287
- return {
3288
- total: records.reduce((sum, record) => sum + (record.universalAstLayers?.total ?? 0), 0),
3289
- names: uniqueNames,
3290
- ids: uniqueIds,
3291
- byName,
3292
- empty: uniqueIds.length === 0
3293
- };
3294
- }
3295
- function summarizeUniversalAstLayers(universalAst, semanticSidecar) {
3296
- const layers = collectUniversalAstLayerRecords(universalAst?.layers);
3297
- const sidecarNames = Array.isArray(semanticSidecar?.summary?.universalAstLayerNames)
3298
- ? semanticSidecar.summary.universalAstLayerNames
3299
- : Array.isArray(semanticSidecar?.universalAstLayers?.names)
3300
- ? semanticSidecar.universalAstLayers.names
3301
- : [];
3302
- const sidecarIds = Array.isArray(semanticSidecar?.universalAstLayers?.ids) ? semanticSidecar.universalAstLayers.ids : [];
3303
- const byName = {};
3304
- for (const layer of layers) {
3305
- if (!layer?.layer)
3306
- continue;
3307
- byName[String(layer.layer)] = (byName[String(layer.layer)] ?? 0) + 1;
3308
- }
3309
- for (const [name, count] of Object.entries(semanticSidecar?.universalAstLayers?.byName ?? {})) {
3310
- byName[name] = Math.max(byName[name] ?? 0, Number(count) || 0);
3311
- }
3312
- const names = uniqueStrings([
3313
- ...layers.map((layer) => String(layer?.layer ?? '')).filter(Boolean),
3314
- ...sidecarNames.map((name) => String(name)).filter(Boolean)
3315
- ]);
3316
- const ids = uniqueStrings([
3317
- ...layers.map((layer) => String(layer?.id ?? '')).filter(Boolean),
3318
- ...sidecarIds.map((id) => String(id)).filter(Boolean)
3319
- ]);
3320
- const total = Math.max(layers.length, Number(semanticSidecar?.universalAstLayers?.total ?? 0) || 0, ids.length);
3321
- return { total, names, ids, byName, empty: total === 0 };
3322
- }
3323
- function collectUniversalAstLayerRecords(layers) {
3324
- if (!layers)
3325
- return [];
3326
- if (Array.isArray(layers))
3327
- return layers.filter(Boolean);
3328
- if (typeof layers !== 'object')
3329
- return [];
3330
- return Object.values(layers).flatMap((value) => Array.isArray(value) ? value : [value]).filter(Boolean);
3331
- }
3332
- function summarizeNativeSourceProjection(value) {
3333
- if (!value || typeof value !== 'object')
3334
- return undefined;
3335
- return {
3336
- kind: value.kind,
3337
- id: value.id,
3338
- language: value.language,
3339
- sourcePath: value.sourcePath,
3340
- mode: value.mode,
3341
- outputHash: value.outputHash,
3342
- declarationCount: Array.isArray(value.declarations) ? value.declarations.length : 0,
3343
- lossCount: Array.isArray(value.losses) ? value.losses.length : 0,
3344
- readiness: value.readiness?.readiness,
3345
- sourceHashVerified: value.metadata?.sourceHashVerified,
3346
- exactSourceAvailable: value.metadata?.exactSourceAvailable
3347
- };
3348
- }
3349
- function summarizeNativeSourceCompile(value) {
3350
- if (!value || typeof value !== 'object')
3351
- return undefined;
3352
- return {
3353
- kind: value.kind,
3354
- id: value.id,
3355
- ok: value.ok,
3356
- language: value.language,
3357
- target: value.target,
3358
- sourcePath: value.sourcePath,
3359
- outputMode: value.outputMode,
3360
- outputHash: value.outputHash,
3361
- lossCount: Array.isArray(value.losses) ? value.losses.length : 0,
3362
- readiness: value.readiness?.readiness,
3363
- targetCoverage: value.targetCoverage ? {
3364
- target: value.targetCoverage.target,
3365
- lossClass: value.targetCoverage.lossClass,
3366
- supported: value.targetCoverage.supported,
3367
- readiness: value.targetCoverage.readiness
3368
- } : undefined
3369
- };
3370
- }
3371
- function summarizeSemanticLosses(value) {
3372
- if (!Array.isArray(value) || value.length === 0)
3373
- return undefined;
3374
- return value.slice(0, 12).map((loss) => ({
3375
- id: loss?.id,
3376
- severity: loss?.severity,
3377
- phase: loss?.phase,
3378
- kind: loss?.kind,
3379
- message: loss?.message,
3380
- nodeId: loss?.nodeId,
3381
- span: loss?.span
3382
- }));
3383
- }
3384
- function summarizeSemanticMergeCandidate(value) {
3385
- if (!value || typeof value !== 'object')
3386
- return undefined;
3387
- return {
3388
- kind: value.kind,
3389
- readiness: value.readiness,
3390
- touchedSymbols: Array.isArray(value.touchedSymbols) ? value.touchedSymbols.slice(0, 50) : [],
3391
- touchedSemanticNodes: Array.isArray(value.touchedSemanticNodes) ? value.touchedSemanticNodes.slice(0, 50) : [],
3392
- nativeSpans: Array.isArray(value.nativeSpans) ? value.nativeSpans.slice(0, 50) : [],
3393
- conflictKeys: Array.isArray(value.conflictKeys) ? value.conflictKeys.slice(0, 100) : [],
3394
- reasons: Array.isArray(value.reasons) ? value.reasons.slice(0, 50) : []
3395
- };
3396
- }
3397
- function uniqueStrings(values) {
3398
- const out = [];
3399
- const seen = new Set();
3400
- for (const value of values) {
3401
- const normalized = String(value).trim();
3402
- if (!normalized || seen.has(normalized))
3403
- continue;
3404
- seen.add(normalized);
3405
- out.push(normalized);
3406
- }
3407
- return out;
3408
- }
3409
- function arrayOfObjects(value) {
3410
- return Array.isArray(value) ? value.filter(isObject) : [];
3411
- }
3412
- function readStringArray(value) {
3413
- return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
3414
- }
3415
- function isObject(value) {
3416
- return !!value && typeof value === 'object' && !Array.isArray(value);
3417
- }
3418
- async function pathExists(file) {
3419
- try {
3420
- await fs.access(file);
3421
- return true;
3422
- }
3423
- catch {
3424
- return false;
3425
- }
3426
- }
3427
- async function readWorkspaceScopedDependencies(root, scope) {
3428
- const packageJson = await readJsonObject(path.join(root, 'package.json'));
3429
- const dependencies = new Map();
3430
- for (const section of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
3431
- const value = packageJson?.[section];
3432
- if (!isObject(value))
3433
- continue;
3434
- for (const [name, range] of Object.entries(value)) {
3435
- if (name === scope || name.startsWith(scope + '/'))
3436
- dependencies.set(name, typeof range === 'string' ? range : undefined);
3437
- }
3438
- }
3439
- return dependencies;
3440
- }
3441
- async function discoverLocalWorkspacePackages(packageRoots, scope) {
3442
- const packages = new Map();
3443
- for (const root of uniqueStrings(packageRoots)) {
3444
- await addLocalWorkspacePackage(packages, root, scope);
3445
- const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
3446
- for (const entry of entries) {
3447
- if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name === '.git')
3448
- continue;
3449
- const child = path.join(root, entry.name);
3450
- if (entry.name.startsWith('@')) {
3451
- const scopedEntries = await fs.readdir(child, { withFileTypes: true }).catch(() => []);
3452
- for (const scopedEntry of scopedEntries) {
3453
- if (scopedEntry.isDirectory())
3454
- await addLocalWorkspacePackage(packages, path.join(child, scopedEntry.name), scope);
3455
- }
3456
- }
3457
- else {
3458
- await addLocalWorkspacePackage(packages, child, scope);
3459
- }
3460
- }
3461
- }
3462
- return packages;
3463
- }
3464
- async function addLocalWorkspacePackage(packages, packageDir, scope) {
3465
- const packageJson = await readJsonObject(path.join(packageDir, 'package.json'));
3466
- const name = typeof packageJson?.name === 'string' ? packageJson.name : undefined;
3467
- if (!name || name !== scope && !name.startsWith(scope + '/'))
3468
- return;
3469
- if (!packages.has(name))
3470
- packages.set(name, path.resolve(packageDir));
3471
- }
3472
- async function readJsonObject(file) {
3473
- try {
3474
- const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
3475
- return isObject(parsed) ? parsed : undefined;
3476
- }
3477
- catch {
3478
- return undefined;
3479
- }
3480
- }
3481
- async function planOrRepairWorkspacePackageLink(input) {
3482
- const base = {
3483
- packageName: input.packageName,
3484
- dependencyRange: input.dependencyRange,
3485
- linkPath: input.linkPath,
3486
- targetPath: input.targetPath
3487
- };
3488
- const stat = await fs.lstat(input.linkPath).catch(() => undefined);
3489
- const relativeTarget = path.relative(path.dirname(input.linkPath), input.targetPath) || '.';
3490
- if (stat?.isSymbolicLink()) {
3491
- const currentTarget = path.resolve(path.dirname(input.linkPath), await fs.readlink(input.linkPath));
3492
- if (currentTarget === input.targetPath)
3493
- return { ...base, status: 'already-linked' };
3494
- if (!input.write)
3495
- return { ...base, status: 'planned', reason: 'existing symlink points at a different package' };
3496
- await fs.unlink(input.linkPath);
3497
- await fs.symlink(relativeTarget, input.linkPath, 'dir');
3498
- return { ...base, status: 'linked', reason: 'updated existing symlink' };
3499
- }
3500
- if (stat) {
3501
- if (!input.replace)
3502
- return { ...base, status: 'conflict', reason: 'existing node_modules entry is not a symlink' };
3503
- if (!input.write)
3504
- return { ...base, status: 'planned', reason: 'would replace existing node_modules entry' };
3505
- await fs.rm(input.linkPath, { recursive: true, force: true });
3506
- await fs.mkdir(path.dirname(input.linkPath), { recursive: true });
3507
- await fs.symlink(relativeTarget, input.linkPath, 'dir');
3508
- return { ...base, status: 'replaced', reason: 'replaced existing node_modules entry with a symlink' };
3509
- }
3510
- if (!input.write)
3511
- return { ...base, status: 'planned', reason: 'missing symlink' };
3512
- await fs.mkdir(path.dirname(input.linkPath), { recursive: true });
3513
- await fs.symlink(relativeTarget, input.linkPath, 'dir');
3514
- return { ...base, status: 'linked', reason: 'created symlink' };
3515
- }
3516
- async function resolvePidManifestPath(runPath) {
3517
- const absolute = path.resolve(runPath);
3518
- const stat = await fs.lstat(absolute).catch(() => undefined);
3519
- if (stat?.isDirectory())
3520
- return path.join(absolute, 'pids.json');
3521
- if (path.basename(absolute) === 'swarm-results.json')
3522
- return path.join(path.dirname(absolute), 'pids.json');
3523
- return absolute;
3524
- }
3525
- async function resolveRunDirectory(runPath) {
3526
- const absolute = path.resolve(runPath);
3527
- const stat = await fs.lstat(absolute).catch(() => undefined);
3528
- if (stat?.isDirectory())
3529
- return absolute;
3530
- if (path.basename(absolute) === 'swarm-results.json' || path.basename(absolute) === 'pids.json')
3531
- return path.dirname(absolute);
3532
- return path.dirname(absolute);
3533
- }
3534
- async function findFilesByName(root, name) {
3535
- const out = [];
3536
- async function walk(current) {
3537
- const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
3538
- for (const entry of entries) {
3539
- const absolute = path.join(current, entry.name);
3540
- if (entry.isDirectory()) {
3541
- if (entry.name === 'collected' || entry.name === 'node_modules' || entry.name === '.git')
3542
- continue;
3543
- await walk(absolute);
3544
- }
3545
- else if (entry.isFile() && entry.name === name) {
3546
- out.push(absolute);
3547
- }
3548
- }
3549
- }
3550
- await walk(root);
3551
- return out;
3552
- }
3553
- async function bundlePatchStaleness(bundle, mergePath, cwd) {
3554
- const patchPath = resolveBundlePatchPath(bundle, mergePath);
3555
- if (!patchPath || !await pathExists(patchPath))
3556
- return { stale: false, patchStatus: 'missing', reasons: ['missing patch'] };
3557
- const patch = await fs.readFile(patchPath, 'utf8').catch(() => '');
3558
- if (!patch.trim())
3559
- return { stale: false, patchStatus: 'missing', reasons: ['empty patch'] };
3560
- const result = await runProcess('git', ['apply', '--check', patchPath], { cwd, allowFailure: true });
3561
- if (result.status === 0)
3562
- return { stale: false, patchStatus: 'applies', reasons: ['patch applies to working tree'] };
3563
- const cached = await runProcess('git', ['apply', '--check', '--cached', patchPath], { cwd, allowFailure: true });
3564
- if (cached.status === 0) {
3565
- return {
3566
- stale: false,
3567
- patchStatus: 'dirty-workspace-conflict',
3568
- reasons: ['patch applies to index but not dirty working tree']
3569
- };
3570
- }
3571
- const baseStatus = await patchBaseHashStatus(patch, cwd);
3572
- if (!baseStatus.known) {
3573
- return {
3574
- stale: false,
3575
- patchStatus: 'needs-port',
3576
- reasons: ['patch does not expose comparable base hashes; coordinator review must port it', ...baseStatus.reasons]
3577
- };
3578
- }
3579
- if (baseStatus.known && baseStatus.mismatched === 0) {
3580
- return {
3581
- stale: false,
3582
- patchStatus: 'needs-port',
3583
- reasons: ['patch base hashes match HEAD but textual apply failed', ...baseStatus.reasons]
3584
- };
3585
- }
3586
- return {
3587
- stale: true,
3588
- patchStatus: 'stale',
3589
- reasons: uniqueStrings(['git apply --check failed', ...baseStatus.reasons, ...tail(result.stderr || result.stdout, 3)])
3590
- };
3591
- }
3592
- async function patchBaseHashStatus(patch, cwd) {
3593
- const entries = parsePatchBaseHashes(patch, cwd);
3594
- if (entries.length === 0)
3595
- return { known: false, mismatched: 0, reasons: ['no patch base hashes available'] };
3596
- let mismatched = 0;
3597
- const reasons = [];
3598
- for (const entry of entries) {
3599
- const head = await runProcess('git', ['rev-parse', `HEAD:${entry.path}`], { cwd, allowFailure: true });
3600
- if (head.status !== 0) {
3601
- mismatched += 1;
3602
- reasons.push(`missing HEAD blob for ${entry.path}`);
3603
- continue;
3604
- }
3605
- const headHash = head.stdout.trim();
3606
- if (!headHash.startsWith(entry.oldHash)) {
3607
- mismatched += 1;
3608
- reasons.push(`base hash mismatch for ${entry.path}`);
3609
- }
3610
- }
3611
- return { known: true, mismatched, reasons };
3612
- }
3613
- function parsePatchBaseHashes(patch, cwd) {
3614
- const lines = patch.split(/\r?\n/);
3615
- const entries = [];
3616
- let currentPath;
3617
- for (const line of lines) {
3618
- if (line.startsWith('diff --git ')) {
3619
- const parts = line.split(/\s+/);
3620
- currentPath = normalizePatchBasePath(parts[2], cwd) ?? normalizePatchBasePath(parts[3], cwd);
3621
- continue;
3622
- }
3623
- if (!currentPath || !line.startsWith('index '))
3624
- continue;
3625
- const match = /^index\s+([0-9a-f]+)\.\.([0-9a-f]+)/i.exec(line);
3626
- if (match?.[1] && match[1] !== '0000000')
3627
- entries.push({ path: currentPath, oldHash: match[1] });
3628
- }
3629
- return entries;
3630
- }
3631
- function normalizePatchBasePath(token, cwd) {
3632
- if (!token || token === '/dev/null')
3633
- return undefined;
3634
- let value = token;
3635
- if (value.startsWith('a/') || value.startsWith('b/'))
3636
- value = value.slice(2);
3637
- if (value === '/dev/null')
3638
- return undefined;
3639
- if (path.isAbsolute(value)) {
3640
- const relative = path.relative(cwd, value);
3641
- if (relative && !relative.startsWith('..') && !path.isAbsolute(relative))
3642
- return relative.replace(/\\/g, '/');
3643
- return undefined;
3644
- }
3645
- const rootedValue = path.join(path.parse(cwd).root, value);
3646
- const rootedRelative = path.relative(cwd, rootedValue);
3647
- if (rootedRelative && !rootedRelative.startsWith('..') && !path.isAbsolute(rootedRelative))
3648
- return rootedRelative.replace(/\\/g, '/');
3649
- return value.replace(/\\/g, '/');
3650
- }
3651
- function resolveBundlePatchPath(bundle, mergePath) {
3652
- if (!bundle.patchPath)
3653
- return undefined;
3654
- return path.isAbsolute(bundle.patchPath) ? bundle.patchPath : path.resolve(path.dirname(mergePath), bundle.patchPath);
3655
- }
3656
- function normalizeCollectedMergeBundle(value, mergePath) {
3657
- const input = typeof value === 'object' && value !== null ? value : {};
3658
- const jobId = typeof input.jobId === 'string' && input.jobId ? input.jobId : path.basename(path.dirname(mergePath));
3659
- const changedPaths = stringArray(input.changedPaths);
3660
- const status = typeof input.status === 'string' ? input.status : 'completed';
3661
- const autoMergeable = Boolean(input.autoMergeable);
3662
- const disposition = typeof input.disposition === 'string'
3663
- ? input.disposition
3664
- : autoMergeable ? 'auto-mergeable' : status === 'failed' ? 'rejected' : 'needs-port';
3665
- const bundle = {
3666
- kind: typeof input.kind === 'string' ? input.kind : FRONTIER_SWARM_MERGE_BUNDLE_KIND,
3667
- version: typeof input.version === 'number' ? input.version : FRONTIER_SWARM_MERGE_BUNDLE_VERSION,
3668
- id: typeof input.id === 'string' && input.id ? input.id : `swarm-merge-bundle:${jobId}`,
3669
- ...(typeof input.runId === 'string' ? { runId: input.runId } : {}),
3670
- ...(typeof input.planId === 'string' ? { planId: input.planId } : {}),
3671
- jobId,
3672
- ...(typeof input.taskId === 'string' ? { taskId: input.taskId } : {}),
3673
- ...(typeof input.lane === 'string' ? { lane: input.lane } : {}),
3674
- ...(typeof input.title === 'string' ? { title: input.title } : {}),
3675
- generatedAt: typeof input.generatedAt === 'number' ? input.generatedAt : Date.now(),
3676
- status,
3677
- mergeReadiness: typeof input.mergeReadiness === 'string'
3678
- ? input.mergeReadiness
3679
- : changedPaths.length ? 'patch-candidate' : 'discovery-only',
3680
- disposition,
3681
- riskLevel: typeof input.riskLevel === 'string' ? input.riskLevel : 'unknown',
3682
- autoMergeable,
3683
- changedPaths,
3684
- changedRegions: stringArray(input.changedRegions),
3685
- ownedFilesTouched: stringArray(input.ownedFilesTouched),
3686
- allowedWrites: stringArray(input.allowedWrites),
3687
- ownershipViolations: stringArray(input.ownershipViolations),
3688
- ...(typeof input.patchPath === 'string' ? { patchPath: input.patchPath } : {}),
3689
- ...(typeof input.patchHash === 'string' ? { patchHash: input.patchHash } : {}),
3690
- evidencePaths: stringArray(input.evidencePaths),
3691
- commandsPassed: Array.isArray(input.commandsPassed) ? input.commandsPassed : [],
3692
- commandsFailed: Array.isArray(input.commandsFailed) ? input.commandsFailed : [],
3693
- queueItemIds: stringArray(input.queueItemIds),
3694
- ...(typeof input.branchName === 'string' ? { branchName: input.branchName } : {}),
3695
- ...(typeof input.commit === 'string' ? { commit: input.commit } : {}),
3696
- staleAgainstHead: Boolean(input.staleAgainstHead),
3697
- reasons: stringArray(input.reasons),
3698
- ...(isObject(input.semanticImport) ? { semanticImport: input.semanticImport } : {}),
3699
- ...(isObject(input.metadata) ? { metadata: input.metadata } : {})
3700
- };
3701
- if (Array.isArray(input.traceShards)) {
3702
- bundle.traceShards = input.traceShards;
3703
- }
3704
- return bundle;
3705
- }
3706
- function mergeRecordScore(record) {
3707
- return (record.mergePath.includes('/evidence/') ? 100 : 0)
3708
- + record.bundle.changedPaths.length
3709
- + record.bundle.evidencePaths.length
3710
- + record.bundle.commandsPassed.length
3711
- + record.bundle.commandsFailed.length;
3712
- }
3713
- function stringArray(value) {
3714
- return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
3715
- }
3716
- function classifyCodexHandoffArtifact(file) {
3717
- const normalized = file.replace(/\\/g, '/').toLowerCase();
3718
- const name = path.basename(normalized);
3719
- if (name === 'last-message.md' || name === 'last.md')
3720
- return 'last-message';
3721
- if (name.endsWith('.patch') || name.endsWith('.diff'))
3722
- return 'patch';
3723
- if (normalized.includes('debug-handoff') || normalized.includes('/debug/') || name.includes('handoff'))
3724
- return 'debug-handoff';
3725
- if (name.includes('replay'))
3726
- return 'replay';
3727
- if (name.includes('watchpoint'))
3728
- return 'watchpoint';
3729
- if (name.includes('trace') || normalized.endsWith('.trace.jsonl'))
3730
- return 'trace';
3731
- if (name.includes('diagnostic') || name.includes('health') || name.includes('probe'))
3732
- return 'diagnostic';
3733
- if (name.endsWith('.log') || name.includes('codex-events') || name.includes('events.jsonl'))
3734
- return 'log';
3735
- if (name === 'evidence.json' || name === 'merge.json' || name === 'resource-allocation.json' || name === 'workspace-proof.json')
3736
- return 'evidence';
3737
- return undefined;
3738
- }
3739
- function classifyCodexCollectBucket(bundle, staleAgainstHead) {
3740
- if (staleAgainstHead || bundle.staleAgainstHead || bundle.disposition === 'stale-against-head')
3741
- return 'stale-against-head';
3742
- if (bundle.disposition === 'rejected' || bundle.disposition === 'blocked' || bundle.commandsFailed.length > 0 || bundle.status === 'failed') {
3743
- return 'failed-evidence';
3744
- }
3745
- if (bundle.disposition === 'auto-mergeable' && bundle.autoMergeable)
3746
- return 'ready-to-apply';
3747
- return 'needs-human-port';
3748
- }
3749
- async function readOptionalText(file) {
3750
- try {
3751
- return await fs.readFile(file, 'utf8');
3752
- }
3753
- catch {
3754
- return undefined;
3755
- }
3756
- }
3757
- function isExcluded(cwd, source, excludes) {
3758
- const relative = path.relative(cwd, source).replace(/\\/g, '/');
3759
- return excludes.some((exclude) => relative === exclude.replace(/\/$/, '') || relative.startsWith(exclude.replace(/\/$/, '') + '/'));
3760
- }
3761
- async function runProcess(command, args, options) {
3762
- return new Promise((resolve, reject) => {
3763
- const child = spawn(command, [...args], { cwd: options.cwd, stdio: ['ignore', 'pipe', 'pipe'] });
3764
- let stdout = '';
3765
- let stderr = '';
3766
- child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
3767
- child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
3768
- child.on('close', (status) => {
3769
- const result = { status: status ?? 1, stdout, stderr };
3770
- if (!options.allowFailure && result.status !== 0)
3771
- reject(new Error(stderr || stdout || `${command} failed`));
3772
- else
3773
- resolve(result);
3774
- });
3775
- child.on('error', (error) => {
3776
- if (options.allowFailure)
3777
- resolve({ status: 1, stdout, stderr: String(error) });
3778
- else
3779
- reject(error);
3780
- });
3781
- });
3782
- }
3783
- function tail(text, maxLines = 24) {
3784
- return text.trim().split(/\r?\n/).filter(Boolean).slice(-maxLines);
3785
- }
3786
- function stableHash(value) {
3787
- const text = stableStringify(value);
3788
- let hash = 2166136261;
3789
- for (let index = 0; index < text.length; index += 1) {
3790
- hash ^= text.charCodeAt(index);
3791
- hash = Math.imul(hash, 16777619);
3792
- }
3793
- return 'fnv1a32:' + (hash >>> 0).toString(16).padStart(8, '0');
3794
- }
3795
- function slug(value) {
3796
- return value.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'item';
3797
- }
3798
- function stableStringify(value) {
3799
- if (value === null || typeof value !== 'object')
3800
- return JSON.stringify(value);
3801
- if (Array.isArray(value))
3802
- return '[' + value.map(stableStringify).join(',') + ']';
3803
- const object = value;
3804
- return '{' + Object.keys(object).sort().map((key) => JSON.stringify(key) + ':' + stableStringify(object[key])).join(',') + '}';
3805
- }
3806
103
  //# sourceMappingURL=index.js.map