@oisincoveney/pipeline 3.11.0 → 3.11.2

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.
@@ -226,8 +226,8 @@ declare const configSchema: z.ZodObject<{
226
226
  policy: z.ZodOptional<z.ZodObject<{
227
227
  commands: z.ZodOptional<z.ZodEnum<{
228
228
  allow: "allow";
229
- deny: "deny";
230
229
  "trusted-only": "trusted-only";
230
+ deny: "deny";
231
231
  }>>;
232
232
  modules: z.ZodOptional<z.ZodEnum<{
233
233
  allow: "allow";
@@ -255,8 +255,8 @@ declare const configSchema: z.ZodObject<{
255
255
  global: "global";
256
256
  }>>;
257
257
  mode: z.ZodEnum<{
258
- local: "local";
259
258
  hosted: "hosted";
259
+ local: "local";
260
260
  }>;
261
261
  provider: z.ZodLiteral<"toolhive">;
262
262
  authorization_env: z.ZodDefault<z.ZodString>;
@@ -299,10 +299,10 @@ declare const configSchema: z.ZodObject<{
299
299
  }, z.core.$strict>>;
300
300
  output: z.ZodOptional<z.ZodObject<{
301
301
  format: z.ZodEnum<{
302
- json_schema: "json_schema";
303
302
  text: "text";
304
303
  json: "json";
305
304
  jsonl: "jsonl";
305
+ json_schema: "json_schema";
306
306
  }>;
307
307
  repair: z.ZodOptional<z.ZodObject<{
308
308
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -371,10 +371,10 @@ declare const configSchema: z.ZodObject<{
371
371
  disabled: "disabled";
372
372
  }>>>;
373
373
  output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
374
- json_schema: "json_schema";
375
374
  text: "text";
376
375
  json: "json";
377
376
  jsonl: "jsonl";
377
+ json_schema: "json_schema";
378
378
  }>>>;
379
379
  rules: z.ZodOptional<z.ZodBoolean>;
380
380
  skills: z.ZodOptional<z.ZodBoolean>;
package/dist/hooks.d.ts CHANGED
@@ -13,8 +13,8 @@ declare const hookResultSchema: z.ZodObject<{
13
13
  taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
14
14
  }, z.core.$strict>>;
15
15
  status: z.ZodEnum<{
16
- fail: "fail";
17
16
  pass: "pass";
17
+ fail: "fail";
18
18
  skip: "skip";
19
19
  }>;
20
20
  summary: z.ZodOptional<z.ZodString>;
@@ -1,3 +1,4 @@
1
+ import { applyJsonEdit, ensureTrailingNewline, parseJsonRecord } from "./json-config-merge.js";
1
2
  import { resolveHarnessTarget } from "./install-commands/shared.js";
2
3
  import { existsSync, readFileSync, statSync } from "node:fs";
3
4
  import { execa } from "execa";
@@ -21,6 +22,36 @@ const HOST_TARGET_ROOT = {
21
22
  function hashContent(content) {
22
23
  return createHash("sha256").update(content).digest("hex");
23
24
  }
25
+ const MERGE_MANAGED = { ".claude/settings.json": ["hooks"] };
26
+ function mergeKeyFor(path) {
27
+ return MERGE_MANAGED[path];
28
+ }
29
+ function canonicalize(value) {
30
+ if (Array.isArray(value)) return value.map(canonicalize);
31
+ if (isRecord(value)) {
32
+ const out = {};
33
+ for (const key of Object.keys(value).sort()) out[key] = canonicalize(value[key]);
34
+ return out;
35
+ }
36
+ return value;
37
+ }
38
+ function hashJson(value) {
39
+ return hashContent(Buffer.from(JSON.stringify(canonicalize(value) ?? null)));
40
+ }
41
+ function managedSubtree(text, keyPath) {
42
+ const parsed = parseJsonRecord(text);
43
+ if (!parsed.ok) return;
44
+ let cursor = parsed.value;
45
+ for (const key of keyPath) {
46
+ if (!isRecord(cursor)) return;
47
+ cursor = cursor[key];
48
+ }
49
+ return cursor;
50
+ }
51
+ function targetIdentityHash(path, content) {
52
+ const mergeKey = mergeKeyFor(path);
53
+ return mergeKey ? hashJson(managedSubtree(content.toString("utf8"), mergeKey)) : hashContent(content);
54
+ }
24
55
  async function cloneHookRepository(targetDir) {
25
56
  await execa("gh", [
26
57
  "repo",
@@ -59,11 +90,12 @@ async function sourceHookFiles(source) {
59
90
  return (await listFiles(hostRoot)).map((file) => {
60
91
  const relativePath = relative(hostRoot, file).replaceAll("\\", "/");
61
92
  const content = readFileSync(file);
93
+ const path = `${HOST_TARGET_ROOT[host]}/${relativePath}`;
62
94
  return {
63
95
  content,
64
- hash: hashContent(content),
96
+ hash: targetIdentityHash(path, content),
65
97
  host,
66
- path: `${HOST_TARGET_ROOT[host]}/${relativePath}`
98
+ path
67
99
  };
68
100
  });
69
101
  }))).flat().sort((a, b) => a.path.localeCompare(b.path));
@@ -111,7 +143,7 @@ function targetPath(path) {
111
143
  function actionForFile(file, force, manifests) {
112
144
  const target = targetPath(file.path);
113
145
  if (!existsSync(target)) return "create";
114
- const currentHash = hashContent(readFileSync(target));
146
+ const currentHash = targetIdentityHash(file.path, readFileSync(target));
115
147
  if (currentHash === file.hash) return "unchanged";
116
148
  if (force) return "update";
117
149
  return (manifests.get(file.host)?.files[file.path])?.hash === currentHash ? "update" : "conflict";
@@ -141,6 +173,11 @@ async function writePlannedFile(file) {
141
173
  if (file.action === "conflict" || file.action === "unchanged") return;
142
174
  const target = targetPath(file.path);
143
175
  await mkdir(dirname(target), { recursive: true });
176
+ const mergeKey = mergeKeyFor(file.path);
177
+ if (mergeKey && existsSync(target)) {
178
+ await writeFile(target, ensureTrailingNewline(applyJsonEdit(readFileSync(target, "utf8"), mergeKey, managedSubtree(file.content.toString("utf8"), mergeKey))));
179
+ return;
180
+ }
144
181
  await writeFile(target, file.content);
145
182
  }
146
183
  function itemFor(file) {
@@ -5,13 +5,13 @@ import { z } from "zod";
5
5
  //#region src/moka-submit.d.ts
6
6
  declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
7
7
  "workflow.start": "workflow.start";
8
+ "node.finish": "node.finish";
9
+ "node.start": "node.start";
8
10
  "workflow.success": "workflow.success";
9
11
  "workflow.failure": "workflow.failure";
10
12
  "workflow.complete": "workflow.complete";
11
- "node.start": "node.start";
12
13
  "node.success": "node.success";
13
14
  "node.error": "node.error";
14
- "node.finish": "node.finish";
15
15
  "gate.failure": "gate.failure";
16
16
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
17
17
  failure: z.ZodDefault<z.ZodEnum<{
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
94
94
  }, z.core.$strict>>;
95
95
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
96
96
  "workflow.start": "workflow.start";
97
+ "node.finish": "node.finish";
98
+ "node.start": "node.start";
97
99
  "workflow.success": "workflow.success";
98
100
  "workflow.failure": "workflow.failure";
99
101
  "workflow.complete": "workflow.complete";
100
- "node.start": "node.start";
101
102
  "node.success": "node.success";
102
103
  "node.error": "node.error";
103
- "node.finish": "node.finish";
104
104
  "gate.failure": "gate.failure";
105
105
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
106
106
  failure: z.ZodDefault<z.ZodEnum<{
@@ -207,13 +207,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
207
207
  }, z.core.$strict>>;
208
208
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
209
209
  "workflow.start": "workflow.start";
210
+ "node.finish": "node.finish";
211
+ "node.start": "node.start";
210
212
  "workflow.success": "workflow.success";
211
213
  "workflow.failure": "workflow.failure";
212
214
  "workflow.complete": "workflow.complete";
213
- "node.start": "node.start";
214
215
  "node.success": "node.success";
215
216
  "node.error": "node.error";
216
- "node.finish": "node.finish";
217
217
  "gate.failure": "gate.failure";
218
218
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
219
219
  failure: z.ZodDefault<z.ZodEnum<{
@@ -1593,6 +1593,17 @@ interface SchedulePlanningContext {
1593
1593
  parentWorkUnits: BacklogWorkUnit[];
1594
1594
  workUnits: BacklogWorkUnit[];
1595
1595
  }
1596
+ /**
1597
+ * A backlog ticket's frontmatter may declare dependencies on sibling tickets
1598
+ * that are not part of this run (e.g. submitting TOVA-766.03 alone when its
1599
+ * frontmatter depends on TOVA-766.01). Those out-of-scope ids must not reach the
1600
+ * planner: it is instructed to preserve Backlog dependency ids as needs edges,
1601
+ * and an edge to a work unit that no node serves fails schedule validation. Prune
1602
+ * each unit's dependencies to the ids actually in scope (parent + work units) —
1603
+ * the same predicate workUnitDependencyIssues applies after generation — so the
1604
+ * planner prompt context and downstream validation stay consistent.
1605
+ */
1606
+ declare function pruneOutOfScopeDependencies(context: SchedulePlanningContext): SchedulePlanningContext;
1596
1607
  declare function parseScheduleArtifact(source: string, sourcePath?: string): ScheduleArtifact;
1597
1608
  declare const ScheduleArtifactError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
1598
1609
  readonly _tag: "ScheduleArtifactError";
@@ -1606,4 +1617,4 @@ declare function compileScheduleArtifact(config: PipelineConfig, artifact: Sched
1606
1617
  declare function generateScheduleArtifact(options: GenerateScheduleOptions): Promise<GenerateScheduleResult>;
1607
1618
  declare function scheduleArtifactPath(worktreePath: string, scheduleId: string): string;
1608
1619
  //#endregion
1609
- export { BacklogWorkUnit, CompiledScheduleArtifact, GenerateScheduleOptions, GenerateScheduleResult, ScheduleArtifact, ScheduleArtifactError, SchedulePlanningContext, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
1620
+ export { BacklogWorkUnit, CompiledScheduleArtifact, GenerateScheduleOptions, GenerateScheduleResult, ScheduleArtifact, ScheduleArtifactError, SchedulePlanningContext, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, pruneOutOfScopeDependencies, scheduleArtifactPath };
@@ -49,6 +49,27 @@ const scheduleArtifactSchema = z.object({
49
49
  version: z.literal(1),
50
50
  workflows: z.record(z.string().regex(ID_RE), workflowSchema)
51
51
  }).strict();
52
+ /**
53
+ * A backlog ticket's frontmatter may declare dependencies on sibling tickets
54
+ * that are not part of this run (e.g. submitting TOVA-766.03 alone when its
55
+ * frontmatter depends on TOVA-766.01). Those out-of-scope ids must not reach the
56
+ * planner: it is instructed to preserve Backlog dependency ids as needs edges,
57
+ * and an edge to a work unit that no node serves fails schedule validation. Prune
58
+ * each unit's dependencies to the ids actually in scope (parent + work units) —
59
+ * the same predicate workUnitDependencyIssues applies after generation — so the
60
+ * planner prompt context and downstream validation stay consistent.
61
+ */
62
+ function pruneOutOfScopeDependencies(context) {
63
+ const inScope = new Set([...context.parentWorkUnits, ...context.workUnits].map((unit) => unit.id));
64
+ const prune = (unit) => unit.dependencies ? {
65
+ ...unit,
66
+ dependencies: unit.dependencies.filter((id) => inScope.has(id))
67
+ } : unit;
68
+ return {
69
+ parentWorkUnits: context.parentWorkUnits.map(prune),
70
+ workUnits: context.workUnits.map(prune)
71
+ };
72
+ }
52
73
  function parseScheduleArtifact(source, sourcePath = "schedule.yaml") {
53
74
  const document = parseDocument(source, {
54
75
  prettyErrors: false,
@@ -93,7 +114,7 @@ async function generateScheduleArtifact(options) {
93
114
  runId: options.runId,
94
115
  task: options.task
95
116
  });
96
- const planningContext = { ...loadBacklogPlanningContext(options.task, options.worktreePath) };
117
+ const planningContext = pruneOutOfScopeDependencies({ ...loadBacklogPlanningContext(options.task, options.worktreePath) });
97
118
  const generatedArtifact = await planScheduleArtifact(baseline, policy.planner_profile, options, planningContext);
98
119
  assertSchedulePassOrder();
99
120
  const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, policy.node_catalog, appendPullRequestDelivery(options.config, integrateParallelWriteFanout(options.config, addGeneratedImplementationCoverage(options.config, generatedArtifact))))), planningContext);
@@ -385,4 +406,4 @@ function flattenWorkflowNodes(nodes) {
385
406
  return flattenNodes(nodes, (node) => node.kind === "parallel" ? node.nodes : void 0);
386
407
  }
387
408
  //#endregion
388
- export { ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
409
+ export { ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, pruneOutOfScopeDependencies, scheduleArtifactPath };
@@ -11,8 +11,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
11
11
  runId: z.ZodString;
12
12
  sequence: z.ZodNumber;
13
13
  type: z.ZodEnum<{
14
- "workflow.start": "workflow.start";
15
14
  "workflow.planned": "workflow.planned";
15
+ "workflow.start": "workflow.start";
16
16
  }>;
17
17
  workflowPlan: z.ZodObject<{
18
18
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -58,10 +58,10 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
58
58
  }>;
59
59
  }, z.core.$strip>;
60
60
  type: z.ZodEnum<{
61
- "node.start": "node.start";
62
- "node.finish": "node.finish";
63
61
  "agent.finish": "agent.finish";
64
62
  "agent.start": "agent.start";
63
+ "node.finish": "node.finish";
64
+ "node.start": "node.start";
65
65
  }>;
66
66
  }, z.core.$strip>, z.ZodObject<{
67
67
  at: z.ZodOptional<z.ZodString>;
@@ -108,8 +108,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
108
108
  nodeId: z.ZodOptional<z.ZodString>;
109
109
  outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
110
110
  status: z.ZodEnum<{
111
- fail: "fail";
112
111
  pass: "pass";
112
+ fail: "fail";
113
113
  skip: "skip";
114
114
  }>;
115
115
  summary: z.ZodOptional<z.ZodString>;
@@ -189,8 +189,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
189
189
  runId: z.ZodString;
190
190
  sequence: z.ZodNumber;
191
191
  type: z.ZodEnum<{
192
- "workflow.start": "workflow.start";
193
192
  "workflow.planned": "workflow.planned";
193
+ "workflow.start": "workflow.start";
194
194
  }>;
195
195
  workflowPlan: z.ZodObject<{
196
196
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -236,10 +236,10 @@ declare const runnerEventBatchSchema: z.ZodObject<{
236
236
  }>;
237
237
  }, z.core.$strip>;
238
238
  type: z.ZodEnum<{
239
- "node.start": "node.start";
240
- "node.finish": "node.finish";
241
239
  "agent.finish": "agent.finish";
242
240
  "agent.start": "agent.start";
241
+ "node.finish": "node.finish";
242
+ "node.start": "node.start";
243
243
  }>;
244
244
  }, z.core.$strip>, z.ZodObject<{
245
245
  at: z.ZodOptional<z.ZodString>;
@@ -286,8 +286,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
286
286
  nodeId: z.ZodOptional<z.ZodString>;
287
287
  outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
288
288
  status: z.ZodEnum<{
289
- fail: "fail";
290
289
  pass: "pass";
290
+ fail: "fail";
291
291
  skip: "skip";
292
292
  }>;
293
293
  summary: z.ZodOptional<z.ZodString>;
@@ -0,0 +1,50 @@
1
+ # Okteto Runner Pod
2
+
3
+ Use the Okteto inner loop for hands-on local work inside the same container
4
+ shape as Argo runner tasks. One command stands the pod up and attaches a shell:
5
+
6
+ ```sh
7
+ mise run dev
8
+ ```
9
+
10
+ `mise run dev` applies the runner Deployment (`k8s/runner-dev/deployment.yaml`)
11
+ into the pipeline namespace (default `momokaya-pipeline`, overridable with
12
+ `PIPELINE_DEV_NAMESPACE`) and then runs `okteto up runner`, which syncs your
13
+ local checkout onto the pod and drops you into a bash terminal at
14
+ `/workspace/oisin-pipeline`. The pod runs `ghcr.io/oisin-ee/pipeline-runner:latest`
15
+ with the `pipeline-runner` service account and `CODEX_AUTH_PER_PROJECT_ACCOUNTS=0`.
16
+
17
+ Inside the pod, run a local entrypoint:
18
+
19
+ ```sh
20
+ moka run --entrypoint quick "inspect the current branch"
21
+ ```
22
+
23
+ Local `moka run` uses the live terminal renderer by default after PIPE-61; no
24
+ extra output-format flag is required.
25
+
26
+ Tear it down when done:
27
+
28
+ ```sh
29
+ mise run dev:down
30
+ ```
31
+
32
+ `dev:down` detaches the inner loop (`okteto down runner`) and removes the runner
33
+ Deployment.
34
+
35
+ Required namespace secrets and credentials in `momokaya-pipeline`:
36
+
37
+ - `ghcr-pull-secret` for pulling the private runner image.
38
+ - `opencode-auth-1` with `auth.json`, mounted at `/root/.local/share/opencode/auth.json`.
39
+ - `oisin-bot-git-credentials` with `username`, `password`, `identity`, and `known_hosts`, mounted at `/etc/pipeline/git-credentials`.
40
+ - `oisin-bot-github-auth` with `hosts.yml`, mounted at `/root/.config/gh/hosts.yml`.
41
+
42
+ Caveat: this recipe is for interactive `moka run` sessions, not Argo
43
+ `runner-command` pods. It intentionally does not mount per-run payload, schedule,
44
+ or task descriptor ConfigMaps.
45
+
46
+ Parity: the dev runner pod is the inner-loop twin of the production Argo runner
47
+ pod built by `buildRunnerArgoWorkflowManifest` in `src/argo-workflow.ts`. Keep
48
+ `k8s/runner-dev/deployment.yaml` in step with that builder (same image, service
49
+ account, env, and secret mounts) so "dev pod == prod runner pod" (the original
50
+ PIPE-62 intent) holds.
package/package.json CHANGED
@@ -127,7 +127,7 @@
127
127
  "prepack": "bun run build:cli"
128
128
  },
129
129
  "type": "module",
130
- "version": "3.11.0",
130
+ "version": "3.11.2",
131
131
  "description": "Config-driven multi-agent pipeline runner for repository work",
132
132
  "main": "./dist/index.js",
133
133
  "types": "./dist/index.d.ts",
@@ -1,26 +0,0 @@
1
- # DevSpace Runner Pod
2
-
3
- Use the runner profile for hands-on local work inside the same container shape as Argo runner tasks:
4
-
5
- ```sh
6
- devspace dev --profile runner
7
- ```
8
-
9
- DevSpace deploys a long-lived `pipeline-runner` pod with `ghcr.io/oisin-ee/pipeline-runner:latest`, the `pipeline-runner` service account, `CODEX_AUTH_PER_PROJECT_ACCOUNTS=0`, and a synced checkout at `/workspace/oisin-pipeline`. The terminal starts in that workdir.
10
-
11
- Inside the pod, run a local entrypoint:
12
-
13
- ```sh
14
- moka run --entrypoint quick "inspect the current branch"
15
- ```
16
-
17
- Local `moka run` uses the live terminal renderer by default after PIPE-61; no extra output-format flag is required.
18
-
19
- Required namespace secrets and credentials in `momokaya-pipeline`:
20
-
21
- - `ghcr-pull-secret` for pulling the private runner image.
22
- - `opencode-auth-1` with `auth.json`, mounted at `/root/.local/share/opencode/auth.json`.
23
- - `oisin-bot-git-credentials` with `username`, `password`, `identity`, and `known_hosts`, mounted at `/etc/pipeline/git-credentials`.
24
- - `oisin-bot-github-auth` with `hosts.yml`, mounted at `/root/.config/gh/hosts.yml`.
25
-
26
- Caveat: this recipe is for interactive `moka run` sessions, not Argo `runner-command` pods. It intentionally does not mount per-run payload, schedule, or task descriptor ConfigMaps.