@oisincoveney/pipeline 1.27.12 → 1.27.14

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.
@@ -64,6 +64,11 @@ async function submitRunnerArgoWorkflow(rawOptions, dependencies = {}) {
64
64
  "pipeline.oisin.dev/workflow": compiled.workflowId
65
65
  };
66
66
  const workflow = buildRunnerArgoWorkflowManifest({
67
+ annotations: payload.task.kind === "ticket" ? {
68
+ "pipeline.oisin.dev/ticket-id": payload.task.id,
69
+ "pipeline.oisin.dev/ticket-project": payload.run.project,
70
+ "pipeline.oisin.dev/ticket-title": payload.task.title
71
+ } : {},
67
72
  eventAuthSecretKey: options.eventAuthSecretKey,
68
73
  eventAuthSecretName: options.eventAuthSecretName,
69
74
  generateName: options.generateName,
@@ -6,6 +6,7 @@ declare const runnerArgoWorkflowManifestSchema: z.ZodObject<{
6
6
  apiVersion: z.ZodLiteral<"argoproj.io/v1alpha1">;
7
7
  kind: z.ZodLiteral<"Workflow">;
8
8
  metadata: z.ZodObject<{
9
+ annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
9
10
  generateName: z.ZodOptional<z.ZodString>;
10
11
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
11
12
  name: z.ZodOptional<z.ZodString>;
@@ -112,6 +113,7 @@ declare const buildRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
112
113
  eventAuthSecretName: z.ZodOptional<z.ZodString>;
113
114
  generateName: z.ZodOptional<z.ZodString>;
114
115
  githubAuthSecretName: z.ZodOptional<z.ZodString>;
116
+ annotations: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodString>>>;
115
117
  image: z.ZodDefault<z.ZodString>;
116
118
  imagePullPolicy: z.ZodDefault<z.ZodEnum<{
117
119
  Always: "Always";
@@ -83,6 +83,7 @@ const runnerArgoWorkflowManifestSchema = z.object({
83
83
  apiVersion: z.literal(ARGO_WORKFLOW_API_VERSION),
84
84
  kind: z.literal(ARGO_WORKFLOW_KIND),
85
85
  metadata: z.object({
86
+ annotations: z.record(z.string().min(1), z.string().min(1)).optional(),
86
87
  generateName: z.string().min(1).optional(),
87
88
  labels: z.record(z.string().min(1), labelValueSchema).optional(),
88
89
  name: z.string().min(1).optional(),
@@ -110,6 +111,7 @@ const buildRunnerArgoWorkflowOptionsSchema = z.object({
110
111
  eventAuthSecretName: kubernetesNameSchema.optional(),
111
112
  generateName: z.string().min(1).optional(),
112
113
  githubAuthSecretName: kubernetesNameSchema.optional(),
114
+ annotations: z.record(z.string().min(1), z.string().min(1).optional()).default({}),
113
115
  image: z.string().min(1).default(RUNNER_WORKFLOW_IMAGE),
114
116
  imagePullPolicy: z.enum([
115
117
  "Always",
@@ -151,6 +153,7 @@ function buildRunnerArgoWorkflowManifest(rawOptions) {
151
153
  apiVersion: ARGO_WORKFLOW_API_VERSION,
152
154
  kind: ARGO_WORKFLOW_KIND,
153
155
  metadata: {
156
+ annotations: compactRecord(options.annotations),
154
157
  ...options.name ? { name: options.name } : {},
155
158
  ...options.generateName ? { generateName: options.generateName } : {},
156
159
  labels: compactRecord({
package/dist/config.d.ts CHANGED
@@ -452,7 +452,6 @@ declare const configSchema: z.ZodObject<{
452
452
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
453
453
  timeout_ms: z.ZodOptional<z.ZodNumber>;
454
454
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
455
- task: "task";
456
455
  read: "read";
457
456
  list: "list";
458
457
  grep: "grep";
@@ -460,6 +459,7 @@ declare const configSchema: z.ZodObject<{
460
459
  bash: "bash";
461
460
  edit: "edit";
462
461
  write: "write";
462
+ task: "task";
463
463
  }>>>;
464
464
  }, z.core.$strict>>>;
465
465
  runner_command: z.ZodDefault<z.ZodObject<{
@@ -511,7 +511,6 @@ declare const configSchema: z.ZodObject<{
511
511
  rules: z.ZodOptional<z.ZodBoolean>;
512
512
  skills: z.ZodOptional<z.ZodBoolean>;
513
513
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
514
- task: "task";
515
514
  read: "read";
516
515
  list: "list";
517
516
  grep: "grep";
@@ -519,6 +518,7 @@ declare const configSchema: z.ZodObject<{
519
518
  bash: "bash";
520
519
  edit: "edit";
521
520
  write: "write";
521
+ task: "task";
522
522
  }>>>;
523
523
  }, z.core.$strict>;
524
524
  command: z.ZodOptional<z.ZodString>;
@@ -28,6 +28,7 @@ function runPipelineFromConfig(options) {
28
28
  function runScheduledWorkflowTask(options) {
29
29
  const { dependencyOutputs, nodeId, ...runtimeOptions } = options;
30
30
  const context = createRuntimeContext(runtimeOptions);
31
+ hydrateScheduledDependencyStates(context, nodeId);
31
32
  hydrateDependencyOutputs(context, dependencyOutputs);
32
33
  recordNodeEvent(context, nodeId, {
33
34
  at: now(),
@@ -208,6 +209,57 @@ function hydrateDependencyOutputs(context, dependencyOutputs) {
208
209
  });
209
210
  }
210
211
  }
212
+ function hydrateScheduledDependencyStates(context, nodeId) {
213
+ const finishedAt = now();
214
+ for (const dependencyId of scheduledDependencyNodeIds(context, nodeId)) {
215
+ const existing = context.nodeStates.get(dependencyId);
216
+ context.nodeStates.set(dependencyId, scheduledDependencyState(dependencyId, finishedAt, existing));
217
+ }
218
+ }
219
+ function scheduledDependencyState(id, finishedAt, existing) {
220
+ return completedScheduledDependencyState(existing ?? emptyScheduledDependencyState(id), finishedAt);
221
+ }
222
+ function emptyScheduledDependencyState(id) {
223
+ return {
224
+ attempts: 0,
225
+ evidence: [],
226
+ gates: [],
227
+ id,
228
+ status: "pending"
229
+ };
230
+ }
231
+ function completedScheduledDependencyState(base, finishedAt) {
232
+ return {
233
+ ...base,
234
+ attempts: positiveAttempts(base),
235
+ evidence: dependencyEvidence(base),
236
+ exitCode: base.exitCode ?? 0,
237
+ finishedAt: base.finishedAt ?? finishedAt,
238
+ output: base.output ?? "",
239
+ status: "passed"
240
+ };
241
+ }
242
+ function positiveAttempts(state) {
243
+ return state.attempts > 0 ? state.attempts : 1;
244
+ }
245
+ function dependencyEvidence(state) {
246
+ return state.evidence.length > 0 ? state.evidence : ["dependency satisfied by scheduled workflow"];
247
+ }
248
+ function scheduledDependencyNodeIds(context, nodeId) {
249
+ const visited = /* @__PURE__ */ new Set();
250
+ const ordered = [];
251
+ const visit = (candidateId) => {
252
+ if (visited.has(candidateId)) return;
253
+ visited.add(candidateId);
254
+ const candidate = context.plan.graph.node(candidateId);
255
+ if (!candidate) return;
256
+ for (const need of candidate.needs) visit(need);
257
+ ordered.push(candidateId);
258
+ };
259
+ const node = context.plan.graph.node(nodeId);
260
+ for (const need of node?.needs ?? []) visit(need);
261
+ return ordered;
262
+ }
211
263
  function cancelledFailure() {
212
264
  return {
213
265
  evidence: ["pipeline cancelled by AbortSignal"],
@@ -302,27 +354,22 @@ async function executeNode(node, context) {
302
354
  return cycle.result;
303
355
  }
304
356
  retry = retryCandidateForCycle(node, cycle, last, attempt);
305
- const selfRemediation = await remediateWritableNodeFailure({
357
+ const remediation = await remediateFailedNode({
306
358
  attempt,
307
359
  context,
308
360
  node,
309
361
  retry
310
362
  });
311
- if (selfRemediation) {
363
+ if (remediation?.result) {
312
364
  recordNodeEvent(context, node.id, {
313
365
  at: now(),
314
- result: selfRemediation,
366
+ result: remediation.result,
315
367
  type: "PASSED"
316
368
  });
317
- emitNodeFinish(context, selfRemediation);
318
- return selfRemediation;
369
+ emitNodeFinish(context, remediation.result);
370
+ return remediation.result;
319
371
  }
320
- if (await remediateCoverageFailure({
321
- attempt,
322
- context,
323
- node,
324
- retry
325
- })) continue;
372
+ if (remediation?.retryNode) continue;
326
373
  recordNodeEvent(context, node.id, {
327
374
  at: now(),
328
375
  attempt,
@@ -379,6 +426,13 @@ async function executeNode(node, context) {
379
426
  emitNodeFinish(context, result);
380
427
  return result;
381
428
  }
429
+ async function remediateFailedNode(input) {
430
+ const selfRemediation = await remediateWritableNodeFailure(input);
431
+ if (selfRemediation) return { result: selfRemediation };
432
+ if (await remediateCoverageFailure(input)) return { retryNode: true };
433
+ if (await remediateUpstreamImplementationFailure(input)) return { retryNode: true };
434
+ return null;
435
+ }
382
436
  async function remediateWritableNodeFailure(input) {
383
437
  if (input.retry.retryReason !== "gate_failure" || isRemediationNode(input.node) || !nodeCanWrite(input.context, input.node)) return null;
384
438
  const beforeSnapshot = await snapshotChangedFiles(input.context.worktreePath);
@@ -421,23 +475,41 @@ async function executeSelfRemediation(input) {
421
475
  }
422
476
  async function remediateCoverageFailure(input) {
423
477
  if (input.retry.retryReason !== "gate_failure" || !hasSchedulingRole(input.context, input.node, "coverage")) return false;
478
+ return await remediatePassedImplementationAncestors(input);
479
+ }
480
+ async function remediateUpstreamImplementationFailure(input) {
481
+ if (isRemediationNode(input.node) || nodeCanWrite(input.context, input.node) || hasSchedulingRole(input.context, input.node, "coverage")) return false;
482
+ return await remediatePassedImplementationAncestors(input);
483
+ }
484
+ async function remediatePassedImplementationAncestors(input) {
424
485
  const implementationNodes = upstreamImplementationNodes(input.context, input.node).filter((candidate) => input.context.nodeStates.get(candidate.id)?.status === "passed");
425
486
  if (implementationNodes.length === 0) return false;
426
- for (const implementationNode of implementationNodes) {
427
- if (isCancelled(input.context)) return false;
428
- const beforeSnapshot = await snapshotChangedFiles(input.context.worktreePath);
429
- const beforeOutput = input.context.lastOutputByNode.get(implementationNode.id);
430
- const result = await executeImplementationRemediation({
431
- attempt: input.attempt,
432
- context: input.context,
433
- coverageNode: input.node,
434
- implementationNode,
435
- retry: input.retry
436
- });
437
- if (result.status !== "passed") return false;
438
- if (diffChangedFiles(beforeSnapshot, await snapshotChangedFiles(input.context.worktreePath), input.context.worktreePath).files.size === 0 && result.output === beforeOutput) return false;
439
- input.context.lastOutputByNode.set(implementationNode.id, result.output);
440
- }
487
+ for (const implementationNode of implementationNodes) if (!await remediateImplementationAncestor(input, implementationNode)) return false;
488
+ return true;
489
+ }
490
+ async function remediateImplementationAncestor(input, implementationNode) {
491
+ if (isCancelled(input.context)) return false;
492
+ const beforeSnapshot = await snapshotChangedFiles(input.context.worktreePath);
493
+ const beforeOutput = input.context.lastOutputByNode.get(implementationNode.id);
494
+ const result = await executeImplementationRemediation({
495
+ attempt: input.attempt,
496
+ context: input.context,
497
+ coverageNode: input.node,
498
+ implementationNode,
499
+ retry: input.retry
500
+ });
501
+ if (result.status !== "passed") return false;
502
+ return await recordImplementationRemediationEffect({
503
+ beforeOutput,
504
+ beforeSnapshot,
505
+ context: input.context,
506
+ implementationNode,
507
+ result
508
+ });
509
+ }
510
+ async function recordImplementationRemediationEffect(input) {
511
+ if (diffChangedFiles(input.beforeSnapshot, await snapshotChangedFiles(input.context.worktreePath), input.context.worktreePath).files.size === 0 && input.result.output === input.beforeOutput) return false;
512
+ input.context.lastOutputByNode.set(input.implementationNode.id, input.result.output);
441
513
  return true;
442
514
  }
443
515
  async function executeImplementationRemediation(input) {
package/package.json CHANGED
@@ -120,7 +120,7 @@
120
120
  "prepack": "bun run build:cli"
121
121
  },
122
122
  "type": "module",
123
- "version": "1.27.12",
123
+ "version": "1.27.14",
124
124
  "description": "Config-driven multi-agent pipeline runner for repository work",
125
125
  "main": "./dist/index.js",
126
126
  "types": "./dist/index.d.ts",