@oisincoveney/pipeline 1.14.0 → 1.15.1
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 +15 -2
- package/dist/config.d.ts +73 -38
- package/dist/config.js +92 -31
- package/dist/hooks.d.ts +60 -0
- package/dist/hooks.js +28 -0
- package/dist/index.js +5 -5
- package/dist/install-commands.js +2 -5
- package/dist/pipeline-init.js +28 -24
- package/dist/pipeline-runtime.d.ts +3 -213
- package/dist/pipeline-runtime.js +58 -1528
- package/dist/runner-job-contract.d.ts +21 -2
- package/dist/runner-job-contract.js +16 -0
- package/dist/runtime/agent-node/agent-node.js +271 -0
- package/dist/runtime/agent-node/index.js +2 -0
- package/dist/runtime/builtins/builtins.js +48 -0
- package/dist/runtime/builtins/index.js +2 -0
- package/dist/runtime/changed-files/changed-files.js +34 -0
- package/dist/runtime/changed-files/index.js +2 -0
- package/dist/runtime/command-executor/command-executor.js +53 -0
- package/dist/runtime/command-executor/index.js +2 -0
- package/dist/runtime/context/context.js +93 -0
- package/dist/runtime/context/index.js +2 -0
- package/dist/runtime/contracts/contracts.d.ts +229 -0
- package/dist/runtime/contracts/index.d.ts +1 -0
- package/dist/runtime/drain-merge/drain-merge.js +154 -0
- package/dist/runtime/drain-merge/index.js +2 -0
- package/dist/runtime/events/events.js +259 -0
- package/dist/runtime/events/index.js +2 -0
- package/dist/runtime/gates/gates.js +296 -0
- package/dist/runtime/gates/index.js +2 -0
- package/dist/runtime/hooks/hooks.js +261 -0
- package/dist/runtime/hooks/index.js +2 -0
- package/dist/runtime/json-validation/index.js +2 -0
- package/dist/runtime/json-validation/json-validation.js +82 -0
- package/dist/runtime/parallel-node/index.js +2 -0
- package/dist/runtime/parallel-node/parallel-node.js +105 -0
- package/dist/runtime/worktrees/index.js +2 -0
- package/dist/runtime/worktrees/worktrees.js +55 -0
- package/dist/runtime-machines/contracts.d.ts +1 -2
- package/dist/runtime-machines/node-machine.d.ts +1 -0
- package/dist/runtime-machines/workflow-machine.d.ts +1 -0
- package/dist/runtime-machines/workflow-machine.js +146 -93
- package/dist/schedule-planner.d.ts +0 -25
- package/dist/schedule-planner.js +95 -11
- package/dist/task-ref.js +12 -16
- package/dist/workflow-planner.d.ts +0 -1
- package/dist/workflow-planner.js +0 -1
- package/docs/config-architecture.md +3 -2
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -188,7 +188,10 @@ default_workflow: default
|
|
|
188
188
|
|
|
189
189
|
orchestrator:
|
|
190
190
|
profile: orchestrator
|
|
191
|
-
|
|
191
|
+
|
|
192
|
+
hooks:
|
|
193
|
+
functions: {}
|
|
194
|
+
on: {}
|
|
192
195
|
|
|
193
196
|
workflows:
|
|
194
197
|
default:
|
|
@@ -353,7 +356,7 @@ runners:
|
|
|
353
356
|
- JSON Schema gates validate structure only. Use `verdict` and `acceptance`
|
|
354
357
|
gates to enforce semantic pass/fail and per-criterion coverage.
|
|
355
358
|
- Command hooks support host policy controls, sanitized environments, timeouts,
|
|
356
|
-
output limits, and JSON
|
|
359
|
+
output limits, and JSON file input/result payloads.
|
|
357
360
|
|
|
358
361
|
## App-Facing API
|
|
359
362
|
|
|
@@ -377,6 +380,16 @@ import {
|
|
|
377
380
|
} from "@oisincoveney/pipeline/runtime";
|
|
378
381
|
```
|
|
379
382
|
|
|
383
|
+
Hook modules can import the typed helper and result contract:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
import {
|
|
387
|
+
defineHook,
|
|
388
|
+
type HookContext,
|
|
389
|
+
type HookResult,
|
|
390
|
+
} from "@oisincoveney/pipeline/hooks";
|
|
391
|
+
```
|
|
392
|
+
|
|
380
393
|
Runner Job producers can import the shared payload contract:
|
|
381
394
|
|
|
382
395
|
```ts
|
package/dist/config.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ declare const RUNNERS_CONFIG_PATH = ".pipeline/runners.yaml";
|
|
|
6
6
|
declare const PROFILES_CONFIG_PATH = ".pipeline/profiles.yaml";
|
|
7
7
|
declare const RUNNER_TYPES: readonly ["codex", "opencode", "command"];
|
|
8
8
|
declare const NODE_KINDS: readonly ["agent", "command", "builtin", "group", "parallel", "workflow"];
|
|
9
|
-
declare const HOOK_EVENTS: readonly ["workflow.start", "workflow.success", "workflow.failure", "workflow.complete", "node.start", "node.success", "node.error", "gate.failure"];
|
|
9
|
+
declare const HOOK_EVENTS: readonly ["workflow.start", "workflow.success", "workflow.failure", "workflow.complete", "node.start", "node.success", "node.error", "node.finish", "gate.failure"];
|
|
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
|
type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "PIPELINE_CONFIG_MISSING" | "PIPELINE_CONFIG_PARSE_ERROR" | "PIPELINE_CONFIG_VALIDATION_ERROR";
|
|
@@ -88,7 +88,6 @@ declare const workflowNodeBaseSchema: z.ZodObject<{
|
|
|
88
88
|
field: z.ZodOptional<z.ZodString>;
|
|
89
89
|
kind: z.ZodLiteral<"verdict">;
|
|
90
90
|
}, z.core.$strict>], "kind">>>;
|
|
91
|
-
hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
92
91
|
id: z.ZodString;
|
|
93
92
|
needs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
94
93
|
retries: z.ZodOptional<z.ZodObject<{
|
|
@@ -146,7 +145,6 @@ declare const workflowSchema: z.ZodObject<{
|
|
|
146
145
|
max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
|
|
147
146
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
148
147
|
}, z.core.$strict>>;
|
|
149
|
-
hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
150
148
|
nodes: z.ZodArray<z.ZodType<WorkflowNode, unknown, z.core.$ZodTypeInternals<WorkflowNode, unknown>>>;
|
|
151
149
|
}, z.core.$strict>;
|
|
152
150
|
declare const configSchema: z.ZodObject<{
|
|
@@ -164,36 +162,75 @@ declare const configSchema: z.ZodObject<{
|
|
|
164
162
|
}, z.core.$loose>>;
|
|
165
163
|
schedule: z.ZodString;
|
|
166
164
|
}, z.core.$strict>]>>>;
|
|
167
|
-
hooks: z.ZodDefault<z.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
165
|
+
hooks: z.ZodDefault<z.ZodObject<{
|
|
166
|
+
functions: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
167
|
+
kind: z.ZodLiteral<"module">;
|
|
168
|
+
module: z.ZodString;
|
|
169
|
+
permissions: z.ZodOptional<z.ZodObject<{
|
|
170
|
+
filesystem: z.ZodOptional<z.ZodEnum<{
|
|
171
|
+
"read-only": "read-only";
|
|
172
|
+
"workspace-write": "workspace-write";
|
|
173
|
+
}>>;
|
|
174
|
+
network: z.ZodOptional<z.ZodEnum<{
|
|
175
|
+
inherit: "inherit";
|
|
176
|
+
disabled: "disabled";
|
|
177
|
+
}>>;
|
|
178
|
+
}, z.core.$strict>>;
|
|
179
|
+
returns: z.ZodOptional<z.ZodObject<{
|
|
180
|
+
schema: z.ZodOptional<z.ZodString>;
|
|
181
|
+
}, z.core.$strict>>;
|
|
182
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
183
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
184
|
+
command: z.ZodArray<z.ZodString>;
|
|
185
|
+
env: z.ZodOptional<z.ZodObject<{
|
|
186
|
+
passthrough: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
187
|
+
set: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
188
|
+
}, z.core.$strict>>;
|
|
189
|
+
kind: z.ZodLiteral<"command">;
|
|
190
|
+
output_limit_bytes: z.ZodOptional<z.ZodNumber>;
|
|
191
|
+
protocol: z.ZodDefault<z.ZodObject<{
|
|
192
|
+
input: z.ZodLiteral<"file">;
|
|
193
|
+
result: z.ZodLiteral<"file">;
|
|
194
|
+
}, z.core.$strict>>;
|
|
195
|
+
returns: z.ZodOptional<z.ZodObject<{
|
|
196
|
+
schema: z.ZodOptional<z.ZodString>;
|
|
197
|
+
}, z.core.$strict>>;
|
|
198
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
199
|
+
trusted: z.ZodOptional<z.ZodBoolean>;
|
|
200
|
+
}, z.core.$strict>], "kind">>>;
|
|
201
|
+
on: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
202
|
+
failure: z.ZodDefault<z.ZodEnum<{
|
|
203
|
+
fail: "fail";
|
|
204
|
+
ignore: "ignore";
|
|
205
|
+
}>>;
|
|
206
|
+
function: z.ZodString;
|
|
207
|
+
id: z.ZodString;
|
|
208
|
+
result: z.ZodOptional<z.ZodObject<{
|
|
209
|
+
pass_to: z.ZodOptional<z.ZodEnum<{
|
|
210
|
+
downstream: "downstream";
|
|
211
|
+
}>>;
|
|
212
|
+
publish: z.ZodOptional<z.ZodBoolean>;
|
|
213
|
+
save_as: z.ZodOptional<z.ZodString>;
|
|
214
|
+
}, z.core.$strict>>;
|
|
215
|
+
where: z.ZodOptional<z.ZodObject<{
|
|
216
|
+
gate: z.ZodOptional<z.ZodString>;
|
|
217
|
+
node: z.ZodOptional<z.ZodString>;
|
|
218
|
+
workflow: z.ZodOptional<z.ZodString>;
|
|
219
|
+
}, z.core.$strict>>;
|
|
220
|
+
with: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
221
|
+
}, z.core.$strict>>>>;
|
|
222
|
+
policy: z.ZodOptional<z.ZodObject<{
|
|
223
|
+
commands: z.ZodOptional<z.ZodEnum<{
|
|
224
|
+
allow: "allow";
|
|
225
|
+
deny: "deny";
|
|
226
|
+
"trusted-only": "trusted-only";
|
|
227
|
+
}>>;
|
|
228
|
+
modules: z.ZodOptional<z.ZodEnum<{
|
|
229
|
+
allow: "allow";
|
|
230
|
+
deny: "deny";
|
|
231
|
+
}>>;
|
|
174
232
|
}, z.core.$strict>>;
|
|
175
|
-
|
|
176
|
-
"workflow.start": "workflow.start";
|
|
177
|
-
"workflow.success": "workflow.success";
|
|
178
|
-
"workflow.failure": "workflow.failure";
|
|
179
|
-
"workflow.complete": "workflow.complete";
|
|
180
|
-
"node.start": "node.start";
|
|
181
|
-
"node.success": "node.success";
|
|
182
|
-
"node.error": "node.error";
|
|
183
|
-
"gate.failure": "gate.failure";
|
|
184
|
-
}>;
|
|
185
|
-
kind: z.ZodEnum<{
|
|
186
|
-
builtin: "builtin";
|
|
187
|
-
command: "command";
|
|
188
|
-
}>;
|
|
189
|
-
output_limit_bytes: z.ZodOptional<z.ZodNumber>;
|
|
190
|
-
payload: z.ZodOptional<z.ZodEnum<{
|
|
191
|
-
stdin: "stdin";
|
|
192
|
-
}>>;
|
|
193
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
194
|
-
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
195
|
-
trusted: z.ZodOptional<z.ZodBoolean>;
|
|
196
|
-
}, z.core.$strict>>>;
|
|
233
|
+
}, z.core.$strict>>;
|
|
197
234
|
mcp_gateway: z.ZodOptional<z.ZodObject<{
|
|
198
235
|
default_profile: z.ZodOptional<z.ZodString>;
|
|
199
236
|
mode: z.ZodEnum<{
|
|
@@ -213,7 +250,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
213
250
|
url: z.ZodOptional<z.ZodString>;
|
|
214
251
|
}, z.core.$strict>>>;
|
|
215
252
|
orchestrator: z.ZodObject<{
|
|
216
|
-
hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
217
253
|
profile: z.ZodString;
|
|
218
254
|
}, z.core.$strict>;
|
|
219
255
|
profiles: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -241,10 +277,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
241
277
|
}, z.core.$strict>>;
|
|
242
278
|
output: z.ZodOptional<z.ZodObject<{
|
|
243
279
|
format: z.ZodEnum<{
|
|
280
|
+
json_schema: "json_schema";
|
|
244
281
|
text: "text";
|
|
245
282
|
json: "json";
|
|
246
283
|
jsonl: "jsonl";
|
|
247
|
-
json_schema: "json_schema";
|
|
248
284
|
}>;
|
|
249
285
|
repair: z.ZodOptional<z.ZodObject<{
|
|
250
286
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -257,7 +293,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
257
293
|
runner: z.ZodString;
|
|
258
294
|
skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
259
295
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
260
|
-
task: "task";
|
|
261
296
|
read: "read";
|
|
262
297
|
list: "list";
|
|
263
298
|
grep: "grep";
|
|
@@ -265,6 +300,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
265
300
|
bash: "bash";
|
|
266
301
|
edit: "edit";
|
|
267
302
|
write: "write";
|
|
303
|
+
task: "task";
|
|
268
304
|
}>>>;
|
|
269
305
|
}, z.core.$strict>>>;
|
|
270
306
|
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -284,15 +320,14 @@ declare const configSchema: z.ZodObject<{
|
|
|
284
320
|
disabled: "disabled";
|
|
285
321
|
}>>>;
|
|
286
322
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
323
|
+
json_schema: "json_schema";
|
|
287
324
|
text: "text";
|
|
288
325
|
json: "json";
|
|
289
326
|
jsonl: "jsonl";
|
|
290
|
-
json_schema: "json_schema";
|
|
291
327
|
}>>>;
|
|
292
328
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
293
329
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
294
330
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
295
|
-
task: "task";
|
|
296
331
|
read: "read";
|
|
297
332
|
list: "list";
|
|
298
333
|
grep: "grep";
|
|
@@ -300,6 +335,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
300
335
|
bash: "bash";
|
|
301
336
|
edit: "edit";
|
|
302
337
|
write: "write";
|
|
338
|
+
task: "task";
|
|
303
339
|
}>>>;
|
|
304
340
|
}, z.core.$strict>;
|
|
305
341
|
command: z.ZodOptional<z.ZodString>;
|
|
@@ -334,7 +370,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
334
370
|
max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
|
|
335
371
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
336
372
|
}, z.core.$strict>>;
|
|
337
|
-
hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
338
373
|
nodes: z.ZodArray<z.ZodType<WorkflowNode, unknown, z.core.$ZodTypeInternals<WorkflowNode, unknown>>>;
|
|
339
374
|
}, z.core.$strict>>>;
|
|
340
375
|
}, z.core.$strict>;
|
package/dist/config.js
CHANGED
|
@@ -22,6 +22,7 @@ const HOOK_EVENTS = [
|
|
|
22
22
|
"node.start",
|
|
23
23
|
"node.success",
|
|
24
24
|
"node.error",
|
|
25
|
+
"node.finish",
|
|
25
26
|
"gate.failure"
|
|
26
27
|
];
|
|
27
28
|
const TOOL_NAMES = [
|
|
@@ -229,27 +230,72 @@ const profileSchema = z.object({
|
|
|
229
230
|
skills: z.array(z.string()).optional(),
|
|
230
231
|
tools: z.array(z.enum(TOOL_NAMES)).optional()
|
|
231
232
|
}).strict();
|
|
232
|
-
const orchestratorSchema = z.object({
|
|
233
|
-
hooks: z.array(z.string()).optional(),
|
|
234
|
-
profile: z.string()
|
|
235
|
-
}).strict();
|
|
233
|
+
const orchestratorSchema = z.object({ profile: z.string() }).strict();
|
|
236
234
|
const hookEnvSchema = z.object({
|
|
237
235
|
passthrough: z.array(z.string()).optional(),
|
|
238
236
|
set: z.record(z.string(), z.string()).optional()
|
|
239
237
|
}).strict();
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
const hookPermissionsSchema = z.object({
|
|
239
|
+
filesystem: z.enum(FILESYSTEM_MODES).optional(),
|
|
240
|
+
network: z.enum(NETWORK_MODES).optional()
|
|
241
|
+
}).strict();
|
|
242
|
+
const hookReturnsSchema = z.object({ schema: z.string().min(1).optional() }).strict();
|
|
243
|
+
const moduleHookFunctionSchema = z.object({
|
|
244
|
+
kind: z.literal("module"),
|
|
245
|
+
module: z.string().min(1),
|
|
246
|
+
permissions: hookPermissionsSchema.optional(),
|
|
247
|
+
returns: hookReturnsSchema.optional(),
|
|
248
|
+
timeout_ms: z.number().int().positive().optional()
|
|
249
|
+
}).strict();
|
|
250
|
+
const commandHookProtocolSchema = z.object({
|
|
251
|
+
input: z.literal("file"),
|
|
252
|
+
result: z.literal("file")
|
|
253
|
+
}).strict();
|
|
254
|
+
const commandHookFunctionSchema = z.object({
|
|
255
|
+
command: z.array(z.string()).min(1),
|
|
244
256
|
env: hookEnvSchema.optional(),
|
|
245
|
-
|
|
246
|
-
kind: z.enum(["command", "builtin"]),
|
|
257
|
+
kind: z.literal("command"),
|
|
247
258
|
output_limit_bytes: z.number().int().positive().optional(),
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
protocol: commandHookProtocolSchema.default({
|
|
260
|
+
input: "file",
|
|
261
|
+
result: "file"
|
|
262
|
+
}),
|
|
263
|
+
returns: hookReturnsSchema.optional(),
|
|
250
264
|
timeout_ms: z.number().int().positive().optional(),
|
|
251
265
|
trusted: z.boolean().optional()
|
|
252
266
|
}).strict();
|
|
267
|
+
const hookFunctionSchema = z.discriminatedUnion("kind", [moduleHookFunctionSchema, commandHookFunctionSchema]);
|
|
268
|
+
const hookBindingWhereSchema = z.object({
|
|
269
|
+
gate: z.string().optional(),
|
|
270
|
+
node: z.string().optional(),
|
|
271
|
+
workflow: z.string().optional()
|
|
272
|
+
}).strict();
|
|
273
|
+
const hookBindingResultSchema = z.object({
|
|
274
|
+
pass_to: z.enum(["downstream"]).optional(),
|
|
275
|
+
publish: z.boolean().optional(),
|
|
276
|
+
save_as: z.string().min(1).optional()
|
|
277
|
+
}).strict();
|
|
278
|
+
const hookBindingSchema = z.object({
|
|
279
|
+
failure: z.enum(["fail", "ignore"]).default("ignore"),
|
|
280
|
+
function: z.string().min(1),
|
|
281
|
+
id: z.string().min(1),
|
|
282
|
+
result: hookBindingResultSchema.optional(),
|
|
283
|
+
where: hookBindingWhereSchema.optional(),
|
|
284
|
+
with: z.record(z.string(), z.unknown()).optional()
|
|
285
|
+
}).strict();
|
|
286
|
+
const hookPolicySchema = z.object({
|
|
287
|
+
commands: z.enum([
|
|
288
|
+
"allow",
|
|
289
|
+
"trusted-only",
|
|
290
|
+
"deny"
|
|
291
|
+
]).optional(),
|
|
292
|
+
modules: z.enum(["allow", "deny"]).optional()
|
|
293
|
+
}).strict();
|
|
294
|
+
const hooksConfigSchema = z.object({
|
|
295
|
+
functions: strictRecord(hookFunctionSchema).default({}),
|
|
296
|
+
on: strictRecord(z.array(hookBindingSchema)).default({}),
|
|
297
|
+
policy: hookPolicySchema.optional()
|
|
298
|
+
}).strict();
|
|
253
299
|
const taskContextResolverSchema = z.object({ type: z.string().min(1) }).passthrough();
|
|
254
300
|
const nodeTaskContextSchema = z.object({
|
|
255
301
|
acceptance_criteria: z.array(z.object({
|
|
@@ -274,7 +320,6 @@ const schedulePolicySchema = z.object({
|
|
|
274
320
|
const workflowNodeBaseSchema = z.object({
|
|
275
321
|
artifacts: z.array(artifactSchema).optional(),
|
|
276
322
|
gates: z.array(gateSchema).optional(),
|
|
277
|
-
hooks: z.array(z.string()).optional(),
|
|
278
323
|
id: z.string(),
|
|
279
324
|
needs: z.array(z.string()).optional(),
|
|
280
325
|
retries: retriesSchema.optional(),
|
|
@@ -311,7 +356,6 @@ const workflowNodeSchema = z.lazy(() => z.discriminatedUnion("kind", [
|
|
|
311
356
|
const workflowSchema = z.object({
|
|
312
357
|
description: z.string().optional(),
|
|
313
358
|
execution: workflowExecutionSchema.optional(),
|
|
314
|
-
hooks: z.array(z.string()).optional(),
|
|
315
359
|
nodes: z.array(workflowNodeSchema)
|
|
316
360
|
}).strict();
|
|
317
361
|
const runnersFileSchema = z.object({
|
|
@@ -329,7 +373,10 @@ const profilesFileSchema = z.object({
|
|
|
329
373
|
const pipelineFileSchema = z.object({
|
|
330
374
|
default_workflow: z.string(),
|
|
331
375
|
entrypoints: strictRecord(entrypointSchema).default({}),
|
|
332
|
-
hooks:
|
|
376
|
+
hooks: hooksConfigSchema.default({
|
|
377
|
+
functions: {},
|
|
378
|
+
on: {}
|
|
379
|
+
}),
|
|
333
380
|
orchestrator: orchestratorSchema,
|
|
334
381
|
schedules: strictRecord(schedulePolicySchema).default({}),
|
|
335
382
|
task_context: taskContextResolverSchema.optional(),
|
|
@@ -339,7 +386,10 @@ const pipelineFileSchema = z.object({
|
|
|
339
386
|
const configSchema = z.object({
|
|
340
387
|
default_workflow: z.string(),
|
|
341
388
|
entrypoints: strictRecord(entrypointSchema).default({}),
|
|
342
|
-
hooks:
|
|
389
|
+
hooks: hooksConfigSchema.default({
|
|
390
|
+
functions: {},
|
|
391
|
+
on: {}
|
|
392
|
+
}),
|
|
343
393
|
mcp_gateway: mcpGatewaySchema.optional(),
|
|
344
394
|
mcp_servers: strictRecord(mcpServerSchema).default({}),
|
|
345
395
|
orchestrator: orchestratorSchema,
|
|
@@ -504,11 +554,10 @@ function validatePipelineConfig(rawConfig, projectRoot, options = {}) {
|
|
|
504
554
|
validateRegistryIds("rules", config.rules, issues);
|
|
505
555
|
validateRegistryIds("skills", config.skills, issues);
|
|
506
556
|
validateRegistryIds("mcp_servers", config.mcp_servers, issues);
|
|
507
|
-
validateRegistryIds("hooks", config.hooks, issues);
|
|
557
|
+
validateRegistryIds("hooks.functions", config.hooks.functions, issues);
|
|
508
558
|
validateRegistryIds("workflows", config.workflows, issues);
|
|
509
559
|
validateRegistryIds("entrypoints", config.entrypoints, issues);
|
|
510
|
-
if (config.profiles[config.orchestrator.profile])
|
|
511
|
-
else issues.push({
|
|
560
|
+
if (!config.profiles[config.orchestrator.profile]) issues.push({
|
|
512
561
|
path: "orchestrator.profile",
|
|
513
562
|
message: `orchestrator references missing profile '${config.orchestrator.profile}'`
|
|
514
563
|
});
|
|
@@ -523,16 +572,7 @@ function validatePipelineConfig(rawConfig, projectRoot, options = {}) {
|
|
|
523
572
|
}
|
|
524
573
|
validateProfile(profileId, profile, runner, config, issues, projectRoot, options);
|
|
525
574
|
}
|
|
526
|
-
|
|
527
|
-
if (hook.kind === "command" && !hook.command) issues.push({
|
|
528
|
-
path: `hooks.${hookId}.command`,
|
|
529
|
-
message: `command hook '${hookId}' must declare command`
|
|
530
|
-
});
|
|
531
|
-
if (hook.kind === "builtin" && !hook.builtin) issues.push({
|
|
532
|
-
path: `hooks.${hookId}.builtin`,
|
|
533
|
-
message: `builtin hook '${hookId}' must declare builtin`
|
|
534
|
-
});
|
|
535
|
-
}
|
|
575
|
+
validateHookConfig(config, issues, projectRoot, options);
|
|
536
576
|
for (const [ruleId, rule] of Object.entries(config.rules)) validatePath(`rules.${ruleId}.path`, rule.path, projectRoot, issues, options);
|
|
537
577
|
for (const [skillId, skill] of Object.entries(config.skills)) validatePath(`skills.${skillId}.path`, skill.path, projectRoot, issues, options);
|
|
538
578
|
for (const [workflowId, workflow] of Object.entries(config.workflows)) validateWorkflow(workflowId, workflow, config, issues, projectRoot, options);
|
|
@@ -545,6 +585,29 @@ function validateRegistryIds(name, registry, issues) {
|
|
|
545
585
|
message: `registry id '${id}' must match ${ID_RE.source}`
|
|
546
586
|
});
|
|
547
587
|
}
|
|
588
|
+
function validateHookConfig(config, issues, projectRoot, options = {}) {
|
|
589
|
+
const allowedEvents = new Set(HOOK_EVENTS);
|
|
590
|
+
for (const [functionId, hookFunction] of Object.entries(config.hooks.functions)) validatePath(`hooks.functions.${functionId}.returns.schema`, hookFunction.returns?.schema, projectRoot, issues, options);
|
|
591
|
+
for (const [event, bindings] of Object.entries(config.hooks.on)) {
|
|
592
|
+
if (!allowedEvents.has(event)) {
|
|
593
|
+
issues.push({
|
|
594
|
+
path: `hooks.on.${event}`,
|
|
595
|
+
message: `unsupported hook event '${event}'`
|
|
596
|
+
});
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
for (const [index, binding] of bindings.entries()) {
|
|
600
|
+
if (!ID_RE.test(binding.id)) issues.push({
|
|
601
|
+
path: `hooks.on.${event}.${index}.id`,
|
|
602
|
+
message: `hook binding id '${binding.id}' must match ${ID_RE.source}`
|
|
603
|
+
});
|
|
604
|
+
if (!config.hooks.functions[binding.function]) issues.push({
|
|
605
|
+
path: `hooks.on.${event}.${index}.function`,
|
|
606
|
+
message: `hook binding '${binding.id}' references missing function '${binding.function}'`
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
548
611
|
function validateProfile(profileId, profile, runner, config, issues, projectRoot, options = {}) {
|
|
549
612
|
validateActor(`profile '${profileId}'`, `profiles.${profileId}`, profile, runner, config, issues, projectRoot, options);
|
|
550
613
|
validateListCapability(`profiles.${profileId}.output.format`, profile.output?.format ? [profile.output.format] : void 0, runner.capabilities.output_formats, "output format", issues);
|
|
@@ -580,7 +643,6 @@ function validateActor(label, path, actor, runner, config, issues, projectRoot,
|
|
|
580
643
|
validateListCapability(`${path}.network.mode`, actor.network?.mode ? [actor.network.mode] : void 0, runner.capabilities.network, "network mode", issues);
|
|
581
644
|
}
|
|
582
645
|
function validateWorkflow(workflowId, workflow, config, issues, projectRoot, options = {}) {
|
|
583
|
-
validateReferences(`workflows.${workflowId}.hooks`, workflow.hooks, config.hooks, "hook", issues);
|
|
584
646
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
585
647
|
for (const node of workflow.nodes) {
|
|
586
648
|
if (nodeIds.has(node.id)) issues.push({
|
|
@@ -603,7 +665,6 @@ function validateWorkflowNode(workflowId, node, nodeIds, config, issues) {
|
|
|
603
665
|
path: `workflows.${workflowId}.nodes.${node.id}.needs`,
|
|
604
666
|
message: `node '${node.id}' references missing dependency '${need}'`
|
|
605
667
|
});
|
|
606
|
-
validateReferences(`workflows.${workflowId}.nodes.${node.id}.hooks`, node.hooks, config.hooks, "hook", issues);
|
|
607
668
|
validateWorkflowNodeKind(workflowId, node, config, issues);
|
|
608
669
|
if (node.kind === "parallel") validateParallelWorkflowNode(workflowId, node, config, issues);
|
|
609
670
|
}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks.d.ts
|
|
4
|
+
declare const hookResultSchema: z.ZodObject<{
|
|
5
|
+
artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
6
|
+
contentType: z.ZodOptional<z.ZodString>;
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
path: z.ZodString;
|
|
9
|
+
}, z.core.$strict>>>;
|
|
10
|
+
outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
11
|
+
patch: z.ZodOptional<z.ZodObject<{
|
|
12
|
+
runLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
13
|
+
taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
|
+
}, z.core.$strict>>;
|
|
15
|
+
status: z.ZodEnum<{
|
|
16
|
+
fail: "fail";
|
|
17
|
+
pass: "pass";
|
|
18
|
+
skip: "skip";
|
|
19
|
+
}>;
|
|
20
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
type HookResult = z.infer<typeof hookResultSchema>;
|
|
23
|
+
interface HookContext {
|
|
24
|
+
event: {
|
|
25
|
+
gateId?: string;
|
|
26
|
+
hookId: string;
|
|
27
|
+
nodeId?: string;
|
|
28
|
+
type: string;
|
|
29
|
+
workflowId: string;
|
|
30
|
+
};
|
|
31
|
+
failure?: {
|
|
32
|
+
evidence: string[];
|
|
33
|
+
gate: string;
|
|
34
|
+
nodeId?: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
};
|
|
37
|
+
input: Record<string, unknown>;
|
|
38
|
+
node?: {
|
|
39
|
+
id: string;
|
|
40
|
+
};
|
|
41
|
+
results: Record<string, HookResult>;
|
|
42
|
+
task: string;
|
|
43
|
+
taskContext?: {
|
|
44
|
+
acceptanceCriteria?: Array<{
|
|
45
|
+
id: string;
|
|
46
|
+
text: string;
|
|
47
|
+
}>;
|
|
48
|
+
description?: string;
|
|
49
|
+
id?: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
};
|
|
52
|
+
workflow: {
|
|
53
|
+
id: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
type HookFunction = (context: HookContext) => HookResult | Promise<HookResult>;
|
|
57
|
+
declare function defineHook<T extends HookFunction>(hook: T): T;
|
|
58
|
+
declare function parseHookResult(value: unknown): HookResult;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { HookContext, HookFunction, HookResult, defineHook, hookResultSchema, parseHookResult };
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/hooks.ts
|
|
3
|
+
const hookResultSchema = z.object({
|
|
4
|
+
artifacts: z.array(z.object({
|
|
5
|
+
contentType: z.string().optional(),
|
|
6
|
+
name: z.string().min(1),
|
|
7
|
+
path: z.string().min(1)
|
|
8
|
+
}).strict()).optional(),
|
|
9
|
+
outputs: z.record(z.string(), z.unknown()).optional(),
|
|
10
|
+
patch: z.object({
|
|
11
|
+
runLabels: z.record(z.string(), z.string()).optional(),
|
|
12
|
+
taskContext: z.record(z.string(), z.unknown()).optional()
|
|
13
|
+
}).strict().optional(),
|
|
14
|
+
status: z.enum([
|
|
15
|
+
"pass",
|
|
16
|
+
"fail",
|
|
17
|
+
"skip"
|
|
18
|
+
]),
|
|
19
|
+
summary: z.string().optional()
|
|
20
|
+
}).strict();
|
|
21
|
+
function defineHook(hook) {
|
|
22
|
+
return hook;
|
|
23
|
+
}
|
|
24
|
+
function parseHookResult(value) {
|
|
25
|
+
return hookResultSchema.parse(value);
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { defineHook, hookResultSchema, parseHookResult };
|
package/dist/index.js
CHANGED
|
@@ -110,6 +110,7 @@ function formatAgentProgress(event) {
|
|
|
110
110
|
case "agent.finish": return `Agent finished: ${event.nodeId} runner=${event.runnerId ?? "unknown"} exit=${event.exitCode}`;
|
|
111
111
|
case "hook.start": return `Hook starting: ${event.hookId} event=${event.event}${event.nodeId ? ` node=${event.nodeId}` : ""}`;
|
|
112
112
|
case "hook.finish": return `Hook ${event.passed ? "passed" : "failed"}: ${event.hookId}${event.reason ? ` (${event.reason})` : ""}`;
|
|
113
|
+
case "hook.result": return `Hook result: ${event.hookId} ${event.status}${event.summary ? ` (${event.summary})` : ""}`;
|
|
113
114
|
default: return null;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
@@ -497,7 +498,6 @@ function formatSelectedWorkflowPlan(config, worktreePath, flags) {
|
|
|
497
498
|
return formatWorkflowPlan(config, worktreePath, resolveWorkflowSelection(config, flags.workflow, flags.entrypoint));
|
|
498
499
|
}
|
|
499
500
|
function formatCompiledWorkflowPlan(config, worktreePath, plan) {
|
|
500
|
-
const workflow = config.workflows[plan.workflowId];
|
|
501
501
|
const lines = [`Workflow: ${plan.workflowId}`];
|
|
502
502
|
lines.push(formatOrchestratorPlan(config, worktreePath));
|
|
503
503
|
lines.push(`Batches: ${plan.parallelBatches.map((batch) => `[${batch.map((node) => node.id).join(", ")}]`).join(" -> ")}`);
|
|
@@ -505,7 +505,8 @@ function formatCompiledWorkflowPlan(config, worktreePath, plan) {
|
|
|
505
505
|
if (node.kind === "parallel" && node.children?.length) lines.push(`${node.id}(parallel: ${node.children.map((child) => child.id).join(", ")})`);
|
|
506
506
|
lines.push(formatWorkflowPlanNode(node, config, worktreePath));
|
|
507
507
|
}
|
|
508
|
-
|
|
508
|
+
const workflowHooks = Object.entries(config.hooks.on).flatMap(([event, bindings]) => bindings.filter((binding) => binding.where?.workflow === plan.workflowId).map((binding) => `${event}:${binding.id}`));
|
|
509
|
+
if (workflowHooks.length > 0) lines.push(`Workflow hooks: ${workflowHooks.join(", ")}`);
|
|
509
510
|
return lines.join("\n");
|
|
510
511
|
}
|
|
511
512
|
function formatWorkflowPlanNode(node, config, worktreePath) {
|
|
@@ -522,8 +523,7 @@ function formatWorkflowPlanNode(node, config, worktreePath) {
|
|
|
522
523
|
launch ? `runner=${launch.runnerId}` : "",
|
|
523
524
|
launch ? `strategy=${launch.strategy}` : "",
|
|
524
525
|
node.gates?.length ? `gates=${node.gates.length}` : "gates=0",
|
|
525
|
-
node.artifacts?.length ? `artifacts=${node.artifacts.map((artifact) => artifact.path).join(",")}` : "artifacts=none"
|
|
526
|
-
node.hooks?.length ? `hooks=${node.hooks.join(",")}` : ""
|
|
526
|
+
node.artifacts?.length ? `artifacts=${node.artifacts.map((artifact) => artifact.path).join(",")}` : "artifacts=none"
|
|
527
527
|
].filter(Boolean).join(" ");
|
|
528
528
|
}
|
|
529
529
|
function resolveWorkflowSelection(config, workflowId, entrypointId) {
|
|
@@ -548,7 +548,7 @@ function formatOrchestratorPlan(config, worktreePath) {
|
|
|
548
548
|
formatList("rules", orchestrator.rules),
|
|
549
549
|
formatList("skills", orchestrator.skills),
|
|
550
550
|
formatList("mcp_servers", orchestrator.mcp_servers),
|
|
551
|
-
formatList("hooks", config.
|
|
551
|
+
formatList("hooks", Object.keys(config.hooks.functions))
|
|
552
552
|
].filter(Boolean).join(" ");
|
|
553
553
|
}
|
|
554
554
|
function formatList(label, items) {
|
package/dist/install-commands.js
CHANGED
|
@@ -58,10 +58,7 @@ function nativeProfileEntries(host, config) {
|
|
|
58
58
|
function orchestratorProfile(config) {
|
|
59
59
|
const profile = config.profiles[config.orchestrator.profile];
|
|
60
60
|
if (!profile) throw new Error(`Orchestrator profile '${config.orchestrator.profile}' is not declared.`);
|
|
61
|
-
return {
|
|
62
|
-
...profile,
|
|
63
|
-
hooks: config.orchestrator.hooks
|
|
64
|
-
};
|
|
61
|
+
return { ...profile };
|
|
65
62
|
}
|
|
66
63
|
function resolvedHostModel(config, host, profile) {
|
|
67
64
|
const runner = config.runners[profile.runner];
|
|
@@ -132,7 +129,6 @@ function grants(actor) {
|
|
|
132
129
|
`mcp_servers: ${(actor.mcp_servers ?? []).join(", ") || "none"}`,
|
|
133
130
|
`filesystem: ${actor.filesystem?.mode ?? "default"}`,
|
|
134
131
|
`network: ${actor.network?.mode ?? "default"}`,
|
|
135
|
-
..."hooks" in actor ? [`hooks: ${(actor.hooks ?? []).join(", ") || "none"}`] : [],
|
|
136
132
|
..."output" in actor ? [`output: ${actor.output?.format ?? "text"}`] : []
|
|
137
133
|
].join("\n");
|
|
138
134
|
}
|
|
@@ -149,6 +145,7 @@ function orchestratorBlock(config) {
|
|
|
149
145
|
return [
|
|
150
146
|
"Configured orchestrator:",
|
|
151
147
|
grants(orchestratorProfile(config)),
|
|
148
|
+
`hooks: ${Object.keys(config.hooks.functions).join(", ") || "none"}`,
|
|
152
149
|
"",
|
|
153
150
|
instructionsPointer(orchestratorProfile(config))
|
|
154
151
|
].join("\n");
|
package/dist/pipeline-init.js
CHANGED
|
@@ -34,31 +34,35 @@ entrypoints:
|
|
|
34
34
|
|
|
35
35
|
orchestrator:
|
|
36
36
|
profile: orchestrator
|
|
37
|
-
hooks: [generated-defaults-audit]
|
|
38
37
|
|
|
39
38
|
hooks:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
functions:
|
|
40
|
+
generated-defaults-audit:
|
|
41
|
+
kind: command
|
|
42
|
+
command:
|
|
43
|
+
- node
|
|
44
|
+
- -e
|
|
45
|
+
- |
|
|
46
|
+
const fs = require("node:fs");
|
|
47
|
+
const files = [".pipeline/profiles.yaml"].filter((file) => fs.existsSync(file));
|
|
48
|
+
const text = files.map((file) => fs.readFileSync(file, "utf8")).join("\\n").toLowerCase();
|
|
49
|
+
const banned = ["atlassian", "jira", "linear", "confluence", "compass", "sentry", "deepwiki"];
|
|
50
|
+
const hits = banned.filter((item) => text.includes(item));
|
|
51
|
+
const githubUrls = [...text.matchAll(/https:\\/\\/api\\.githubcopilot\\.com\\/mcp[^"'\\s]*/g)].map((match) => match[0]);
|
|
52
|
+
const writeGithub = githubUrls.filter((url) => !url.includes("/readonly"));
|
|
53
|
+
if (hits.length || writeGithub.length) {
|
|
54
|
+
console.error(["Banned generated defaults detected.", hits.length ? "services=" + hits.join(",") : "", writeGithub.length ? "github=" + writeGithub.join(",") : ""].filter(Boolean).join(" "));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
fs.writeFileSync(process.env.PIPELINE_HOOK_RESULT, JSON.stringify({ status: "pass", summary: "Generated defaults audit passed" }));
|
|
58
|
+
trusted: true
|
|
59
|
+
timeout_ms: 5000
|
|
60
|
+
output_limit_bytes: 4096
|
|
61
|
+
on:
|
|
62
|
+
workflow.start:
|
|
63
|
+
- id: generated-defaults-audit
|
|
64
|
+
function: generated-defaults-audit
|
|
65
|
+
failure: fail
|
|
62
66
|
|
|
63
67
|
schedules:
|
|
64
68
|
pipe-schedule:
|
|
@@ -564,7 +568,7 @@ const EPIC_TRACK_ITEM_SCHEMA = z.object({
|
|
|
564
568
|
title: z.string().optional()
|
|
565
569
|
});
|
|
566
570
|
function zodJsonSchema(schema) {
|
|
567
|
-
return JSON.stringify(z.toJSONSchema(schema, { target: "draft-
|
|
571
|
+
return JSON.stringify(z.toJSONSchema(schema, { target: "draft-2020-12" }), null, 2);
|
|
568
572
|
}
|
|
569
573
|
const RESEARCH_SCHEMA = zodJsonSchema(z.object({
|
|
570
574
|
ac: STRING_ARRAY_SCHEMA,
|