@shapeshift-labs/frontier-swarm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,622 @@
1
+ export const FRONTIER_SWARM_MANIFEST_KIND = 'frontier.swarm.manifest';
2
+ export const FRONTIER_SWARM_MANIFEST_VERSION = 1;
3
+ export const FRONTIER_SWARM_TASK_KIND = 'frontier.swarm.task';
4
+ export const FRONTIER_SWARM_TASK_VERSION = 1;
5
+ export const FRONTIER_SWARM_PLAN_KIND = 'frontier.swarm.plan';
6
+ export const FRONTIER_SWARM_PLAN_VERSION = 1;
7
+ export const FRONTIER_SWARM_RUN_KIND = 'frontier.swarm.run';
8
+ export const FRONTIER_SWARM_RUN_VERSION = 1;
9
+ export const FRONTIER_SWARM_EVENT_KIND = 'frontier.swarm.event';
10
+ export const FRONTIER_SWARM_EVENT_VERSION = 1;
11
+ export const FRONTIER_SWARM_PROOF_KIND = 'frontier.swarm.proof';
12
+ export const FRONTIER_SWARM_PROOF_VERSION = 1;
13
+ export const FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID = 'codex.gpt-5.5.xhigh';
14
+ export const FRONTIER_SWARM_DEFAULT_MODEL = 'gpt-5.5';
15
+ export const FRONTIER_SWARM_DEFAULT_REASONING_EFFORT = 'xhigh';
16
+ const DEFAULT_COMPLETED_STATUSES = ['completed', 'verified', 'done', 'verified-local-harness'];
17
+ export function defineSwarmManifest(input = {}) {
18
+ return createSwarmManifest(input);
19
+ }
20
+ export function createSwarmManifest(input = {}) {
21
+ const compute = normalizeComputeList(input.compute);
22
+ const defaultCompute = input.policy?.defaultCompute ?? compute[0]?.id ?? FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID;
23
+ const layers = (input.layers ?? []).map(normalizeLayer);
24
+ const lanes = (input.lanes ?? []).map(normalizeLane);
25
+ const policy = normalizePolicy(input.policy, defaultCompute);
26
+ return {
27
+ kind: FRONTIER_SWARM_MANIFEST_KIND,
28
+ version: FRONTIER_SWARM_MANIFEST_VERSION,
29
+ id: normalizeId(input.id ?? 'frontier-swarm', 'manifest id'),
30
+ title: input.title ?? titleFromId(input.id ?? 'frontier swarm'),
31
+ ...(input.description ? { description: input.description } : {}),
32
+ ...(input.package ? { package: input.package } : {}),
33
+ ...(input.feature ? { feature: input.feature } : {}),
34
+ ...(input.owner ? { owner: input.owner } : {}),
35
+ compute,
36
+ layers,
37
+ lanes,
38
+ policy,
39
+ resources: uniqueStrings(input.resources ?? []),
40
+ tags: uniqueStrings(input.tags ?? []),
41
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {}),
42
+ summary: {
43
+ computeCount: compute.length,
44
+ layerCount: layers.length,
45
+ laneCount: lanes.length
46
+ }
47
+ };
48
+ }
49
+ export function defineSwarmTasks(input = []) {
50
+ const raw = Array.isArray(input) ? input : (input.tasks ?? input.items ?? []);
51
+ return raw.map(normalizeTask);
52
+ }
53
+ export function compileSwarm(input) {
54
+ const manifest = isSwarmManifest(input) ? cloneJsonValue(input) : createSwarmManifest(input);
55
+ const computeById = new Map(manifest.compute.map((compute) => [compute.id, compute]));
56
+ const layersById = new Map(manifest.layers.map((layer) => [layer.id, layer]));
57
+ const lanesById = new Map(manifest.lanes.map((lane) => [lane.id, lane]));
58
+ return {
59
+ manifest,
60
+ computeById,
61
+ layersById,
62
+ lanesById,
63
+ validation: validateSwarmManifest(manifest)
64
+ };
65
+ }
66
+ export function validateSwarmManifest(input) {
67
+ const manifest = isSwarmManifest(input) ? input : createSwarmManifest(input);
68
+ const issues = [];
69
+ const computeIds = new Set();
70
+ const layerIds = new Set();
71
+ const laneIds = new Set();
72
+ for (const compute of manifest.compute) {
73
+ if (computeIds.has(compute.id))
74
+ addIssue(issues, 'duplicate-compute', 'error', `compute.${compute.id}`, `Duplicate compute id: ${compute.id}`);
75
+ computeIds.add(compute.id);
76
+ }
77
+ if (!computeIds.has(manifest.policy.defaultCompute)) {
78
+ addIssue(issues, 'missing-default-compute', 'error', 'policy.defaultCompute', `Default compute is not declared: ${manifest.policy.defaultCompute}`);
79
+ }
80
+ for (const layer of manifest.layers) {
81
+ if (layerIds.has(layer.id))
82
+ addIssue(issues, 'duplicate-layer', 'error', `layers.${layer.id}`, `Duplicate layer id: ${layer.id}`);
83
+ layerIds.add(layer.id);
84
+ }
85
+ for (const layer of manifest.layers) {
86
+ if (layer.parentId && !layerIds.has(layer.parentId)) {
87
+ addIssue(issues, 'missing-parent-layer', 'error', `layers.${layer.id}.parentId`, `Layer parent is not declared: ${layer.parentId}`);
88
+ }
89
+ for (const [childLayer, compute] of Object.entries(layer.childCompute)) {
90
+ if (childLayer !== '*' && !layerIds.has(childLayer)) {
91
+ addIssue(issues, 'missing-child-layer', 'error', `layers.${layer.id}.childCompute.${childLayer}`, `Child layer is not declared: ${childLayer}`);
92
+ }
93
+ if (!computeIds.has(compute)) {
94
+ addIssue(issues, 'missing-child-compute', 'error', `layers.${layer.id}.childCompute.${childLayer}`, `Child compute is not declared: ${compute}`);
95
+ }
96
+ }
97
+ for (const field of ['compute', 'defaultCompute']) {
98
+ const compute = layer[field];
99
+ if (compute && !computeIds.has(compute)) {
100
+ addIssue(issues, 'missing-layer-compute', 'error', `layers.${layer.id}.${field}`, `Layer compute is not declared: ${compute}`);
101
+ }
102
+ }
103
+ if (hasLayerCycle(layer.id, manifest.layers)) {
104
+ addIssue(issues, 'layer-cycle', 'error', `layers.${layer.id}`, `Layer parent chain contains a cycle at ${layer.id}`);
105
+ }
106
+ }
107
+ for (const lane of manifest.lanes) {
108
+ if (laneIds.has(lane.id))
109
+ addIssue(issues, 'duplicate-lane', 'error', `lanes.${lane.id}`, `Duplicate lane id: ${lane.id}`);
110
+ laneIds.add(lane.id);
111
+ if (lane.layer && !layerIds.has(lane.layer)) {
112
+ addIssue(issues, 'missing-lane-layer', 'error', `lanes.${lane.id}.layer`, `Lane layer is not declared: ${lane.layer}`);
113
+ }
114
+ if (lane.compute && !computeIds.has(lane.compute)) {
115
+ addIssue(issues, 'missing-lane-compute', 'error', `lanes.${lane.id}.compute`, `Lane compute is not declared: ${lane.compute}`);
116
+ }
117
+ }
118
+ return { valid: issues.every((issue) => issue.severity !== 'error'), issues };
119
+ }
120
+ export function createSwarmPlan(manifestInput, taskInput, options = {}) {
121
+ const compiled = compileSwarm(manifestInput);
122
+ const tasks = normalizeTaskList(taskInput);
123
+ const jobs = selectSwarmTasks(compiled.manifest, tasks, options).map((task) => createJob(compiled, task, options));
124
+ const id = options.id ?? 'swarm-plan:' + stableHash([compiled.manifest.id, jobs.map((job) => job.id), options]);
125
+ return {
126
+ kind: FRONTIER_SWARM_PLAN_KIND,
127
+ version: FRONTIER_SWARM_PLAN_VERSION,
128
+ id,
129
+ runId: options.runId ?? id.replace(/^swarm-plan:/, 'swarm-run:'),
130
+ manifestId: compiled.manifest.id,
131
+ createdAt: options.now ?? Date.now(),
132
+ filters: {
133
+ lanes: options.lanes ? [...options.lanes] : undefined,
134
+ layers: options.layers ? [...options.layers] : undefined,
135
+ statuses: options.statuses ? [...options.statuses] : undefined,
136
+ selectors: options.selectors ? [...options.selectors] : undefined,
137
+ includeCompleted: options.includeCompleted,
138
+ limit: options.limit,
139
+ compute: options.compute
140
+ },
141
+ validation: validateTasksForManifest(compiled, tasks),
142
+ jobs,
143
+ summary: summarizeJobs(jobs),
144
+ ...(toJsonObject(options.metadata) ? { metadata: toJsonObject(options.metadata) } : {})
145
+ };
146
+ }
147
+ export function createSwarmRun(input) {
148
+ const results = (input.results ?? []).map(normalizeResult);
149
+ const events = (input.events ?? []).map((event) => normalizeEvent({ ...event, runId: event.runId ?? input.id ?? input.plan.runId }));
150
+ const run = {
151
+ kind: FRONTIER_SWARM_RUN_KIND,
152
+ version: FRONTIER_SWARM_RUN_VERSION,
153
+ id: input.id ?? input.plan.runId,
154
+ planId: input.plan.id,
155
+ manifestId: input.plan.manifestId,
156
+ startedAt: input.startedAt ?? Date.now(),
157
+ status: input.status ?? 'planned',
158
+ jobs: input.plan.jobs.map((job) => cloneJsonValue(job)),
159
+ events,
160
+ results,
161
+ summary: summarizeRun(input.plan.jobs, results),
162
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
163
+ };
164
+ return run;
165
+ }
166
+ export function recordSwarmEvent(runInput, eventInput) {
167
+ const run = cloneJsonValue(runInput);
168
+ run.events = run.events.concat(normalizeEvent({ ...eventInput, runId: eventInput.runId ?? run.id }));
169
+ return run;
170
+ }
171
+ export function completeSwarmJob(runInput, resultInput) {
172
+ const run = cloneJsonValue(runInput);
173
+ const result = normalizeResult(resultInput);
174
+ const resultIndex = run.results.findIndex((entry) => entry.jobId === result.jobId);
175
+ if (resultIndex >= 0)
176
+ run.results[resultIndex] = result;
177
+ else
178
+ run.results.push(result);
179
+ run.jobs = run.jobs.map((job) => job.id === result.jobId ? { ...job, status: result.status } : job);
180
+ run.summary = summarizeRun(run.jobs, run.results);
181
+ if (run.results.length >= run.jobs.length && run.summary.failedCount === 0 && run.summary.blockedCount === 0) {
182
+ run.status = 'completed';
183
+ run.finishedAt = result.finishedAt ?? Date.now();
184
+ }
185
+ else if (run.summary.failedCount && run.summary.failedCount > 0) {
186
+ run.status = 'failed';
187
+ }
188
+ return run;
189
+ }
190
+ export function checkSwarmOwnership(job, changedPaths) {
191
+ const changed = uniqueStrings(changedPaths);
192
+ const violations = changed.filter((file) => !job.allowedWrites.some((glob) => matchesGlob(file, glob)));
193
+ return {
194
+ ok: violations.length === 0,
195
+ changedPaths: changed,
196
+ allowedWrites: [...job.allowedWrites],
197
+ violations
198
+ };
199
+ }
200
+ export function resolveSwarmCompute(manifestInput, taskInput) {
201
+ const compiled = compileSwarm(manifestInput);
202
+ const task = isSwarmTask(taskInput) ? taskInput : normalizeTask(taskInput);
203
+ return resolveTaskCompute(compiled, task);
204
+ }
205
+ export function createSwarmProof(input, options = {}) {
206
+ const manifestId = 'manifestId' in input ? input.manifestId : input.id;
207
+ const summary = input.summary;
208
+ const generatedAt = options.generatedAt ?? Date.now();
209
+ return {
210
+ kind: FRONTIER_SWARM_PROOF_KIND,
211
+ version: FRONTIER_SWARM_PROOF_VERSION,
212
+ id: 'swarm-proof:' + stableHash([manifestId, summary, generatedAt]),
213
+ manifestId,
214
+ generatedAt,
215
+ hash: stableHash([input, options.validation, options.metadata]),
216
+ summary,
217
+ ...(options.validation ? { validation: options.validation } : {}),
218
+ ...(toJsonObject(options.metadata) ? { metadata: toJsonObject(options.metadata) } : {})
219
+ };
220
+ }
221
+ export function encodeSwarmJsonl(records) {
222
+ return records.map((record) => JSON.stringify(record)).join('\n') + (records.length ? '\n' : '');
223
+ }
224
+ export function decodeSwarmJsonl(jsonl) {
225
+ return jsonl.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
226
+ }
227
+ export function matchesGlob(file, glob) {
228
+ const escaped = glob
229
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
230
+ .replace(/\*\*/g, '\u0000')
231
+ .replace(/\*/g, '[^/]*')
232
+ .replace(/\u0000/g, '.*');
233
+ return new RegExp('^' + escaped + '$').test(file);
234
+ }
235
+ function normalizeComputeList(input) {
236
+ const values = input && input.length > 0 ? input : [{
237
+ id: FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID,
238
+ kind: 'codex',
239
+ model: FRONTIER_SWARM_DEFAULT_MODEL,
240
+ reasoningEffort: FRONTIER_SWARM_DEFAULT_REASONING_EFFORT
241
+ }];
242
+ return values.map((compute) => ({
243
+ id: normalizeId(compute.id, 'compute id'),
244
+ kind: compute.kind ?? 'external',
245
+ ...(compute.title ? { title: compute.title } : {}),
246
+ ...(compute.model ? { model: compute.model } : {}),
247
+ ...(compute.reasoningEffort ? { reasoningEffort: compute.reasoningEffort } : {}),
248
+ ...(compute.serviceTier ? { serviceTier: compute.serviceTier } : {}),
249
+ ...(compute.profile ? { profile: compute.profile } : {}),
250
+ ...(compute.sandbox ? { sandbox: compute.sandbox } : {}),
251
+ ...(compute.approval ? { approval: compute.approval } : {}),
252
+ ...(positiveNumber(compute.maxConcurrency) ? { maxConcurrency: Math.floor(compute.maxConcurrency) } : {}),
253
+ ...(positiveNumber(compute.timeoutMs) ? { timeoutMs: Math.floor(compute.timeoutMs) } : {}),
254
+ ...(toJsonObject(compute.metadata) ? { metadata: toJsonObject(compute.metadata) } : {})
255
+ }));
256
+ }
257
+ function normalizeLayer(input) {
258
+ return {
259
+ id: normalizeId(input.id, 'layer id'),
260
+ title: input.title ?? titleFromId(input.id),
261
+ ...(input.description ? { description: input.description } : {}),
262
+ ...(input.parentId ? { parentId: normalizeId(input.parentId, 'parent layer id') } : {}),
263
+ ...(input.compute ? { compute: input.compute } : {}),
264
+ ...(input.defaultCompute ? { defaultCompute: input.defaultCompute } : {}),
265
+ childCompute: { ...(input.childCompute ?? {}) },
266
+ tags: uniqueStrings(input.tags ?? []),
267
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
268
+ };
269
+ }
270
+ function normalizeLane(input) {
271
+ const allowedWrites = uniqueStrings([...(input.allowedWrites ?? []), ...(input.allowedGlobs ?? [])]);
272
+ return {
273
+ id: normalizeId(input.id, 'lane id'),
274
+ title: input.title ?? titleFromId(input.id),
275
+ ...(input.description ? { description: input.description } : {}),
276
+ ...(input.layer ? { layer: input.layer } : {}),
277
+ ...(input.compute ? { compute: input.compute } : {}),
278
+ allowedWrites,
279
+ sharedReadOnly: uniqueStrings(input.sharedReadOnly ?? []),
280
+ neverEdit: uniqueStrings(input.neverEdit ?? []),
281
+ ...(input.worktreePath ? { worktreePath: input.worktreePath } : {}),
282
+ ...(input.evidencePrefix || input.evidenceOutDirPrefix ? { evidencePrefix: input.evidencePrefix ?? input.evidenceOutDirPrefix } : {}),
283
+ concurrencyKey: input.concurrencyKey ?? input.id,
284
+ ...(positiveNumber(input.maxConcurrency) ? { maxConcurrency: Math.floor(input.maxConcurrency) } : {}),
285
+ handoffCommands: normalizeCommands(input.handoffCommands ?? []),
286
+ tags: uniqueStrings(input.tags ?? []),
287
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
288
+ };
289
+ }
290
+ function normalizePolicy(input, defaultCompute) {
291
+ return {
292
+ mode: input?.mode ?? 'hard-file-ownership',
293
+ defaultConcurrency: Math.max(1, Math.floor(input?.defaultConcurrency ?? 1)),
294
+ defaultCompute: input?.defaultCompute ?? defaultCompute,
295
+ ...(input?.defaultLayer ? { defaultLayer: input.defaultLayer } : {}),
296
+ completedStatuses: uniqueStrings(input?.completedStatuses ?? DEFAULT_COMPLETED_STATUSES),
297
+ sharedReadOnly: uniqueStrings(input?.sharedReadOnly ?? []),
298
+ neverEditWithoutParent: uniqueStrings(input?.neverEditWithoutParent ?? []),
299
+ requireCleanWorktree: input?.requireCleanWorktree ?? true,
300
+ ...(toJsonObject(input?.metadata) ? { metadata: toJsonObject(input?.metadata) } : {})
301
+ };
302
+ }
303
+ function normalizeTask(input) {
304
+ const targetRefs = uniqueStrings([...(input.targetRefs ?? []), ...(input.ownedFiles ?? [])]);
305
+ return {
306
+ kind: FRONTIER_SWARM_TASK_KIND,
307
+ version: FRONTIER_SWARM_TASK_VERSION,
308
+ id: normalizeId(input.id, 'task id'),
309
+ title: input.title ?? titleFromId(input.id),
310
+ objective: input.objective ?? input.description ?? input.title ?? input.id,
311
+ ...(input.description ? { description: input.description } : {}),
312
+ workKind: input.kind ?? 'agent-task',
313
+ status: input.status ?? 'open',
314
+ ...(input.lane ? { lane: input.lane } : {}),
315
+ ...(input.layer ? { layer: input.layer } : {}),
316
+ ...(input.compute ? { compute: input.compute } : {}),
317
+ ...(input.parentTaskId ? { parentTaskId: input.parentTaskId } : {}),
318
+ priority: Number.isFinite(input.priority) ? Number(input.priority) : 100,
319
+ sourceRefs: uniqueStrings(input.sourceRefs ?? []),
320
+ targetRefs,
321
+ allowedWrites: uniqueStrings([...(input.allowedWrites ?? []), ...targetRefs]),
322
+ acceptance: normalizeAcceptance(input),
323
+ verification: normalizeCommands(input.verification ?? []),
324
+ ...(input.evidenceCommand ? { evidenceCommand: input.evidenceCommand } : {}),
325
+ ...(input.shardCommand ? { shardCommand: input.shardCommand } : {}),
326
+ tags: uniqueStrings(input.tags ?? []),
327
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
328
+ };
329
+ }
330
+ function normalizeTaskList(input) {
331
+ if (Array.isArray(input))
332
+ return input.map((task) => isSwarmTask(task) ? cloneJsonValue(task) : normalizeTask(task));
333
+ return defineSwarmTasks(input);
334
+ }
335
+ function normalizeAcceptance(input) {
336
+ const checks = (input.acceptanceChecks ?? []).map((check) => typeof check === 'string' ? check : check.description ?? check.id ?? '').filter(Boolean);
337
+ return uniqueStrings([...(input.acceptance ?? []), ...checks]);
338
+ }
339
+ function normalizeCommands(input) {
340
+ return input.map((entry) => {
341
+ if (typeof entry === 'string') {
342
+ return { name: entry, command: entry, args: [], required: true };
343
+ }
344
+ return {
345
+ name: entry.name ?? [entry.command, ...(entry.args ?? [])].join(' '),
346
+ command: entry.command,
347
+ args: [...(entry.args ?? [])],
348
+ required: entry.required ?? true,
349
+ ...(entry.cwd ? { cwd: entry.cwd } : {}),
350
+ ...(toJsonObject(entry.metadata) ? { metadata: toJsonObject(entry.metadata) } : {})
351
+ };
352
+ });
353
+ }
354
+ function selectSwarmTasks(manifest, tasks, options) {
355
+ const lanes = new Set(options.lanes ?? []);
356
+ const layers = new Set(options.layers ?? []);
357
+ const statuses = new Set(options.statuses ?? []);
358
+ const selectors = (options.selectors ?? []).map((selector) => selector.toLowerCase());
359
+ const completed = new Set(manifest.policy.completedStatuses);
360
+ const limit = options.limit === undefined ? tasks.length : Math.max(0, Math.floor(options.limit));
361
+ return tasks
362
+ .filter((task) => !task.lane || manifest.lanes.some((lane) => lane.id === task.lane))
363
+ .filter((task) => lanes.size === 0 || (task.lane !== undefined && lanes.has(task.lane)))
364
+ .filter((task) => layers.size === 0 || taskLayer(manifest, task) !== undefined && layers.has(taskLayer(manifest, task)))
365
+ .filter((task) => statuses.size === 0 || statuses.has(task.status))
366
+ .filter((task) => options.includeCompleted || !completed.has(task.status))
367
+ .filter((task) => selectors.length === 0 || selectors.some((selector) => searchableTask(task).includes(selector)))
368
+ .sort((left, right) => left.priority - right.priority || left.id.localeCompare(right.id))
369
+ .slice(0, limit);
370
+ }
371
+ function createJob(compiled, task, options) {
372
+ const lane = task.lane ? compiled.lanesById.get(task.lane) : undefined;
373
+ const layer = task.layer ?? lane?.layer ?? compiled.manifest.policy.defaultLayer;
374
+ const compute = options.compute
375
+ ? readCompute(compiled, options.compute)
376
+ : resolveTaskCompute(compiled, task);
377
+ const evidencePrefix = lane?.evidencePrefix ? lane.evidencePrefix.replace(/\/?$/, '/') + slug(task.id) + '/' : undefined;
378
+ const allowedWrites = uniqueStrings([
379
+ ...(lane?.allowedWrites ?? []),
380
+ ...task.allowedWrites,
381
+ ...(evidencePrefix ? [evidencePrefix + '**'] : [])
382
+ ]);
383
+ const ownershipWarnings = task.targetRefs
384
+ .filter((file) => allowedWrites.length > 0 && !allowedWrites.some((glob) => matchesGlob(file, glob)))
385
+ .map((file) => `${file} is outside allowed write globs for ${lane?.id ?? 'unassigned'}`);
386
+ return {
387
+ id: `${lane?.id ?? 'unassigned'}-${slug(task.id)}`,
388
+ taskId: task.id,
389
+ title: task.title,
390
+ lane: lane?.id ?? 'unassigned',
391
+ ...(layer ? { layer } : {}),
392
+ compute,
393
+ status: 'planned',
394
+ priority: task.priority,
395
+ task,
396
+ allowedWrites,
397
+ sharedReadOnly: uniqueStrings([...(compiled.manifest.policy.sharedReadOnly ?? []), ...(lane?.sharedReadOnly ?? [])]),
398
+ neverEdit: uniqueStrings([...(compiled.manifest.policy.neverEditWithoutParent ?? []), ...(lane?.neverEdit ?? [])]),
399
+ ...(lane?.worktreePath ? { worktreePath: lane.worktreePath } : {}),
400
+ ...(evidencePrefix ? { evidencePrefix } : {}),
401
+ concurrencyKey: lane?.concurrencyKey ?? task.lane ?? compute.id,
402
+ ownershipWarnings,
403
+ verification: task.verification.length ? task.verification : (lane?.handoffCommands ?? []),
404
+ acceptance: [...task.acceptance],
405
+ tags: uniqueStrings([...task.tags, ...(lane?.tags ?? []), ...(layer ? [layer] : []), compute.id]),
406
+ ...(task.metadata ? { metadata: task.metadata } : {})
407
+ };
408
+ }
409
+ function resolveTaskCompute(compiled, task) {
410
+ if (task.compute)
411
+ return readCompute(compiled, task.compute);
412
+ const lane = task.lane ? compiled.lanesById.get(task.lane) : undefined;
413
+ if (lane?.compute)
414
+ return readCompute(compiled, lane.compute);
415
+ const layerId = task.layer ?? lane?.layer ?? compiled.manifest.policy.defaultLayer;
416
+ const layered = layerId ? resolveLayerCompute(compiled, layerId) : undefined;
417
+ return layered ?? readCompute(compiled, compiled.manifest.policy.defaultCompute);
418
+ }
419
+ function resolveLayerCompute(compiled, layerId) {
420
+ const layer = compiled.layersById.get(layerId);
421
+ if (!layer)
422
+ return undefined;
423
+ let childId = layer.id;
424
+ let parentId = layer.parentId;
425
+ while (parentId) {
426
+ const parent = compiled.layersById.get(parentId);
427
+ if (!parent)
428
+ break;
429
+ const selected = parent.childCompute[childId] ?? parent.childCompute['*'];
430
+ if (selected)
431
+ return readCompute(compiled, selected);
432
+ childId = parent.id;
433
+ parentId = parent.parentId;
434
+ }
435
+ const own = layer.compute ?? layer.defaultCompute;
436
+ return own ? readCompute(compiled, own) : undefined;
437
+ }
438
+ function readCompute(compiled, id) {
439
+ return compiled.computeById.get(id) ?? compiled.computeById.get(compiled.manifest.policy.defaultCompute) ?? compiled.manifest.compute[0];
440
+ }
441
+ function validateTasksForManifest(compiled, tasks) {
442
+ const issues = [...compiled.validation.issues];
443
+ for (const task of tasks) {
444
+ if (task.lane && !compiled.lanesById.has(task.lane)) {
445
+ addIssue(issues, 'missing-task-lane', 'error', `tasks.${task.id}.lane`, `Task lane is not declared: ${task.lane}`);
446
+ }
447
+ if (task.layer && !compiled.layersById.has(task.layer)) {
448
+ addIssue(issues, 'missing-task-layer', 'error', `tasks.${task.id}.layer`, `Task layer is not declared: ${task.layer}`);
449
+ }
450
+ if (task.compute && !compiled.computeById.has(task.compute)) {
451
+ addIssue(issues, 'missing-task-compute', 'error', `tasks.${task.id}.compute`, `Task compute is not declared: ${task.compute}`);
452
+ }
453
+ }
454
+ return { valid: issues.every((issue) => issue.severity !== 'error'), issues };
455
+ }
456
+ function normalizeEvent(input) {
457
+ const at = input.at ?? Date.now();
458
+ return {
459
+ kind: FRONTIER_SWARM_EVENT_KIND,
460
+ version: FRONTIER_SWARM_EVENT_VERSION,
461
+ id: input.id ?? 'swarm-event:' + stableHash([input.type, input.runId, input.jobId, at, input.data]),
462
+ type: input.type,
463
+ ...(input.runId ? { runId: input.runId } : {}),
464
+ ...(input.jobId ? { jobId: input.jobId } : {}),
465
+ ...(input.taskId ? { taskId: input.taskId } : {}),
466
+ ...(input.lane ? { lane: input.lane } : {}),
467
+ ...(input.layer ? { layer: input.layer } : {}),
468
+ ...(input.compute ? { compute: input.compute } : {}),
469
+ at,
470
+ ...(input.message ? { message: input.message } : {}),
471
+ ...(input.data !== undefined ? { data: toJsonValue(input.data) } : {}),
472
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
473
+ };
474
+ }
475
+ function normalizeResult(input) {
476
+ const startedAt = input.startedAt;
477
+ const finishedAt = input.finishedAt;
478
+ return {
479
+ jobId: input.jobId,
480
+ status: input.status ?? (input.exitCode === 0 || input.exitCode === undefined ? 'completed' : 'failed'),
481
+ ...(startedAt !== undefined ? { startedAt } : {}),
482
+ ...(finishedAt !== undefined ? { finishedAt } : {}),
483
+ ...(startedAt !== undefined && finishedAt !== undefined ? { durationMs: Math.max(0, finishedAt - startedAt) } : {}),
484
+ ...(input.exitCode !== undefined ? { exitCode: input.exitCode } : {}),
485
+ ...(input.signal ? { signal: input.signal } : {}),
486
+ changedPaths: uniqueStrings(input.changedPaths ?? []),
487
+ ownershipViolations: uniqueStrings(input.ownershipViolations ?? []),
488
+ evidencePaths: uniqueStrings(input.evidencePaths ?? []),
489
+ verification: (input.verification ?? []).map(normalizeVerificationResult),
490
+ ...(input.lastMessage ? { lastMessage: input.lastMessage } : {}),
491
+ ...(input.error !== undefined ? { error: stringifyError(input.error) } : {}),
492
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
493
+ };
494
+ }
495
+ function normalizeVerificationResult(input) {
496
+ return {
497
+ name: input.name ?? ((input.command ?? []).join(' ') || 'verification'),
498
+ command: [...(input.command ?? [])],
499
+ ...(input.status !== undefined ? { status: input.status } : {}),
500
+ ...(input.durationMs !== undefined ? { durationMs: input.durationMs } : {}),
501
+ stdoutTail: [...(input.stdoutTail ?? [])],
502
+ stderrTail: [...(input.stderrTail ?? [])],
503
+ required: input.required ?? true,
504
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
505
+ };
506
+ }
507
+ function summarizeJobs(jobs) {
508
+ return {
509
+ computeCount: new Set(jobs.map((job) => job.compute.id)).size,
510
+ layerCount: new Set(jobs.map((job) => job.layer).filter((layer) => !!layer)).size,
511
+ laneCount: new Set(jobs.map((job) => job.lane)).size,
512
+ taskCount: jobs.length,
513
+ jobCount: jobs.length,
514
+ ownershipViolationCount: jobs.reduce((total, job) => total + job.ownershipWarnings.length, 0)
515
+ };
516
+ }
517
+ function summarizeRun(jobs, results) {
518
+ const ownershipViolationCount = results.reduce((total, result) => total + result.ownershipViolations.length, 0);
519
+ return {
520
+ ...summarizeJobs(jobs),
521
+ completedCount: results.filter((result) => result.status === 'completed' || result.status === 'verified').length,
522
+ blockedCount: results.filter((result) => result.status === 'blocked').length,
523
+ failedCount: results.filter((result) => result.status === 'failed' || result.exitCode !== undefined && result.exitCode !== 0).length,
524
+ ownershipViolationCount
525
+ };
526
+ }
527
+ function taskLayer(manifest, task) {
528
+ if (task.layer)
529
+ return task.layer;
530
+ const lane = task.lane ? manifest.lanes.find((entry) => entry.id === task.lane) : undefined;
531
+ return lane?.layer ?? manifest.policy.defaultLayer;
532
+ }
533
+ function searchableTask(task) {
534
+ return [
535
+ task.id,
536
+ task.title,
537
+ task.objective,
538
+ task.description,
539
+ task.workKind,
540
+ task.status,
541
+ task.lane,
542
+ task.layer,
543
+ task.compute,
544
+ ...task.sourceRefs,
545
+ ...task.targetRefs,
546
+ ...task.tags
547
+ ].filter(Boolean).join(' ').toLowerCase();
548
+ }
549
+ function addIssue(issues, code, severity, path, message) {
550
+ issues.push({ code, severity, path, message });
551
+ }
552
+ function hasLayerCycle(layerId, layers) {
553
+ const byId = new Map(layers.map((layer) => [layer.id, layer]));
554
+ const seen = new Set();
555
+ let cursor = layerId;
556
+ while (cursor) {
557
+ if (seen.has(cursor))
558
+ return true;
559
+ seen.add(cursor);
560
+ cursor = byId.get(cursor)?.parentId;
561
+ }
562
+ return false;
563
+ }
564
+ function isSwarmManifest(value) {
565
+ return !!value && typeof value === 'object' && value.kind === FRONTIER_SWARM_MANIFEST_KIND;
566
+ }
567
+ function isSwarmTask(value) {
568
+ return !!value && typeof value === 'object' && value.kind === FRONTIER_SWARM_TASK_KIND;
569
+ }
570
+ function normalizeId(value, label) {
571
+ const id = String(value || '').trim();
572
+ if (!id)
573
+ throw new Error(`Missing ${label}`);
574
+ return id;
575
+ }
576
+ function titleFromId(id) {
577
+ const parts = String(id).split(/[.:/_-]+/).filter(Boolean);
578
+ return parts.length ? parts.map((part) => part[0]?.toUpperCase() + part.slice(1)).join(' ') : String(id);
579
+ }
580
+ function slug(value) {
581
+ return String(value).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'item';
582
+ }
583
+ function uniqueStrings(values) {
584
+ return Array.from(new Set(values.map((value) => String(value ?? '').trim()).filter(Boolean)));
585
+ }
586
+ function positiveNumber(value) {
587
+ return typeof value === 'number' && Number.isFinite(value) && value > 0;
588
+ }
589
+ function toJsonObject(value) {
590
+ if (!value || typeof value !== 'object' || Array.isArray(value))
591
+ return undefined;
592
+ return cloneJsonValue(value);
593
+ }
594
+ function toJsonValue(value) {
595
+ if (value === undefined)
596
+ return null;
597
+ return cloneJsonValue(value);
598
+ }
599
+ function cloneJsonValue(value) {
600
+ return JSON.parse(JSON.stringify(value));
601
+ }
602
+ function stringifyError(error) {
603
+ return error instanceof Error ? error.message : String(error);
604
+ }
605
+ function stableHash(value) {
606
+ const text = stableStringify(value);
607
+ let hash = 2166136261;
608
+ for (let index = 0; index < text.length; index += 1) {
609
+ hash ^= text.charCodeAt(index);
610
+ hash = Math.imul(hash, 16777619);
611
+ }
612
+ return 'fnv1a32:' + (hash >>> 0).toString(16).padStart(8, '0');
613
+ }
614
+ function stableStringify(value) {
615
+ if (value === null || typeof value !== 'object')
616
+ return JSON.stringify(value);
617
+ if (Array.isArray(value))
618
+ return '[' + value.map(stableStringify).join(',') + ']';
619
+ const object = value;
620
+ return '{' + Object.keys(object).sort().map((key) => JSON.stringify(key) + ':' + stableStringify(object[key])).join(',') + '}';
621
+ }
622
+ //# sourceMappingURL=index.js.map