@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.
- package/README.md +1 -1
- package/dist/apply.d.ts +3 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/apply.js +127 -0
- package/dist/apply.js.map +1 -0
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/codex-events.d.ts +15 -0
- package/dist/codex-events.d.ts.map +1 -0
- package/dist/codex-events.js +108 -0
- package/dist/codex-events.js.map +1 -0
- package/dist/codex-evidence.d.ts +33 -0
- package/dist/codex-evidence.d.ts.map +1 -0
- package/dist/codex-evidence.js +143 -0
- package/dist/codex-evidence.js.map +1 -0
- package/dist/codex-executor.d.ts +5 -0
- package/dist/codex-executor.d.ts.map +1 -0
- package/dist/codex-executor.js +160 -0
- package/dist/codex-executor.js.map +1 -0
- package/dist/codex-prompt.d.ts +20 -0
- package/dist/codex-prompt.d.ts.map +1 -0
- package/dist/codex-prompt.js +238 -0
- package/dist/codex-prompt.js.map +1 -0
- package/dist/codex-run-scheduler.d.ts +9 -0
- package/dist/codex-run-scheduler.d.ts.map +1 -0
- package/dist/codex-run-scheduler.js +145 -0
- package/dist/codex-run-scheduler.js.map +1 -0
- package/dist/codex-run.d.ts +5 -0
- package/dist/codex-run.d.ts.map +1 -0
- package/dist/codex-run.js +278 -0
- package/dist/codex-run.js.map +1 -0
- package/dist/codex-workspace-changes.d.ts +28 -0
- package/dist/codex-workspace-changes.d.ts.map +1 -0
- package/dist/codex-workspace-changes.js +147 -0
- package/dist/codex-workspace-changes.js.map +1 -0
- package/dist/codex-workspace.d.ts +10 -0
- package/dist/codex-workspace.d.ts.map +1 -0
- package/dist/codex-workspace.js +210 -0
- package/dist/codex-workspace.js.map +1 -0
- package/dist/collect-bundles.d.ts +14 -0
- package/dist/collect-bundles.d.ts.map +1 -0
- package/dist/collect-bundles.js +173 -0
- package/dist/collect-bundles.js.map +1 -0
- package/dist/collect-evidence.d.ts +13 -0
- package/dist/collect-evidence.d.ts.map +1 -0
- package/dist/collect-evidence.js +131 -0
- package/dist/collect-evidence.js.map +1 -0
- package/dist/collect.d.ts +5 -0
- package/dist/collect.d.ts.map +1 -0
- package/dist/collect.js +189 -0
- package/dist/collect.js.map +1 -0
- package/dist/common.d.ts +37 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +191 -0
- package/dist/common.js.map +1 -0
- package/dist/constants.d.ts +25 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -0
- package/dist/dashboard.d.ts +9 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +80 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/handoff-artifacts.d.ts +4 -0
- package/dist/handoff-artifacts.d.ts.map +1 -0
- package/dist/handoff-artifacts.js +67 -0
- package/dist/handoff-artifacts.js.map +1 -0
- package/dist/index.d.ts +14 -843
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3717
- package/dist/index.js.map +1 -1
- package/dist/patch-score-semantic.d.ts +4 -0
- package/dist/patch-score-semantic.d.ts.map +1 -0
- package/dist/patch-score-semantic.js +154 -0
- package/dist/patch-score-semantic.js.map +1 -0
- package/dist/score.d.ts +3 -0
- package/dist/score.d.ts.map +1 -0
- package/dist/score.js +219 -0
- package/dist/score.js.map +1 -0
- package/dist/semantic-import-layers.d.ts +9 -0
- package/dist/semantic-import-layers.d.ts.map +1 -0
- package/dist/semantic-import-layers.js +143 -0
- package/dist/semantic-import-layers.js.map +1 -0
- package/dist/semantic-import-paradigm.d.ts +6 -0
- package/dist/semantic-import-paradigm.d.ts.map +1 -0
- package/dist/semantic-import-paradigm.js +159 -0
- package/dist/semantic-import-paradigm.js.map +1 -0
- package/dist/semantic-import-proof.d.ts +7 -0
- package/dist/semantic-import-proof.d.ts.map +1 -0
- package/dist/semantic-import-proof.js +142 -0
- package/dist/semantic-import-proof.js.map +1 -0
- package/dist/semantic-import-quality.d.ts +6 -0
- package/dist/semantic-import-quality.d.ts.map +1 -0
- package/dist/semantic-import-quality.js +108 -0
- package/dist/semantic-import-quality.js.map +1 -0
- package/dist/semantic-import-select.d.ts +36 -0
- package/dist/semantic-import-select.d.ts.map +1 -0
- package/dist/semantic-import-select.js +132 -0
- package/dist/semantic-import-select.js.map +1 -0
- package/dist/semantic-import-sidecar.d.ts +7 -0
- package/dist/semantic-import-sidecar.d.ts.map +1 -0
- package/dist/semantic-import-sidecar.js +169 -0
- package/dist/semantic-import-sidecar.js.map +1 -0
- package/dist/semantic-import.d.ts +13 -0
- package/dist/semantic-import.d.ts.map +1 -0
- package/dist/semantic-import.js +122 -0
- package/dist/semantic-import.js.map +1 -0
- package/dist/trace-summary.d.ts +6 -0
- package/dist/trace-summary.d.ts.map +1 -0
- package/dist/trace-summary.js +61 -0
- package/dist/trace-summary.js.map +1 -0
- package/dist/types-collection.d.ts +166 -0
- package/dist/types-collection.d.ts.map +1 -0
- package/dist/types-collection.js +2 -0
- package/dist/types-collection.js.map +1 -0
- package/dist/types-evidence.d.ts +158 -0
- package/dist/types-evidence.d.ts.map +1 -0
- package/dist/types-evidence.js +2 -0
- package/dist/types-evidence.js.map +1 -0
- package/dist/types-run.d.ts +161 -0
- package/dist/types-run.d.ts.map +1 -0
- package/dist/types-run.js +2 -0
- package/dist/types-run.js.map +1 -0
- package/dist/types-semantic.d.ts +192 -0
- package/dist/types-semantic.d.ts.map +1 -0
- package/dist/types-semantic.js +2 -0
- package/dist/types-semantic.js.map +1 -0
- package/dist/types-workspace.d.ts +122 -0
- package/dist/types-workspace.d.ts.map +1 -0
- package/dist/types-workspace.js +2 -0
- package/dist/types-workspace.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace-link-repair.d.ts +3 -0
- package/dist/workspace-link-repair.d.ts.map +1 -0
- package/dist/workspace-link-repair.js +164 -0
- package/dist/workspace-link-repair.js.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,51 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|