@oisincoveney/pipeline 1.19.0 → 1.20.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/README.md +8 -2
- package/dist/commands/runner-job-command.js +6 -2
- package/dist/config.d.ts +16 -4
- package/dist/config.js +54 -50
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp/gateway.js +1 -1
- package/dist/path-refs.js +1 -1
- package/dist/pipeline-runtime.js +27 -1
- package/dist/runner-job/delivery.js +45 -17
- package/dist/runner-job/run.js +123 -46
- package/dist/runner-job-contract.d.ts +6 -19
- package/dist/runner-job-contract.js +5 -25
- package/dist/runner-job-k8s.d.ts +66 -0
- package/dist/runner-job-k8s.js +144 -0
- package/dist/runner.js +2 -2
- package/dist/runtime/agent-node/agent-node.js +22 -0
- package/dist/runtime/hooks/hooks.js +1 -1
- package/dist/runtime/worktrees/worktrees.js +11 -1
- package/dist/schedule-planner.js +7 -2
- package/docs/operator-guide.md +74 -33
- package/docs/pipeline-console-runner-contract.md +25 -16
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -124,8 +124,11 @@ storage, Kueue discovery, and UI rendering. This package owns the in-container
|
|
|
124
124
|
translation, authenticated event posting, signal cancellation, and final event
|
|
125
125
|
flushing.
|
|
126
126
|
|
|
127
|
-
The console starts the image with
|
|
128
|
-
|
|
127
|
+
The console starts the image with the payload as a mounted ConfigMap file and
|
|
128
|
+
the event auth token as a mounted Secret file. The runner reads the payload
|
|
129
|
+
from `--payload-file` and reads the event auth token from the file path
|
|
130
|
+
specified in `events.authTokenFile`. No environment variables are used for
|
|
131
|
+
payload or auth token delivery. The payload contract is documented in
|
|
129
132
|
[`docs/pipeline-console-runner-contract.md`](docs/pipeline-console-runner-contract.md).
|
|
130
133
|
The executable contract is exported from
|
|
131
134
|
`@oisincoveney/pipeline/runner-job-contract` for payload construction,
|
|
@@ -410,3 +413,6 @@ bun run check
|
|
|
410
413
|
bun run test
|
|
411
414
|
bun run build:cli
|
|
412
415
|
```
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
Runner Kubernetes Jobs can be generated with `buildRunnerJobK8sManifest` from `@oisincoveney/pipeline/runner-job-k8s`.
|
|
@@ -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
|
|
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,6 +10,10 @@ 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
|
+
declare const DEFAULT_RUNNER_JOB_GIT_COMMITTER: {
|
|
14
|
+
readonly email: "git@oisin.ee";
|
|
15
|
+
readonly name: "oisin-bot";
|
|
16
|
+
};
|
|
13
17
|
type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "PIPELINE_CONFIG_MISSING" | "PIPELINE_CONFIG_PARSE_ERROR" | "PIPELINE_CONFIG_VALIDATION_ERROR";
|
|
14
18
|
interface PipelineConfigIssue {
|
|
15
19
|
message: string;
|
|
@@ -223,8 +227,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
223
227
|
policy: z.ZodOptional<z.ZodObject<{
|
|
224
228
|
commands: z.ZodOptional<z.ZodEnum<{
|
|
225
229
|
allow: "allow";
|
|
226
|
-
deny: "deny";
|
|
227
230
|
"trusted-only": "trusted-only";
|
|
231
|
+
deny: "deny";
|
|
228
232
|
}>>;
|
|
229
233
|
modules: z.ZodOptional<z.ZodEnum<{
|
|
230
234
|
allow: "allow";
|
|
@@ -279,10 +283,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
279
283
|
}, z.core.$strict>>;
|
|
280
284
|
output: z.ZodOptional<z.ZodObject<{
|
|
281
285
|
format: z.ZodEnum<{
|
|
282
|
-
json_schema: "json_schema";
|
|
283
286
|
text: "text";
|
|
284
287
|
json: "json";
|
|
285
288
|
jsonl: "jsonl";
|
|
289
|
+
json_schema: "json_schema";
|
|
286
290
|
}>;
|
|
287
291
|
repair: z.ZodOptional<z.ZodObject<{
|
|
288
292
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -298,6 +302,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
298
302
|
implementation: "implementation";
|
|
299
303
|
}>>>;
|
|
300
304
|
skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
305
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
301
306
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
302
307
|
read: "read";
|
|
303
308
|
list: "list";
|
|
@@ -322,6 +327,12 @@ declare const configSchema: z.ZodObject<{
|
|
|
322
327
|
required: z.ZodDefault<z.ZodBoolean>;
|
|
323
328
|
}, z.core.$strict>>>;
|
|
324
329
|
}, z.core.$strict>>;
|
|
330
|
+
git: z.ZodDefault<z.ZodObject<{
|
|
331
|
+
committer: z.ZodDefault<z.ZodObject<{
|
|
332
|
+
email: z.ZodDefault<z.ZodString>;
|
|
333
|
+
name: z.ZodDefault<z.ZodString>;
|
|
334
|
+
}, z.core.$strict>>;
|
|
335
|
+
}, z.core.$strict>>;
|
|
325
336
|
}, z.core.$strict>>;
|
|
326
337
|
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
327
338
|
path: z.ZodString;
|
|
@@ -340,10 +351,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
340
351
|
disabled: "disabled";
|
|
341
352
|
}>>>;
|
|
342
353
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
343
|
-
json_schema: "json_schema";
|
|
344
354
|
text: "text";
|
|
345
355
|
json: "json";
|
|
346
356
|
jsonl: "jsonl";
|
|
357
|
+
json_schema: "json_schema";
|
|
347
358
|
}>>>;
|
|
348
359
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
349
360
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -409,9 +420,10 @@ interface PipelineConfigValidationOptions {
|
|
|
409
420
|
allowMissingLintFileReferences?: boolean;
|
|
410
421
|
}
|
|
411
422
|
declare function loadPipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
423
|
+
declare function loadPackagePipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
412
424
|
declare function tryLoadPipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig | null;
|
|
413
425
|
declare function parsePipelineConfigYaml(source: string, sourcePath?: string, projectRoot?: string): PipelineConfig;
|
|
414
426
|
declare function parsePipelineConfigParts(sources: PipelineConfigParts, projectRoot?: string, sourcePaths?: PipelineConfigParts, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
415
427
|
declare function validatePipelineConfig(rawConfig: PipelineConfig, projectRoot?: string, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
416
428
|
//#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 };
|
|
429
|
+
export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, GateKind, HookEvent, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, SchedulingRole, WorkflowNodeKind, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
|
package/dist/config.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { resolveFileReference } from "./path-refs.js";
|
|
2
|
-
import { existsSync
|
|
3
|
-
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
4
3
|
import { parseDocument } from "yaml";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
//#region src/config.ts
|
|
7
6
|
const PIPELINE_CONFIG_PATH = ".pipeline/pipeline.yaml";
|
|
8
7
|
const RUNNERS_CONFIG_PATH = ".pipeline/runners.yaml";
|
|
9
8
|
const PROFILES_CONFIG_PATH = ".pipeline/profiles.yaml";
|
|
10
|
-
const LEGACY_CONFIG_PATH = ".pipeline/config.toml";
|
|
11
9
|
const ID_RE = /^[a-z][a-z0-9-]*$/;
|
|
12
10
|
const RUNNER_TYPES = [
|
|
13
11
|
"codex",
|
|
@@ -57,6 +55,10 @@ const RETRY_REASONS = [
|
|
|
57
55
|
const SCHEDULE_BASELINES = ["epic", "pipe"];
|
|
58
56
|
const SCHEDULING_ROLES = ["coverage", "implementation"];
|
|
59
57
|
const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
|
|
58
|
+
const DEFAULT_RUNNER_JOB_GIT_COMMITTER = {
|
|
59
|
+
email: "git@oisin.ee",
|
|
60
|
+
name: "oisin-bot"
|
|
61
|
+
};
|
|
60
62
|
const PACKAGE_DEFAULT_RUNNERS_YAML = `version: 1
|
|
61
63
|
runners:
|
|
62
64
|
codex:
|
|
@@ -73,7 +75,7 @@ runners:
|
|
|
73
75
|
output_formats: [text, json, jsonl, json_schema]
|
|
74
76
|
opencode:
|
|
75
77
|
type: opencode
|
|
76
|
-
model:
|
|
78
|
+
model: openai/gpt-5.5
|
|
77
79
|
capabilities:
|
|
78
80
|
native_subagents: true
|
|
79
81
|
rules: true
|
|
@@ -113,7 +115,8 @@ profiles:
|
|
|
113
115
|
pipeline-researcher:
|
|
114
116
|
runner: codex
|
|
115
117
|
description: Research the requested task and produce structured findings.
|
|
116
|
-
instructions: { inline: "Inspect first-party source, tests, docs, and task context
|
|
118
|
+
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." }
|
|
119
|
+
timeout_ms: 900000
|
|
117
120
|
mcp_servers: [pipeline-gateway]
|
|
118
121
|
tools: [read, list, grep, glob, bash]
|
|
119
122
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
@@ -163,7 +166,7 @@ profiles:
|
|
|
163
166
|
runner: codex
|
|
164
167
|
scheduling_roles: [coverage]
|
|
165
168
|
description: Audit the finished change against every acceptance criterion.
|
|
166
|
-
instructions: { inline:
|
|
169
|
+
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
170
|
mcp_servers: [pipeline-gateway]
|
|
168
171
|
tools: [read, list, grep, glob, bash]
|
|
169
172
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
@@ -181,7 +184,7 @@ profiles:
|
|
|
181
184
|
runner: codex
|
|
182
185
|
scheduling_roles: [coverage]
|
|
183
186
|
description: Verify checks, implementation fit, and final evidence.
|
|
184
|
-
instructions: { inline:
|
|
187
|
+
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
188
|
mcp_servers: [pipeline-gateway]
|
|
186
189
|
tools: [read, list, grep, glob, bash]
|
|
187
190
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
@@ -213,7 +216,7 @@ hooks:
|
|
|
213
216
|
functions:
|
|
214
217
|
generated-defaults-audit:
|
|
215
218
|
kind: command
|
|
216
|
-
command: [node, -e, "process.
|
|
219
|
+
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
220
|
trusted: true
|
|
218
221
|
timeout_ms: 5000
|
|
219
222
|
output_limit_bytes: 4096
|
|
@@ -340,6 +343,10 @@ workflows:
|
|
|
340
343
|
kind: agent
|
|
341
344
|
profile: pipeline-thermo-nuclear-reviewer
|
|
342
345
|
needs: [merge]
|
|
346
|
+
gates:
|
|
347
|
+
- id: review-verdict
|
|
348
|
+
kind: verdict
|
|
349
|
+
target: stdout
|
|
343
350
|
`;
|
|
344
351
|
var PipelineConfigError = class extends Error {
|
|
345
352
|
code;
|
|
@@ -515,6 +522,7 @@ const profileSchema = z.object({
|
|
|
515
522
|
runner: z.string(),
|
|
516
523
|
scheduling_roles: z.array(z.enum(SCHEDULING_ROLES)).optional(),
|
|
517
524
|
skills: z.array(z.string()).optional(),
|
|
525
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
518
526
|
tools: z.array(z.enum(TOOL_NAMES)).optional()
|
|
519
527
|
}).strict();
|
|
520
528
|
const orchestratorSchema = z.object({ profile: z.string() }).strict();
|
|
@@ -654,10 +662,18 @@ const runnerJobEnvironmentSchema = z.object({
|
|
|
654
662
|
setup: z.array(runnerJobCommandSchema).default([]),
|
|
655
663
|
smoke: z.array(runnerJobCommandSchema).default([])
|
|
656
664
|
}).strict();
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
})
|
|
665
|
+
const runnerJobGitCommitterSchema = z.object({
|
|
666
|
+
email: z.string().email().default(DEFAULT_RUNNER_JOB_GIT_COMMITTER.email),
|
|
667
|
+
name: z.string().min(1).default(DEFAULT_RUNNER_JOB_GIT_COMMITTER.name)
|
|
668
|
+
}).strict();
|
|
669
|
+
const runnerJobGitSchema = z.object({ committer: runnerJobGitCommitterSchema.default(DEFAULT_RUNNER_JOB_GIT_COMMITTER) }).strict();
|
|
670
|
+
const runnerJobConfigSchema = z.object({
|
|
671
|
+
environment: runnerJobEnvironmentSchema.default({
|
|
672
|
+
setup: [],
|
|
673
|
+
smoke: []
|
|
674
|
+
}),
|
|
675
|
+
git: runnerJobGitSchema.default({ committer: DEFAULT_RUNNER_JOB_GIT_COMMITTER })
|
|
676
|
+
}).strict();
|
|
661
677
|
const runnersFileSchema = z.object({
|
|
662
678
|
runners: strictRecord(runnerSchema).default({}),
|
|
663
679
|
version: z.literal(1)
|
|
@@ -678,10 +694,13 @@ const pipelineFileSchema = z.object({
|
|
|
678
694
|
on: {}
|
|
679
695
|
}),
|
|
680
696
|
orchestrator: orchestratorSchema,
|
|
681
|
-
runner_job: runnerJobConfigSchema.default({
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
697
|
+
runner_job: runnerJobConfigSchema.default({
|
|
698
|
+
environment: {
|
|
699
|
+
setup: [],
|
|
700
|
+
smoke: []
|
|
701
|
+
},
|
|
702
|
+
git: { committer: DEFAULT_RUNNER_JOB_GIT_COMMITTER }
|
|
703
|
+
}),
|
|
685
704
|
schedules: strictRecord(schedulePolicySchema).default({}),
|
|
686
705
|
task_context: taskContextResolverSchema.optional(),
|
|
687
706
|
workflows: strictRecord(workflowSchema).default({}),
|
|
@@ -698,10 +717,13 @@ const configSchema = z.object({
|
|
|
698
717
|
mcp_servers: strictRecord(mcpServerSchema).default({}),
|
|
699
718
|
orchestrator: orchestratorSchema,
|
|
700
719
|
profiles: strictRecord(profileSchema).default({}),
|
|
701
|
-
runner_job: runnerJobConfigSchema.default({
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
720
|
+
runner_job: runnerJobConfigSchema.default({
|
|
721
|
+
environment: {
|
|
722
|
+
setup: [],
|
|
723
|
+
smoke: []
|
|
724
|
+
},
|
|
725
|
+
git: { committer: DEFAULT_RUNNER_JOB_GIT_COMMITTER }
|
|
726
|
+
}),
|
|
705
727
|
rules: strictRecord(pathRefSchema).default({}),
|
|
706
728
|
runners: strictRecord(runnerSchema).default({}),
|
|
707
729
|
schedules: strictRecord(schedulePolicySchema).default({}),
|
|
@@ -769,36 +791,18 @@ function addConfigSchemaIssue(ctx, path, message) {
|
|
|
769
791
|
});
|
|
770
792
|
}
|
|
771
793
|
function loadPipelineConfig(projectRoot, options = {}) {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
}
|
|
794
|
+
return loadPackagePipelineConfig(projectRoot, options);
|
|
795
|
+
}
|
|
796
|
+
function loadPackagePipelineConfig(projectRoot, options = {}) {
|
|
797
797
|
return parsePipelineConfigParts({
|
|
798
|
-
pipeline:
|
|
799
|
-
profiles:
|
|
800
|
-
runners:
|
|
801
|
-
}, projectRoot,
|
|
798
|
+
pipeline: PACKAGE_DEFAULT_PIPELINE_YAML,
|
|
799
|
+
profiles: PACKAGE_DEFAULT_PROFILES_YAML,
|
|
800
|
+
runners: PACKAGE_DEFAULT_RUNNERS_YAML
|
|
801
|
+
}, projectRoot, {
|
|
802
|
+
pipeline: "@oisincoveney/pipeline/defaults/pipeline.yaml",
|
|
803
|
+
profiles: "@oisincoveney/pipeline/defaults/profiles.yaml",
|
|
804
|
+
runners: "@oisincoveney/pipeline/defaults/runners.yaml"
|
|
805
|
+
}, options);
|
|
802
806
|
}
|
|
803
807
|
function tryLoadPipelineConfig(projectRoot, options = {}) {
|
|
804
808
|
return loadPipelineConfig(projectRoot, options);
|
|
@@ -1066,4 +1070,4 @@ function validationError(issues) {
|
|
|
1066
1070
|
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
1071
|
}
|
|
1068
1072
|
//#endregion
|
|
1069
|
-
export { PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfigError, RUNNERS_CONFIG_PATH, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
|
|
1073
|
+
export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfigError, RUNNERS_CONFIG_PATH, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ interface PipeOptions {
|
|
|
9
9
|
workflow?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Config-driven `pipe` entrypoint.
|
|
13
|
-
*
|
|
12
|
+
* Config-driven `pipe` entrypoint. Package-owned defaults are the source of
|
|
13
|
+
* truth; repo-local pipeline files are only scaffolding fixtures.
|
|
14
14
|
*/
|
|
15
15
|
declare function pipe(description: string, options?: PipeOptions): Promise<void>;
|
|
16
16
|
interface DoctorCheck {
|
package/dist/index.js
CHANGED
|
@@ -18,8 +18,8 @@ import { execa } from "execa";
|
|
|
18
18
|
const PATH_SEPARATOR_RE = /[\\/]/;
|
|
19
19
|
const LINE_RE = /\r?\n/;
|
|
20
20
|
/**
|
|
21
|
-
* Config-driven `pipe` entrypoint.
|
|
22
|
-
*
|
|
21
|
+
* Config-driven `pipe` entrypoint. Package-owned defaults are the source of
|
|
22
|
+
* truth; repo-local pipeline files are only scaffolding fixtures.
|
|
23
23
|
*/
|
|
24
24
|
function pipe(description, options = {}) {
|
|
25
25
|
try {
|
package/dist/mcp/gateway.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
2
|
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
//#region src/mcp/gateway.ts
|
|
6
6
|
const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
|
package/dist/path-refs.js
CHANGED
package/dist/pipeline-runtime.js
CHANGED
|
@@ -19,7 +19,7 @@ import { dispatchHooks } from "./runtime/hooks/hooks.js";
|
|
|
19
19
|
import "./runtime/hooks/index.js";
|
|
20
20
|
import { executeParallelNode } from "./runtime/parallel-node/parallel-node.js";
|
|
21
21
|
import "./runtime/parallel-node/index.js";
|
|
22
|
-
import { prepareWorkflowNodeWorktree, removeWorkflowNodeWorktree, workflowBaseSha } from "./runtime/worktrees/worktrees.js";
|
|
22
|
+
import { commitWorkflowNodeWorktree, prepareWorkflowNodeWorktree, removeWorkflowNodeWorktree, workflowBaseSha } from "./runtime/worktrees/worktrees.js";
|
|
23
23
|
import "./runtime/worktrees/index.js";
|
|
24
24
|
import { nodeExecutionMachine } from "./runtime-machines/node-machine.js";
|
|
25
25
|
import { workflowSchedulerMachine } from "./runtime-machines/workflow-machine.js";
|
|
@@ -572,10 +572,36 @@ async function executeWorkflowNode(node, context) {
|
|
|
572
572
|
childContext.inheritedOutputNodeIds = new Set(childContext.lastOutputByNode.keys());
|
|
573
573
|
const result = await runPipelineWithContext(childContext);
|
|
574
574
|
context.agentInvocations.push(...result.agentInvocations);
|
|
575
|
+
if (result.outcome === "PASS" && worktree.worktreePath) try {
|
|
576
|
+
worktree.commitSha = await commitWorkflowNodeWorktree(worktree.worktreePath, node.id, context.config.runner_job.git.committer);
|
|
577
|
+
} catch (error) {
|
|
578
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
579
|
+
return {
|
|
580
|
+
evidence: [
|
|
581
|
+
`workflow '${result.plan.workflowId}' passed`,
|
|
582
|
+
`workflow worktree commit failed: ${message}`,
|
|
583
|
+
`inspect workflow worktree: cd ${worktree.worktreePath}`
|
|
584
|
+
],
|
|
585
|
+
exitCode: 1,
|
|
586
|
+
output: JSON.stringify({
|
|
587
|
+
baseSha: worktree.baseSha,
|
|
588
|
+
branch: worktree.branch,
|
|
589
|
+
commitSha: worktree.commitSha ?? null,
|
|
590
|
+
nodeResults: result.nodes.map((child) => ({
|
|
591
|
+
nodeId: child.nodeId,
|
|
592
|
+
status: child.status
|
|
593
|
+
})),
|
|
594
|
+
status: "FAIL",
|
|
595
|
+
worktreePath: worktree.worktreePath,
|
|
596
|
+
workflowId: result.plan.workflowId
|
|
597
|
+
})
|
|
598
|
+
};
|
|
599
|
+
}
|
|
575
600
|
if (result.outcome === "PASS" && worktree.worktreePath && !shouldPreserveWorkflowNodeWorktree(node, context)) await removeWorkflowNodeWorktree(worktree.worktreePath);
|
|
576
601
|
const output = JSON.stringify({
|
|
577
602
|
baseSha: worktree.baseSha,
|
|
578
603
|
branch: worktree.branch,
|
|
604
|
+
...worktree.worktreePath ? { commitSha: worktree.commitSha ?? null } : {},
|
|
579
605
|
nodeResults: result.nodes.map((child) => ({
|
|
580
606
|
nodeId: child.nodeId,
|
|
581
607
|
status: child.status
|
|
@@ -1,30 +1,36 @@
|
|
|
1
|
+
import { DEFAULT_RUNNER_JOB_GIT_COMMITTER } from "../config.js";
|
|
1
2
|
import { execa } from "execa";
|
|
3
|
+
import simpleGit$1 from "simple-git";
|
|
2
4
|
//#region src/runner-job/delivery.ts
|
|
3
5
|
const GITHUB_HTTPS_REPOSITORY_RE = /^https:\/\/github\.com\/([^/]+\/[^/.]+)(?:\.git)?$/;
|
|
4
6
|
const GITHUB_SSH_REPOSITORY_RE = /^git@github\.com:([^/]+\/[^/.]+)(?:\.git)?$/;
|
|
7
|
+
const deliverGitBranch = async (options) => {
|
|
8
|
+
const git = createDeliveryGitClient(options);
|
|
9
|
+
const branch = options.branch ?? await currentBranch(git);
|
|
10
|
+
const status = await git.status();
|
|
11
|
+
let commitSha = null;
|
|
12
|
+
if (status.files.length > 0) {
|
|
13
|
+
await git.add(["--all"]);
|
|
14
|
+
await configureCommitter(git, options.committer ?? DEFAULT_RUNNER_JOB_GIT_COMMITTER);
|
|
15
|
+
await git.commit(deliveryCommitMessage(options.payload));
|
|
16
|
+
if ((await git.status()).files.length > 0) throw new Error("Runner job delivery requires all worktree changes to be committed");
|
|
17
|
+
commitSha = (await git.revparse(["HEAD"])).trim();
|
|
18
|
+
}
|
|
19
|
+
for (const branchToPush of await deliveryBranches(git, branch, options.payload.run.id)) await git.push("origin", branchToPush, ["--set-upstream"]);
|
|
20
|
+
return {
|
|
21
|
+
branch,
|
|
22
|
+
commitSha,
|
|
23
|
+
pushed: true
|
|
24
|
+
};
|
|
25
|
+
};
|
|
5
26
|
const createPullRequest = async (options) => {
|
|
6
27
|
if (!options.payload.delivery.pullRequest) return null;
|
|
7
28
|
const repository = options.payload.repository;
|
|
8
29
|
const env = compactEnv(options.env);
|
|
9
30
|
const runCommand = options.runCommand ?? runDeliveryCommand;
|
|
10
|
-
const branch =
|
|
11
|
-
cwd: options.worktreePath,
|
|
12
|
-
env,
|
|
13
|
-
stdin: "ignore"
|
|
14
|
-
})).stdout.trim();
|
|
15
|
-
if (!branch) throw new Error("Pull request delivery requires a checked-out branch");
|
|
31
|
+
const branch = options.branch ?? await currentBranch(createDeliveryGitClient(options));
|
|
16
32
|
const headOwner = options.env.PIPELINE_PR_HEAD_OWNER?.trim() || "oisin-bot";
|
|
17
33
|
const repositoryName = githubRepositoryName(repository.url);
|
|
18
|
-
await runCommand("git", [
|
|
19
|
-
"push",
|
|
20
|
-
"--set-upstream",
|
|
21
|
-
"origin",
|
|
22
|
-
branch
|
|
23
|
-
], {
|
|
24
|
-
cwd: options.worktreePath,
|
|
25
|
-
env,
|
|
26
|
-
stdin: "ignore"
|
|
27
|
-
});
|
|
28
34
|
const url = (await runCommand("gh", [
|
|
29
35
|
"pr",
|
|
30
36
|
"create",
|
|
@@ -43,6 +49,28 @@ const createPullRequest = async (options) => {
|
|
|
43
49
|
return url ? { url } : null;
|
|
44
50
|
};
|
|
45
51
|
const runDeliveryCommand = (command, args, options) => execa(command, args, options);
|
|
52
|
+
function createDeliveryGitClient(options) {
|
|
53
|
+
return options.createGitClient?.(options.worktreePath) ?? simpleGit$1({ baseDir: options.worktreePath });
|
|
54
|
+
}
|
|
55
|
+
async function currentBranch(git) {
|
|
56
|
+
const branch = (await git.branch()).current.trim();
|
|
57
|
+
if (!branch) throw new Error("Runner job delivery requires a checked-out branch");
|
|
58
|
+
return branch;
|
|
59
|
+
}
|
|
60
|
+
async function deliveryBranches(git, current, runId) {
|
|
61
|
+
const branches = await git.branchLocal();
|
|
62
|
+
return [...new Set([current, ...Object.keys(branches.branches).map((branch) => branch.trim()).filter((branch) => isRunScopedBranch(branch, runId))])];
|
|
63
|
+
}
|
|
64
|
+
function isRunScopedBranch(branch, runId) {
|
|
65
|
+
return branch.startsWith(`${runId}/`) || branch === `runs/integration/${runId}`;
|
|
66
|
+
}
|
|
67
|
+
function deliveryCommitMessage(payload) {
|
|
68
|
+
return `pipeline: ${(payload.task.kind === "ticket" ? payload.task.id.trim() : void 0) || payload.run.id}`;
|
|
69
|
+
}
|
|
70
|
+
async function configureCommitter(git, committer) {
|
|
71
|
+
await git.addConfig("user.name", committer.name, false, "local");
|
|
72
|
+
await git.addConfig("user.email", committer.email, false, "local");
|
|
73
|
+
}
|
|
46
74
|
function compactEnv(env) {
|
|
47
75
|
return Object.fromEntries(Object.entries(env).filter((entry) => Boolean(entry[1])));
|
|
48
76
|
}
|
|
@@ -54,4 +82,4 @@ function githubRepositoryName(repositoryUrl) {
|
|
|
54
82
|
throw new Error("Pull request delivery requires a GitHub repository URL");
|
|
55
83
|
}
|
|
56
84
|
//#endregion
|
|
57
|
-
export { createPullRequest };
|
|
85
|
+
export { createPullRequest, deliverGitBranch };
|