@oisincoveney/pipeline 1.19.1 → 1.21.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.
Files changed (49) hide show
  1. package/README.md +49 -45
  2. package/dist/commands/pipeline-command.js +0 -1
  3. package/dist/commands/runner-job-command.js +6 -2
  4. package/dist/config.d.ts +31 -3
  5. package/dist/config.js +144 -59
  6. package/dist/gates.js +22 -6
  7. package/dist/hooks.d.ts +1 -1
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +26 -23
  10. package/dist/install-commands.js +8 -2
  11. package/dist/mcp/gateway.js +102 -2
  12. package/dist/mcp/repo-local-backends.js +70 -0
  13. package/dist/mcp/toolhive-vmcp.js +57 -0
  14. package/dist/path-refs.js +1 -1
  15. package/dist/pipeline-init.js +10 -256
  16. package/dist/pipeline-runtime.d.ts +2 -2
  17. package/dist/pipeline-runtime.js +210 -5
  18. package/dist/runner-event-sink.js +27 -7
  19. package/dist/runner-job/delivery.js +108 -31
  20. package/dist/runner-job/pr-summary.js +117 -0
  21. package/dist/runner-job/run.js +165 -55
  22. package/dist/runner-job/workspace.js +39 -5
  23. package/dist/runner-job-contract.d.ts +6 -19
  24. package/dist/runner-job-contract.js +5 -25
  25. package/dist/runner-job-k8s.d.ts +66 -0
  26. package/dist/runner-job-k8s.js +144 -0
  27. package/dist/runner-output.js +21 -19
  28. package/dist/runner.d.ts +1 -1
  29. package/dist/runner.js +16 -4
  30. package/dist/runtime/agent-node/agent-node.js +53 -5
  31. package/dist/runtime/builtins/builtins.js +11 -4
  32. package/dist/runtime/context/context.js +1 -0
  33. package/dist/runtime/contracts/contracts.d.ts +17 -1
  34. package/dist/runtime/contracts/index.d.ts +1 -1
  35. package/dist/runtime/events/events.js +45 -1
  36. package/dist/runtime/hooks/hooks.js +1 -1
  37. package/dist/runtime/json-validation/json-validation.js +8 -2
  38. package/dist/runtime/parallel-node/parallel-node.js +1 -0
  39. package/dist/runtime/worktrees/worktrees.js +11 -1
  40. package/dist/schedule-planner.js +175 -7
  41. package/dist/standard-output-schemas.js +80 -0
  42. package/docs/config-architecture.md +13 -15
  43. package/docs/mcp-gateway.md +30 -9
  44. package/docs/mcp-host-isolation.md +2 -1
  45. package/docs/operator-guide.md +134 -51
  46. package/docs/pipeline-console-runner-contract.md +39 -30
  47. package/docs/pipeline-smoke-recovery-plan.md +7 -8
  48. package/docs/slash-command-adapter-contract.md +5 -6
  49. package/package.json +6 -2
package/README.md CHANGED
@@ -1,16 +1,15 @@
1
1
  # @oisincoveney/pipeline
2
2
 
3
- Config-driven multi-agent pipeline runner for repository work. The source of
4
- truth is three YAML files: `.pipeline/runners.yaml` declares runner adapters,
5
- `.pipeline/profiles.yaml` declares reusable profiles and their grants, and
6
- `.pipeline/pipeline.yaml` declares orchestration, workflows, gates, hooks, and
7
- artifacts.
3
+ Config-driven multi-agent pipeline runner for repository work. Runtime config is
4
+ owned by the installed `@oisincoveney/pipeline` package. Repo-local `.pipeline`
5
+ paths are runtime artifact locations only; they are not the runtime config
6
+ source.
8
7
 
9
8
  ## Requirements
10
9
 
11
10
  - Bun 1.1 or newer
12
11
  - Node.js 22.13 or newer
13
- - `npx`, `backlog`, `uvx`, and Docker on `PATH` for default skills and MCP setup
12
+ - `npx`, `backlog`, `uvx`, and Docker on `PATH` for default skills and MCP gateway setup
14
13
  - At least one configured runner CLI on `PATH`: `codex`, `opencode`, `kimi`,
15
14
  `pi`, or a declared command runner
16
15
 
@@ -22,33 +21,31 @@ bun install --frozen-lockfile
22
21
 
23
22
  ## Start A Repository
24
23
 
25
- Scaffold the default YAML workflow:
24
+ Initialize package-owned pipeline support:
26
25
 
27
26
  ```shell
28
27
  pipe init
29
28
  ```
30
29
 
31
30
  `pipe init` installs default project skills with
32
- `npx skills add oisincoveney/skills` and registers default MCP servers with the
33
- MCPM CLI from https://mcpm.sh/. Default profiles point at the installed
34
- `.agents/skills/<skill>/SKILL.md` files in the target repository. The package
35
- invokes MCPM through `uvx --python 3.12 mcpm`, so generated `.mcp.json` entries
36
- do not depend on a globally installed `mcpm` binary. The default Qdrant/memory
37
- MCP is the Momokaya remote endpoint
38
- `https://memory-mcp.momokaya.ee/mcp/`.
39
-
40
- The default GitHub MCP registration uses GitHub's official container in
41
- read-only mode and reads `GITHUB_PERSONAL_ACCESS_TOKEN` from the environment.
42
- The Momokaya gateway endpoint is protected by Traefik HTTP Basic auth. Set
43
- `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP `Authorization` header
44
- value before starting Codex or OpenCode:
31
+ `npx skills add oisincoveney/skills`, then writes generated Codex and OpenCode
32
+ command surfaces plus their singleton `pipeline-gateway` MCP entries. It does
33
+ not create repo-local `.pipeline` config files.
34
+
35
+ The default MCP gateway can run locally or point at the hosted Momokaya
36
+ gateway. Set `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP
37
+ `Authorization` header value before starting Codex or OpenCode when using a
38
+ protected gateway:
45
39
 
46
40
  ```shell
47
41
  export PIPELINE_MCP_GATEWAY_AUTHORIZATION="Basic $(printf '%s' 'user:password' | base64)"
48
42
  ```
49
43
 
50
- `pipe init` writes project-level Codex and OpenCode config that points at the
51
- singleton `pipeline-gateway` MCP server.
44
+ To refresh or check generated host files later, use:
45
+
46
+ ```shell
47
+ pipe install-commands --host all --check
48
+ ```
52
49
 
53
50
  Check local prerequisites and config health:
54
51
 
@@ -124,8 +121,11 @@ storage, Kueue discovery, and UI rendering. This package owns the in-container
124
121
  translation, authenticated event posting, signal cancellation, and final event
125
122
  flushing.
126
123
 
127
- The console starts the image with `OISIN_PIPELINE_RUNNER_PAYLOAD_JSON` and the
128
- runner-side event token. The payload contract is documented in
124
+ The console starts the image with the payload as a mounted ConfigMap file and
125
+ the event auth token as a mounted Secret file. The runner reads the payload
126
+ from `--payload-file` and reads the event auth token from the file path
127
+ specified in `events.authTokenFile`. No environment variables are used for
128
+ payload or auth token delivery. The payload contract is documented in
129
129
  [`docs/pipeline-console-runner-contract.md`](docs/pipeline-console-runner-contract.md).
130
130
  The executable contract is exported from
131
131
  `@oisincoveney/pipeline/runner-job-contract` for payload construction,
@@ -133,9 +133,12 @@ validation, contract-version checks, and JSON Schema generation.
133
133
  Use `PIPELINE_TARGET_PATH=/path/to/worktree` when the checked-out target repo is
134
134
  mounted somewhere other than the process working directory.
135
135
 
136
- ## Minimal YAML
136
+ ## Custom YAML Parts
137
137
 
138
- `.pipeline/runners.yaml`:
138
+ Runtime execution uses package-owned defaults. Tests and advanced embedding code
139
+ can still parse explicit YAML parts with `parsePipelineConfigParts()`.
140
+
141
+ `runners`:
139
142
 
140
143
  ```yaml
141
144
  version: 1
@@ -153,7 +156,7 @@ runners:
153
156
  output_formats: [text, json, jsonl, json_schema]
154
157
  ```
155
158
 
156
- `.pipeline/profiles.yaml`:
159
+ `profiles`:
157
160
 
158
161
  ```yaml
159
162
  version: 1
@@ -179,7 +182,7 @@ profiles:
179
182
  format: text
180
183
  ```
181
184
 
182
- `.pipeline/pipeline.yaml`:
185
+ Example workflow shape:
183
186
 
184
187
  ```yaml
185
188
  version: 1
@@ -212,14 +215,12 @@ workflows:
212
215
  builtin: typecheck
213
216
  ```
214
217
 
215
- Projects can also declare `entrypoints` in `.pipeline/pipeline.yaml` to expose
216
- stable app or CLI names that resolve to workflows or schedule policies. Direct
217
- `--workflow` selection remains available and takes precedence over `--entrypoint`
218
- when both are set.
218
+ Package-owned defaults declare `entrypoints` that expose stable app or CLI names
219
+ resolving to workflows or schedule policies. Direct `--workflow` selection
220
+ remains available and takes precedence over `--entrypoint` when both are set.
219
221
 
220
- The default scaffold includes a full research, red, green, verify, learn
221
- workflow. See `docs/config-architecture.md` for a complete example and the host
222
- support matrix.
222
+ The package defaults include a full research, red, green, verify, learn
223
+ workflow. See `docs/config-architecture.md` for the host support matrix.
223
224
 
224
225
  ### Structural Parallelism
225
226
 
@@ -289,22 +290,22 @@ branches share a base SHA, and merges passing branches into an integration
289
290
  branch in declaration order. It reports merge conflicts; it does not resolve
290
291
  them automatically.
291
292
 
292
- Default profile skills are installed into `.agents/skills` by `pipe init`.
293
- Runtime MCP projection and host-specific isolation policy live in `src/mcp`; see
294
- [`docs/mcp-host-isolation.md`](docs/mcp-host-isolation.md) and
293
+ Default profile skills and generated host resources are installed by
294
+ `pipe init`. Runtime MCP projection and host-specific isolation policy live in
295
+ `src/mcp`; see [`docs/mcp-host-isolation.md`](docs/mcp-host-isolation.md) and
295
296
  [`docs/mcp-gateway.md`](docs/mcp-gateway.md).
296
297
 
297
298
  ## Generated Host Resources
298
299
 
299
- Generate native host files from the YAML config:
300
+ Generate native host files during setup:
300
301
 
301
302
  ```shell
302
- pipe install-commands --host all
303
+ pipe init
303
304
  ```
304
305
 
305
- Generated resources are derived from the three config files; they are not
306
- separate sources of truth. Host resources use exact native agents when the node
307
- runner matches the host. OpenCode also uses native subagents for cross-runner
306
+ Generated resources are derived from package-owned config; they are not separate
307
+ sources of truth. Host resources use exact native agents when the node runner
308
+ matches the host. OpenCode also uses native subagents for cross-runner
308
309
  model-backed nodes when the runner/profile provides an OpenCode-compatible
309
310
  `model` or `host_models.opencode` value. Otherwise generated instructions
310
311
  dispatch to that runner's CLI instead of inventing a host model.
@@ -333,8 +334,8 @@ runners:
333
334
 
334
335
  ## Runtime Guarantees
335
336
 
336
- - `pipe run` fails without `.pipeline/pipeline.yaml`,
337
- `.pipeline/profiles.yaml`, and `.pipeline/runners.yaml`.
337
+ - `pipe run` loads package-owned `@oisincoveney/pipeline` config even when the
338
+ repository has no repo-local `.pipeline` config files.
338
339
  - Multi-agent workflows execute as separate agent boundaries; nodes are not
339
340
  merged into one prompt.
340
341
  - Native subagent strategy is preferred when the selected runner can represent
@@ -410,3 +411,6 @@ bun run check
410
411
  bun run test
411
412
  bun run build:cli
412
413
  ```
414
+
415
+
416
+ Runner Kubernetes Jobs can be generated with `buildRunnerJobK8sManifest` from `@oisincoveney/pipeline/runner-job-k8s`.
@@ -12,7 +12,6 @@ const BUILTIN_PIPE_COMMANDS = new Set([
12
12
  ]);
13
13
  function registerConfiguredEntrypointCommands(program, config, runEntrypoint) {
14
14
  const registered = /* @__PURE__ */ new Set();
15
- if (!config) return registered;
16
15
  const reservedCommands = new Set(program.commands.map((command) => command.name()));
17
16
  for (const [id, entrypoint] of Object.entries(config.entrypoints)) {
18
17
  if (reservedCommands.has(id)) continue;
@@ -1,8 +1,12 @@
1
1
  import { runRunnerJob } from "../runner-job/run.js";
2
2
  //#region src/commands/runner-job-command.ts
3
3
  function registerRunnerJobCommand(program) {
4
- program.command("runner-job").description("Run an in-pod pipeline runner job from the console payload").action(async () => {
5
- const exitCode = await runRunnerJob();
4
+ program.command("runner-job").description("Run an in-pod pipeline runner job from the console payload").option("--payload-file <path>", "Path to the runner job payload JSON file").option("--orchestrator <name>", "Orchestrator runner (codex|opencode)").argument("[orchestrator]", "Orchestrator runner (codex|opencode)").action(async (orchestratorArg, options) => {
5
+ const orchestrator = orchestratorArg ?? options.orchestrator;
6
+ const exitCode = await runRunnerJob({
7
+ payloadFile: options.payloadFile,
8
+ orchestrator
9
+ });
6
10
  process.exitCode = exitCode;
7
11
  });
8
12
  }
package/dist/config.d.ts CHANGED
@@ -10,7 +10,13 @@ declare const HOOK_EVENTS: readonly ["workflow.start", "workflow.success", "work
10
10
  declare const GATE_KINDS: readonly ["acceptance", "artifact", "builtin", "changed_files", "command", "json_schema", "verdict"];
11
11
  declare const SCHEDULE_BASELINES: readonly ["epic", "pipe"];
12
12
  declare const SCHEDULING_ROLES: readonly ["coverage", "implementation"];
13
- type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "PIPELINE_CONFIG_MISSING" | "PIPELINE_CONFIG_PARSE_ERROR" | "PIPELINE_CONFIG_VALIDATION_ERROR";
13
+ declare const MCP_GATEWAY_BACKEND_LOCALITIES: readonly ["repo-local", "repo-scoped-remote", "shared-remote"];
14
+ declare const MCP_GATEWAY_WORKSPACE_PATH_SOURCES: readonly ["PIPELINE_TARGET_PATH", "cwd"];
15
+ declare const DEFAULT_RUNNER_JOB_GIT_COMMITTER: {
16
+ readonly email: "git@oisin.ee";
17
+ readonly name: "oisin-bot";
18
+ };
19
+ type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "PIPELINE_CONFIG_PARSE_ERROR" | "PIPELINE_CONFIG_VALIDATION_ERROR";
14
20
  interface PipelineConfigIssue {
15
21
  message: string;
16
22
  path?: string;
@@ -233,6 +239,19 @@ declare const configSchema: z.ZodObject<{
233
239
  }, z.core.$strict>>;
234
240
  }, z.core.$strict>>;
235
241
  mcp_gateway: z.ZodOptional<z.ZodObject<{
242
+ backends: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
243
+ locality: z.ZodEnum<{
244
+ "repo-local": "repo-local";
245
+ "repo-scoped-remote": "repo-scoped-remote";
246
+ "shared-remote": "shared-remote";
247
+ }>;
248
+ required: z.ZodDefault<z.ZodBoolean>;
249
+ tool_prefixes: z.ZodArray<z.ZodString>;
250
+ workspace_path_source: z.ZodOptional<z.ZodEnum<{
251
+ PIPELINE_TARGET_PATH: "PIPELINE_TARGET_PATH";
252
+ cwd: "cwd";
253
+ }>>;
254
+ }, z.core.$strict>>>;
236
255
  default_profile: z.ZodOptional<z.ZodString>;
237
256
  mode: z.ZodEnum<{
238
257
  hosted: "hosted";
@@ -298,6 +317,7 @@ declare const configSchema: z.ZodObject<{
298
317
  implementation: "implementation";
299
318
  }>>>;
300
319
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
320
+ timeout_ms: z.ZodOptional<z.ZodNumber>;
301
321
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
302
322
  read: "read";
303
323
  list: "list";
@@ -322,6 +342,12 @@ declare const configSchema: z.ZodObject<{
322
342
  required: z.ZodDefault<z.ZodBoolean>;
323
343
  }, z.core.$strict>>>;
324
344
  }, z.core.$strict>>;
345
+ git: z.ZodDefault<z.ZodObject<{
346
+ committer: z.ZodDefault<z.ZodObject<{
347
+ email: z.ZodDefault<z.ZodString>;
348
+ name: z.ZodDefault<z.ZodString>;
349
+ }, z.core.$strict>>;
350
+ }, z.core.$strict>>;
325
351
  }, z.core.$strict>>;
326
352
  rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
327
353
  path: z.ZodString;
@@ -400,6 +426,8 @@ type HookEvent = (typeof HOOK_EVENTS)[number];
400
426
  type GateKind = (typeof GATE_KINDS)[number];
401
427
  type ScheduleBaseline = (typeof SCHEDULE_BASELINES)[number];
402
428
  type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
429
+ type McpGatewayBackendLocality = (typeof MCP_GATEWAY_BACKEND_LOCALITIES)[number];
430
+ type McpGatewayWorkspacePathSource = (typeof MCP_GATEWAY_WORKSPACE_PATH_SOURCES)[number];
403
431
  interface PipelineConfigParts {
404
432
  pipeline: string;
405
433
  profiles: string;
@@ -409,9 +437,9 @@ interface PipelineConfigValidationOptions {
409
437
  allowMissingLintFileReferences?: boolean;
410
438
  }
411
439
  declare function loadPipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig;
412
- declare function tryLoadPipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig | null;
440
+ declare function loadPackagePipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig;
413
441
  declare function parsePipelineConfigYaml(source: string, sourcePath?: string, projectRoot?: string): PipelineConfig;
414
442
  declare function parsePipelineConfigParts(sources: PipelineConfigParts, projectRoot?: string, sourcePaths?: PipelineConfigParts, options?: PipelineConfigValidationOptions): PipelineConfig;
415
443
  declare function validatePipelineConfig(rawConfig: PipelineConfig, projectRoot?: string, options?: PipelineConfigValidationOptions): PipelineConfig;
416
444
  //#endregion
417
- export { GateKind, HookEvent, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, SchedulingRole, WorkflowNodeKind, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
445
+ export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, GateKind, HookEvent, McpGatewayBackendLocality, McpGatewayWorkspacePathSource, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, SchedulingRole, WorkflowNodeKind, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, validatePipelineConfig, workflowSchema };
package/dist/config.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import { resolveFileReference } from "./path-refs.js";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { standardOutputSchemaNameFromPath } from "./standard-output-schemas.js";
3
+ import { existsSync } from "node:fs";
4
4
  import { parseDocument } from "yaml";
5
5
  import { z } from "zod";
6
6
  //#region src/config.ts
7
7
  const PIPELINE_CONFIG_PATH = ".pipeline/pipeline.yaml";
8
8
  const RUNNERS_CONFIG_PATH = ".pipeline/runners.yaml";
9
9
  const PROFILES_CONFIG_PATH = ".pipeline/profiles.yaml";
10
- const LEGACY_CONFIG_PATH = ".pipeline/config.toml";
11
10
  const ID_RE = /^[a-z][a-z0-9-]*$/;
12
11
  const RUNNER_TYPES = [
13
12
  "codex",
@@ -56,7 +55,17 @@ const RETRY_REASONS = [
56
55
  ];
57
56
  const SCHEDULE_BASELINES = ["epic", "pipe"];
58
57
  const SCHEDULING_ROLES = ["coverage", "implementation"];
58
+ const MCP_GATEWAY_BACKEND_LOCALITIES = [
59
+ "repo-local",
60
+ "repo-scoped-remote",
61
+ "shared-remote"
62
+ ];
63
+ const MCP_GATEWAY_WORKSPACE_PATH_SOURCES = ["PIPELINE_TARGET_PATH", "cwd"];
59
64
  const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
65
+ const DEFAULT_RUNNER_JOB_GIT_COMMITTER = {
66
+ email: "git@oisin.ee",
67
+ name: "oisin-bot"
68
+ };
60
69
  const PACKAGE_DEFAULT_RUNNERS_YAML = `version: 1
61
70
  runners:
62
71
  codex:
@@ -73,7 +82,7 @@ runners:
73
82
  output_formats: [text, json, jsonl, json_schema]
74
83
  opencode:
75
84
  type: opencode
76
- model: default
85
+ model: openai/gpt-5.5
77
86
  capabilities:
78
87
  native_subagents: true
79
88
  rules: true
@@ -102,6 +111,29 @@ mcp_gateway:
102
111
  url_env: PIPELINE_MCP_GATEWAY_URL
103
112
  authorization_env: PIPELINE_MCP_GATEWAY_AUTHORIZATION
104
113
  default_profile: default
114
+ backends:
115
+ context7:
116
+ locality: shared-remote
117
+ tool_prefixes: [context7]
118
+ uidotsh:
119
+ locality: shared-remote
120
+ tool_prefixes: [uidotsh]
121
+ qdrant:
122
+ locality: repo-scoped-remote
123
+ tool_prefixes: [qdrant]
124
+ fallow:
125
+ locality: repo-local
126
+ workspace_path_source: PIPELINE_TARGET_PATH
127
+ required: false
128
+ tool_prefixes: [fallow]
129
+ serena:
130
+ locality: repo-local
131
+ workspace_path_source: PIPELINE_TARGET_PATH
132
+ tool_prefixes: [serena]
133
+ backlog:
134
+ locality: repo-local
135
+ workspace_path_source: PIPELINE_TARGET_PATH
136
+ tool_prefixes: [backlog]
105
137
  profiles:
106
138
  orchestrator:
107
139
  runner: codex
@@ -113,11 +145,16 @@ profiles:
113
145
  pipeline-researcher:
114
146
  runner: codex
115
147
  description: Research the requested task and produce structured findings.
116
- instructions: { inline: "Inspect first-party source, tests, docs, and task context before proposing changes." }
148
+ instructions: { inline: "Inspect first-party source, tests, docs, and task context for the current task only. Produce concise findings with file references and stop; do not perform open-ended repository exploration." }
149
+ timeout_ms: 900000
117
150
  mcp_servers: [pipeline-gateway]
118
151
  tools: [read, list, grep, glob, bash]
119
152
  filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
120
153
  network: { mode: inherit }
154
+ output:
155
+ format: json_schema
156
+ schema_path: .pipeline/schemas/research.schema.json
157
+ repair: { enabled: true, max_attempts: 1 }
121
158
  pipeline-inspector:
122
159
  runner: codex
123
160
  description: Inspect the repository without modifying files.
@@ -136,12 +173,17 @@ profiles:
136
173
  network: { mode: inherit }
137
174
  pipeline-test-writer:
138
175
  runner: codex
176
+ scheduling_roles: [implementation]
139
177
  description: Add focused failing tests for the requested behavior.
140
- instructions: { inline: "Add focused failing tests for the requested behavior only. Do not change production code." }
178
+ instructions: { inline: "Add focused failing tests for the requested behavior only. Do not change production code. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
141
179
  mcp_servers: [pipeline-gateway]
142
180
  tools: [read, list, grep, glob, bash, edit, write]
143
181
  filesystem: { mode: workspace-write, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
144
182
  network: { mode: inherit }
183
+ output:
184
+ format: json_schema
185
+ schema_path: .pipeline/schemas/implementation.schema.json
186
+ repair: { enabled: true, max_attempts: 1 }
145
187
  pipeline-epic-router:
146
188
  runner: codex
147
189
  description: Route epic sub-tickets into fixed implementation tracks.
@@ -154,20 +196,28 @@ profiles:
154
196
  runner: codex
155
197
  scheduling_roles: [implementation]
156
198
  description: Implement production code until the failing tests pass.
157
- instructions: { inline: "Implement the smallest production change that satisfies the failing tests." }
199
+ instructions: { inline: "Implement the smallest production change that satisfies the failing tests. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
158
200
  mcp_servers: [pipeline-gateway]
159
201
  tools: [read, list, grep, glob, bash, edit, write]
160
202
  filesystem: { mode: workspace-write, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
161
203
  network: { mode: inherit }
204
+ output:
205
+ format: json_schema
206
+ schema_path: .pipeline/schemas/implementation.schema.json
207
+ repair: { enabled: true, max_attempts: 1 }
162
208
  pipeline-acceptance-reviewer:
163
209
  runner: codex
164
210
  scheduling_roles: [coverage]
165
211
  description: Audit the finished change against every acceptance criterion.
166
- instructions: { inline: "Audit the completed change against each canonical acceptance criterion independently." }
212
+ instructions: { inline: 'Audit the completed change against each canonical acceptance criterion independently. Return only valid JSON with top-level "verdict", "evidence", "acceptance", and optional "violations". Each "acceptance" entry must include "id", "verdict", and non-empty "evidence". Do not use Markdown fences or prose outside the JSON object.' }
167
213
  mcp_servers: [pipeline-gateway]
168
214
  tools: [read, list, grep, glob, bash]
169
215
  filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
170
216
  network: { mode: inherit }
217
+ output:
218
+ format: json_schema
219
+ schema_path: .pipeline/schemas/acceptance.schema.json
220
+ repair: { enabled: true, max_attempts: 1 }
171
221
  pipeline-thermo-nuclear-reviewer:
172
222
  runner: codex
173
223
  scheduling_roles: [coverage]
@@ -177,15 +227,23 @@ profiles:
177
227
  tools: [read, list, grep, glob, bash]
178
228
  filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
179
229
  network: { mode: inherit }
230
+ output:
231
+ format: json_schema
232
+ schema_path: .pipeline/schemas/review.schema.json
233
+ repair: { enabled: true, max_attempts: 1 }
180
234
  pipeline-verifier:
181
235
  runner: codex
182
236
  scheduling_roles: [coverage]
183
237
  description: Verify checks, implementation fit, and final evidence.
184
- instructions: { inline: "Verify checks, implementation fit, and final evidence." }
238
+ instructions: { inline: 'Verify checks, implementation fit, and final evidence. Return only valid JSON with top-level "verdict", "evidence", and optional "violations". Do not use Markdown fences or prose outside the JSON object.' }
185
239
  mcp_servers: [pipeline-gateway]
186
240
  tools: [read, list, grep, glob, bash]
187
241
  filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
188
242
  network: { mode: inherit }
243
+ output:
244
+ format: json_schema
245
+ schema_path: .pipeline/schemas/verify.schema.json
246
+ repair: { enabled: true, max_attempts: 1 }
189
247
  pipeline-learner:
190
248
  runner: codex
191
249
  description: Store durable lessons from the completed run.
@@ -194,6 +252,10 @@ profiles:
194
252
  tools: [read, list, grep, glob, bash]
195
253
  filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
196
254
  network: { mode: inherit }
255
+ output:
256
+ format: json_schema
257
+ schema_path: .pipeline/schemas/learn.schema.json
258
+ repair: { enabled: true, max_attempts: 1 }
197
259
  `;
198
260
  const PACKAGE_DEFAULT_PIPELINE_YAML = `version: 1
199
261
  default_workflow: default
@@ -213,7 +275,7 @@ hooks:
213
275
  functions:
214
276
  generated-defaults-audit:
215
277
  kind: command
216
- command: [node, -e, "process.exit(0)"]
278
+ command: [node, -e, "const fs=require('node:fs'); fs.writeFileSync(process.env.PIPELINE_HOOK_RESULT, JSON.stringify({status:'pass',summary:'Generated defaults audit passed'}));"]
217
279
  trusted: true
218
280
  timeout_ms: 5000
219
281
  output_limit_bytes: 4096
@@ -340,6 +402,10 @@ workflows:
340
402
  kind: agent
341
403
  profile: pipeline-thermo-nuclear-reviewer
342
404
  needs: [merge]
405
+ gates:
406
+ - id: review-verdict
407
+ kind: verdict
408
+ target: stdout
343
409
  `;
344
410
  var PipelineConfigError = class extends Error {
345
411
  code;
@@ -412,7 +478,28 @@ const mcpServerSchema = z.object({
412
478
  path: ["bearer_token_env_var"]
413
479
  });
414
480
  });
481
+ const mcpGatewayBackendSchema = z.object({
482
+ locality: z.enum(MCP_GATEWAY_BACKEND_LOCALITIES),
483
+ required: z.boolean().default(true),
484
+ tool_prefixes: z.array(z.string().min(1)).min(1),
485
+ workspace_path_source: z.enum(MCP_GATEWAY_WORKSPACE_PATH_SOURCES).optional()
486
+ }).strict().superRefine((backend, ctx) => {
487
+ if (backend.locality === "repo-local") {
488
+ if (!backend.workspace_path_source) ctx.addIssue({
489
+ code: "custom",
490
+ message: "repo-local gateway backend must declare workspace_path_source as PIPELINE_TARGET_PATH or cwd",
491
+ path: ["workspace_path_source"]
492
+ });
493
+ return;
494
+ }
495
+ if (backend.workspace_path_source) ctx.addIssue({
496
+ code: "custom",
497
+ message: "workspace_path_source is only valid for repo-local gateway backends",
498
+ path: ["workspace_path_source"]
499
+ });
500
+ });
415
501
  const mcpGatewaySchema = z.object({
502
+ backends: strictRecord(mcpGatewayBackendSchema).default({}),
416
503
  default_profile: z.string().min(1).optional(),
417
504
  mode: z.enum(["hosted", "local"]),
418
505
  provider: z.literal("toolhive"),
@@ -515,6 +602,7 @@ const profileSchema = z.object({
515
602
  runner: z.string(),
516
603
  scheduling_roles: z.array(z.enum(SCHEDULING_ROLES)).optional(),
517
604
  skills: z.array(z.string()).optional(),
605
+ timeout_ms: z.number().int().positive().optional(),
518
606
  tools: z.array(z.enum(TOOL_NAMES)).optional()
519
607
  }).strict();
520
608
  const orchestratorSchema = z.object({ profile: z.string() }).strict();
@@ -654,10 +742,18 @@ const runnerJobEnvironmentSchema = z.object({
654
742
  setup: z.array(runnerJobCommandSchema).default([]),
655
743
  smoke: z.array(runnerJobCommandSchema).default([])
656
744
  }).strict();
657
- const runnerJobConfigSchema = z.object({ environment: runnerJobEnvironmentSchema.default({
658
- setup: [],
659
- smoke: []
660
- }) }).strict();
745
+ const runnerJobGitCommitterSchema = z.object({
746
+ email: z.string().email().default(DEFAULT_RUNNER_JOB_GIT_COMMITTER.email),
747
+ name: z.string().min(1).default(DEFAULT_RUNNER_JOB_GIT_COMMITTER.name)
748
+ }).strict();
749
+ const runnerJobGitSchema = z.object({ committer: runnerJobGitCommitterSchema.default(DEFAULT_RUNNER_JOB_GIT_COMMITTER) }).strict();
750
+ const runnerJobConfigSchema = z.object({
751
+ environment: runnerJobEnvironmentSchema.default({
752
+ setup: [],
753
+ smoke: []
754
+ }),
755
+ git: runnerJobGitSchema.default({ committer: DEFAULT_RUNNER_JOB_GIT_COMMITTER })
756
+ }).strict();
661
757
  const runnersFileSchema = z.object({
662
758
  runners: strictRecord(runnerSchema).default({}),
663
759
  version: z.literal(1)
@@ -678,10 +774,13 @@ const pipelineFileSchema = z.object({
678
774
  on: {}
679
775
  }),
680
776
  orchestrator: orchestratorSchema,
681
- runner_job: runnerJobConfigSchema.default({ environment: {
682
- setup: [],
683
- smoke: []
684
- } }),
777
+ runner_job: runnerJobConfigSchema.default({
778
+ environment: {
779
+ setup: [],
780
+ smoke: []
781
+ },
782
+ git: { committer: DEFAULT_RUNNER_JOB_GIT_COMMITTER }
783
+ }),
685
784
  schedules: strictRecord(schedulePolicySchema).default({}),
686
785
  task_context: taskContextResolverSchema.optional(),
687
786
  workflows: strictRecord(workflowSchema).default({}),
@@ -698,10 +797,13 @@ const configSchema = z.object({
698
797
  mcp_servers: strictRecord(mcpServerSchema).default({}),
699
798
  orchestrator: orchestratorSchema,
700
799
  profiles: strictRecord(profileSchema).default({}),
701
- runner_job: runnerJobConfigSchema.default({ environment: {
702
- setup: [],
703
- smoke: []
704
- } }),
800
+ runner_job: runnerJobConfigSchema.default({
801
+ environment: {
802
+ setup: [],
803
+ smoke: []
804
+ },
805
+ git: { committer: DEFAULT_RUNNER_JOB_GIT_COMMITTER }
806
+ }),
705
807
  rules: strictRecord(pathRefSchema).default({}),
706
808
  runners: strictRecord(runnerSchema).default({}),
707
809
  schedules: strictRecord(schedulePolicySchema).default({}),
@@ -769,39 +871,18 @@ function addConfigSchemaIssue(ctx, path, message) {
769
871
  });
770
872
  }
771
873
  function loadPipelineConfig(projectRoot, options = {}) {
772
- const paths = [
773
- PIPELINE_CONFIG_PATH,
774
- PROFILES_CONFIG_PATH,
775
- RUNNERS_CONFIG_PATH
776
- ];
777
- const missing = paths.filter((path) => !existsSync(join(projectRoot, path)));
778
- if (missing.length > 0) {
779
- if (existsSync(join(projectRoot, LEGACY_CONFIG_PATH))) throw new PipelineConfigError("PIPELINE_CONFIG_LEGACY_UNSUPPORTED", `${LEGACY_CONFIG_PATH} is not supported by the v1 pipeline config. Create ${PIPELINE_CONFIG_PATH}.`, [{
780
- path: LEGACY_CONFIG_PATH,
781
- message: "legacy TOML config found"
782
- }]);
783
- if (missing.length === paths.length) return parsePipelineConfigParts({
784
- pipeline: PACKAGE_DEFAULT_PIPELINE_YAML,
785
- profiles: PACKAGE_DEFAULT_PROFILES_YAML,
786
- runners: PACKAGE_DEFAULT_RUNNERS_YAML
787
- }, projectRoot, {
788
- pipeline: "@oisincoveney/pipeline/defaults/pipeline.yaml",
789
- profiles: "@oisincoveney/pipeline/defaults/profiles.yaml",
790
- runners: "@oisincoveney/pipeline/defaults/runners.yaml"
791
- }, options);
792
- throw new PipelineConfigError("PIPELINE_CONFIG_MISSING", `Missing required pipeline config files: ${missing.join(", ")}`, missing.map((path) => ({
793
- path,
794
- message: "file does not exist"
795
- })));
796
- }
797
- return parsePipelineConfigParts({
798
- pipeline: readFileSync(join(projectRoot, PIPELINE_CONFIG_PATH), "utf8"),
799
- profiles: readFileSync(join(projectRoot, PROFILES_CONFIG_PATH), "utf8"),
800
- runners: readFileSync(join(projectRoot, RUNNERS_CONFIG_PATH), "utf8")
801
- }, projectRoot, void 0, options);
874
+ return loadPackagePipelineConfig(projectRoot, options);
802
875
  }
803
- function tryLoadPipelineConfig(projectRoot, options = {}) {
804
- return loadPipelineConfig(projectRoot, options);
876
+ function loadPackagePipelineConfig(projectRoot, options = {}) {
877
+ return parsePipelineConfigParts({
878
+ pipeline: PACKAGE_DEFAULT_PIPELINE_YAML,
879
+ profiles: PACKAGE_DEFAULT_PROFILES_YAML,
880
+ runners: PACKAGE_DEFAULT_RUNNERS_YAML
881
+ }, projectRoot, {
882
+ pipeline: "@oisincoveney/pipeline/defaults/pipeline.yaml",
883
+ profiles: "@oisincoveney/pipeline/defaults/profiles.yaml",
884
+ runners: "@oisincoveney/pipeline/defaults/runners.yaml"
885
+ }, options);
805
886
  }
806
887
  function parsePipelineConfigYaml(source, sourcePath = PIPELINE_CONFIG_PATH, projectRoot) {
807
888
  return parsePipelineConfigParts({
@@ -946,10 +1027,13 @@ function validateActor(label, path, actor, runner, config, issues, projectRoot,
946
1027
  validatePath(`${path}.instructions.path`, actor.instructions.path, projectRoot, issues, options);
947
1028
  validateReferences(`${path}.rules`, actor.rules, config.rules, "rule", issues);
948
1029
  validateReferences(`${path}.skills`, actor.skills, config.skills, "skill", issues);
949
- validateReferences(`${path}.mcp_servers`, actor.mcp_servers, config.mcp_gateway ? {
950
- ...config.mcp_servers,
951
- [PIPELINE_GATEWAY_SERVER_ID]: {}
952
- } : config.mcp_servers, "MCP server", issues);
1030
+ validateReferences(`${path}.mcp_servers`, actor.mcp_servers, config.mcp_gateway ? { [PIPELINE_GATEWAY_SERVER_ID]: {} } : config.mcp_servers, "MCP server", issues);
1031
+ if (config.mcp_gateway) {
1032
+ for (const serverId of actor.mcp_servers ?? []) if (serverId !== PIPELINE_GATEWAY_SERVER_ID) issues.push({
1033
+ path: `${path}.mcp_servers`,
1034
+ message: `${path}.mcp_servers must only reference ${PIPELINE_GATEWAY_SERVER_ID} when mcp_gateway is configured`
1035
+ });
1036
+ }
953
1037
  validateBooleanCapability(`${path}.rules`, actor.rules, runner.capabilities.rules, "rules", issues);
954
1038
  validateBooleanCapability(`${path}.skills`, actor.skills, runner.capabilities.skills, "skills", issues);
955
1039
  validateBooleanCapability(`${path}.mcp_servers`, actor.mcp_servers, runner.capabilities.mcp_servers, "MCP servers", issues);
@@ -1048,6 +1132,7 @@ function validateListCapability(path, requested, supported, label, issues) {
1048
1132
  }
1049
1133
  function validatePath(path, value, projectRoot, issues, options = {}) {
1050
1134
  if (!(value && projectRoot)) return;
1135
+ if (standardOutputSchemaNameFromPath(value)) return;
1051
1136
  if (!existsSync(resolveFileReference(projectRoot, value))) {
1052
1137
  if (options.allowMissingLintFileReferences && isLintableMissingFileReferencePath(path)) return;
1053
1138
  issues.push({
@@ -1066,4 +1151,4 @@ function validationError(issues) {
1066
1151
  return new PipelineConfigError("PIPELINE_CONFIG_VALIDATION_ERROR", ["Invalid pipeline config:", ...issues.map((issue) => issue.path ? `- ${issue.path}: ${issue.message}` : `- ${issue.message}`)].join("\n"), issues);
1067
1152
  }
1068
1153
  //#endregion
1069
- export { PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfigError, RUNNERS_CONFIG_PATH, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
1154
+ export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfigError, RUNNERS_CONFIG_PATH, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, validatePipelineConfig, workflowSchema };