@oisincoveney/pipeline 1.2.2 → 1.3.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 +6 -0
- package/dist/index.js +160 -16
- package/dist/mastra/runner.js +3 -3
- package/dist/pipeline-runtime.d.ts +28 -0
- package/docs/config-architecture.md +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,12 @@ Run the default workflow:
|
|
|
45
45
|
pipe run "Implement PIPE-123 user-facing behavior"
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
Run a read-only repository inspection:
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
pipe run --workflow inspect "Report the app structure and available checks. Do not modify files."
|
|
52
|
+
```
|
|
53
|
+
|
|
48
54
|
The `pipe` binary also accepts the task directly:
|
|
49
55
|
|
|
50
56
|
```shell
|
package/dist/index.js
CHANGED
|
@@ -35809,6 +35809,8 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options2 = {})
|
|
|
35809
35809
|
return [
|
|
35810
35810
|
"exec",
|
|
35811
35811
|
"--json",
|
|
35812
|
+
"-C",
|
|
35813
|
+
worktreePath,
|
|
35812
35814
|
...optionalModelArgs(harness, options2.runner, options2.actor),
|
|
35813
35815
|
...mcpArgs,
|
|
35814
35816
|
...skillArgs,
|
|
@@ -35817,9 +35819,7 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options2 = {})
|
|
|
35817
35819
|
"--config",
|
|
35818
35820
|
'approval_policy="never"',
|
|
35819
35821
|
"--skip-git-repo-check",
|
|
35820
|
-
prompt
|
|
35821
|
-
"-C",
|
|
35822
|
-
worktreePath
|
|
35822
|
+
prompt
|
|
35823
35823
|
];
|
|
35824
35824
|
case "opencode":
|
|
35825
35825
|
return contextFile ? [
|
|
@@ -36123,6 +36123,12 @@ orchestrator:
|
|
|
36123
36123
|
hooks: {}
|
|
36124
36124
|
|
|
36125
36125
|
workflows:
|
|
36126
|
+
inspect:
|
|
36127
|
+
description: Read-only repository inspection workflow.
|
|
36128
|
+
nodes:
|
|
36129
|
+
- id: inspect
|
|
36130
|
+
kind: agent
|
|
36131
|
+
profile: pipeline-inspector
|
|
36126
36132
|
default:
|
|
36127
36133
|
description: Default research, red, green, verify, learn workflow.
|
|
36128
36134
|
nodes:
|
|
@@ -36261,6 +36267,20 @@ profiles:
|
|
|
36261
36267
|
output:
|
|
36262
36268
|
format: json_schema
|
|
36263
36269
|
schema_path: .pipeline/schemas/research.schema.json
|
|
36270
|
+
pipeline-inspector:
|
|
36271
|
+
runner: codex
|
|
36272
|
+
description: Inspect the repository without modifying files.
|
|
36273
|
+
instructions:
|
|
36274
|
+
path: .pipeline/prompts/inspector.md
|
|
36275
|
+
tools: [read, list, grep, glob, bash]
|
|
36276
|
+
filesystem:
|
|
36277
|
+
mode: read-only
|
|
36278
|
+
allow: ["**/*"]
|
|
36279
|
+
deny: ["node_modules/**", "dist/**", ".git/**"]
|
|
36280
|
+
network:
|
|
36281
|
+
mode: inherit
|
|
36282
|
+
output:
|
|
36283
|
+
format: text
|
|
36264
36284
|
pipeline-test-writer:
|
|
36265
36285
|
runner: codex
|
|
36266
36286
|
description: Add focused failing tests for the requested behavior.
|
|
@@ -36377,6 +36397,19 @@ var SCAFFOLD_FILES = {
|
|
|
36377
36397
|
"You are the research phase for the pipeline.",
|
|
36378
36398
|
"Inspect first-party source, tests, docs, and task context before proposing changes.",
|
|
36379
36399
|
"Write structured findings that identify relevant files, existing patterns, acceptance criteria, and risks.",
|
|
36400
|
+
"Return only valid JSON matching `.pipeline/schemas/research.schema.json`: an object with `findings` and `ac` arrays, plus optional `files`, `risks`, and `target`.",
|
|
36401
|
+
"Do not wrap the JSON in Markdown fences or add prose outside the JSON object.",
|
|
36402
|
+
""
|
|
36403
|
+
].join(`
|
|
36404
|
+
`),
|
|
36405
|
+
".pipeline/prompts/inspector.md": [
|
|
36406
|
+
"You are the read-only inspection phase for the pipeline.",
|
|
36407
|
+
"Use a bounded inspection: run at most 8 discovery commands and read at most 12 small, high-signal files.",
|
|
36408
|
+
"Prefer `pwd`, `rg --files -g '!*node_modules*' -g '!dist/**' -g '!build/**' | head -200`, package/workspace manifests, mise/turbo config, and test config files.",
|
|
36409
|
+
"When reading paths with shell metacharacters such as brackets, quote the whole path.",
|
|
36410
|
+
"Do not recursively inspect route trees or generated output.",
|
|
36411
|
+
"Report the app structure, available checks, important files, and notable risks from the sampled evidence.",
|
|
36412
|
+
"Do not modify files.",
|
|
36380
36413
|
""
|
|
36381
36414
|
].join(`
|
|
36382
36415
|
`),
|
|
@@ -36400,6 +36433,8 @@ var SCAFFOLD_FILES = {
|
|
|
36400
36433
|
"You are the VERIFY phase for the pipeline.",
|
|
36401
36434
|
"Run configured checks, review implementation fit, and report PASS or FAIL with evidence.",
|
|
36402
36435
|
"Do not mark the workflow passing without concrete verification evidence.",
|
|
36436
|
+
"Return only valid JSON matching `.pipeline/schemas/verify.schema.json`: an object with `verdict`, `evidence`, and optional `violations`.",
|
|
36437
|
+
"Do not wrap the JSON in Markdown fences or add prose outside the JSON object.",
|
|
36403
36438
|
""
|
|
36404
36439
|
].join(`
|
|
36405
36440
|
`),
|
|
@@ -36407,6 +36442,8 @@ var SCAFFOLD_FILES = {
|
|
|
36407
36442
|
"You are the LEARN phase for the pipeline.",
|
|
36408
36443
|
"Store durable lessons from the run when useful and report qdrant-store evidence.",
|
|
36409
36444
|
"Do not write local markdown knowledge as the durable sink.",
|
|
36445
|
+
"Return only valid JSON matching `.pipeline/schemas/learn.schema.json`: an object with `qdrant` and `evidence`.",
|
|
36446
|
+
"Do not wrap the JSON in Markdown fences or add prose outside the JSON object.",
|
|
36410
36447
|
""
|
|
36411
36448
|
].join(`
|
|
36412
36449
|
`),
|
|
@@ -36898,17 +36935,29 @@ async function runPipelineFromConfig(options2) {
|
|
|
36898
36935
|
plan,
|
|
36899
36936
|
task: options2.task,
|
|
36900
36937
|
workflowId,
|
|
36901
|
-
worktreePath
|
|
36938
|
+
worktreePath,
|
|
36939
|
+
...options2.reporter ? { reporter: options2.reporter } : {}
|
|
36902
36940
|
};
|
|
36903
36941
|
const nodes = [];
|
|
36942
|
+
emit(context, {
|
|
36943
|
+
nodeIds: plan.topologicalOrder.map((node) => node.id),
|
|
36944
|
+
type: "workflow.start",
|
|
36945
|
+
workflowId
|
|
36946
|
+
});
|
|
36904
36947
|
const startHook = await dispatchHooks(context, "workflow.start");
|
|
36905
36948
|
if (startHook) {
|
|
36906
|
-
|
|
36949
|
+
const result2 = failedRuntimeResult(context, nodes, startHook);
|
|
36950
|
+
emit(context, {
|
|
36951
|
+
outcome: result2.outcome,
|
|
36952
|
+
type: "workflow.finish",
|
|
36953
|
+
workflowId
|
|
36954
|
+
});
|
|
36955
|
+
return result2;
|
|
36907
36956
|
}
|
|
36908
36957
|
for (const batch of plan.parallelBatches) {
|
|
36909
36958
|
const results = await Promise.all(batch.map((node) => executeNode(node, context)));
|
|
36910
36959
|
nodes.push(...results);
|
|
36911
|
-
const failed = results.find((
|
|
36960
|
+
const failed = results.find((result2) => result2.status === "failed");
|
|
36912
36961
|
if (failed) {
|
|
36913
36962
|
const failure = {
|
|
36914
36963
|
evidence: failed.evidence,
|
|
@@ -36918,16 +36967,28 @@ async function runPipelineFromConfig(options2) {
|
|
|
36918
36967
|
};
|
|
36919
36968
|
await dispatchHooks(context, "workflow.failure", failure);
|
|
36920
36969
|
await dispatchHooks(context, "workflow.complete", failure);
|
|
36921
|
-
|
|
36970
|
+
const result2 = failedRuntimeResult(context, nodes, failure);
|
|
36971
|
+
emit(context, {
|
|
36972
|
+
outcome: result2.outcome,
|
|
36973
|
+
type: "workflow.finish",
|
|
36974
|
+
workflowId
|
|
36975
|
+
});
|
|
36976
|
+
return result2;
|
|
36922
36977
|
}
|
|
36923
36978
|
}
|
|
36924
36979
|
const successHook = await dispatchHooks(context, "workflow.success");
|
|
36925
36980
|
const completeHook = await dispatchHooks(context, "workflow.complete");
|
|
36926
36981
|
const hookFailure = successHook ?? completeHook;
|
|
36927
36982
|
if (hookFailure) {
|
|
36928
|
-
|
|
36983
|
+
const result2 = failedRuntimeResult(context, nodes, hookFailure);
|
|
36984
|
+
emit(context, {
|
|
36985
|
+
outcome: result2.outcome,
|
|
36986
|
+
type: "workflow.finish",
|
|
36987
|
+
workflowId
|
|
36988
|
+
});
|
|
36989
|
+
return result2;
|
|
36929
36990
|
}
|
|
36930
|
-
|
|
36991
|
+
const result = {
|
|
36931
36992
|
agentInvocations: context.agentInvocations,
|
|
36932
36993
|
failureDetails: [],
|
|
36933
36994
|
gates: context.gates,
|
|
@@ -36936,6 +36997,12 @@ async function runPipelineFromConfig(options2) {
|
|
|
36936
36997
|
outcome: "PASS",
|
|
36937
36998
|
plan
|
|
36938
36999
|
};
|
|
37000
|
+
emit(context, {
|
|
37001
|
+
outcome: result.outcome,
|
|
37002
|
+
type: "workflow.finish",
|
|
37003
|
+
workflowId
|
|
37004
|
+
});
|
|
37005
|
+
return result;
|
|
36939
37006
|
}
|
|
36940
37007
|
function failedRuntimeResult(context, nodes, failure) {
|
|
36941
37008
|
return {
|
|
@@ -36956,9 +37023,12 @@ async function executeNode(node, context) {
|
|
|
36956
37023
|
output: ""
|
|
36957
37024
|
};
|
|
36958
37025
|
for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
|
|
37026
|
+
emitNodeStart(context, node, attempt);
|
|
36959
37027
|
const startHook = await dispatchHooks(context, "node.start", undefined, node);
|
|
36960
37028
|
if (startHook) {
|
|
36961
|
-
|
|
37029
|
+
const result2 = nodeFailure(node.id, attempt, startHook.evidence, last.output);
|
|
37030
|
+
emitNodeFinish(context, result2);
|
|
37031
|
+
return result2;
|
|
36962
37032
|
}
|
|
36963
37033
|
last = await executeNodeAttempt(node, context);
|
|
36964
37034
|
context.lastOutputByNode.set(node.id, last.output);
|
|
@@ -36967,9 +37037,11 @@ async function executeNode(node, context) {
|
|
|
36967
37037
|
if (!failedGate && last.exitCode === 0) {
|
|
36968
37038
|
const successHook = await dispatchHooks(context, "node.success", undefined, node);
|
|
36969
37039
|
if (successHook) {
|
|
36970
|
-
|
|
37040
|
+
const result3 = nodeFailure(node.id, attempt, successHook.evidence, last.output);
|
|
37041
|
+
emitNodeFinish(context, result3);
|
|
37042
|
+
return result3;
|
|
36971
37043
|
}
|
|
36972
|
-
|
|
37044
|
+
const result2 = {
|
|
36973
37045
|
attempts: attempt,
|
|
36974
37046
|
evidence: last.evidence,
|
|
36975
37047
|
exitCode: 0,
|
|
@@ -36977,6 +37049,8 @@ async function executeNode(node, context) {
|
|
|
36977
37049
|
output: last.output,
|
|
36978
37050
|
status: "passed"
|
|
36979
37051
|
};
|
|
37052
|
+
emitNodeFinish(context, result2);
|
|
37053
|
+
return result2;
|
|
36980
37054
|
}
|
|
36981
37055
|
const evidence = failedGate?.evidence ?? last.evidence.concat(`node exited with code ${last.exitCode}`);
|
|
36982
37056
|
if (attempt === maxAttempts) {
|
|
@@ -36986,10 +37060,14 @@ async function executeNode(node, context) {
|
|
|
36986
37060
|
nodeId: node.id,
|
|
36987
37061
|
reason: failedGate?.reason ?? `node exited with code ${last.exitCode}`
|
|
36988
37062
|
}, node);
|
|
36989
|
-
|
|
37063
|
+
const result2 = nodeFailure(node.id, attempt, evidence, last.output);
|
|
37064
|
+
emitNodeFinish(context, result2);
|
|
37065
|
+
return result2;
|
|
36990
37066
|
}
|
|
36991
37067
|
}
|
|
36992
|
-
|
|
37068
|
+
const result = nodeFailure(node.id, maxAttempts, last.evidence, last.output);
|
|
37069
|
+
emitNodeFinish(context, result);
|
|
37070
|
+
return result;
|
|
36993
37071
|
}
|
|
36994
37072
|
function nodeFailure(nodeId, attempts, evidence, output) {
|
|
36995
37073
|
return {
|
|
@@ -37267,6 +37345,13 @@ async function evaluateNodeGates(node, context, attempt) {
|
|
|
37267
37345
|
const result = await evaluateGate(gate, node.id, context, attempt);
|
|
37268
37346
|
context.gates.push(result);
|
|
37269
37347
|
results.push(result);
|
|
37348
|
+
emit(context, {
|
|
37349
|
+
gateId: result.gateId,
|
|
37350
|
+
nodeId: result.nodeId,
|
|
37351
|
+
passed: result.passed,
|
|
37352
|
+
type: "gate.finish",
|
|
37353
|
+
...result.reason ? { reason: result.reason } : {}
|
|
37354
|
+
});
|
|
37270
37355
|
if (!result.passed) {
|
|
37271
37356
|
await dispatchHooks(context, "gate.failure", {
|
|
37272
37357
|
evidence: result.evidence,
|
|
@@ -37281,6 +37366,28 @@ async function evaluateNodeGates(node, context, attempt) {
|
|
|
37281
37366
|
}
|
|
37282
37367
|
return results;
|
|
37283
37368
|
}
|
|
37369
|
+
function emit(context, event) {
|
|
37370
|
+
context.reporter?.(event);
|
|
37371
|
+
}
|
|
37372
|
+
function emitNodeStart(context, node, attempt) {
|
|
37373
|
+
const profile = node.profile ? context.config.profiles[node.profile] : undefined;
|
|
37374
|
+
emit(context, {
|
|
37375
|
+
attempt,
|
|
37376
|
+
nodeId: node.id,
|
|
37377
|
+
type: "node.start",
|
|
37378
|
+
...node.profile ? { profile: node.profile } : {},
|
|
37379
|
+
...profile?.runner ? { runnerId: profile.runner } : {}
|
|
37380
|
+
});
|
|
37381
|
+
}
|
|
37382
|
+
function emitNodeFinish(context, result) {
|
|
37383
|
+
emit(context, {
|
|
37384
|
+
attempt: result.attempts,
|
|
37385
|
+
exitCode: result.exitCode,
|
|
37386
|
+
nodeId: result.nodeId,
|
|
37387
|
+
status: result.status,
|
|
37388
|
+
type: "node.finish"
|
|
37389
|
+
});
|
|
37390
|
+
}
|
|
37284
37391
|
async function evaluateGate(gate, nodeId, context, attempt) {
|
|
37285
37392
|
const gateId = gate.id ?? `${gate.kind}:${nodeId}`;
|
|
37286
37393
|
if (gate.kind === "command") {
|
|
@@ -37520,6 +37627,7 @@ function pipe2(description, options2 = {}) {
|
|
|
37520
37627
|
async function runConfiguredPipeline(inputs) {
|
|
37521
37628
|
const runner = inputs.pipelineRunner ?? runPipelineFromConfig;
|
|
37522
37629
|
const result = await runner({
|
|
37630
|
+
reporter: formatRuntimeProgress,
|
|
37523
37631
|
task: inputs.task,
|
|
37524
37632
|
workflowId: inputs.workflow,
|
|
37525
37633
|
worktreePath: inputs.worktreePath
|
|
@@ -37529,13 +37637,49 @@ async function runConfiguredPipeline(inputs) {
|
|
|
37529
37637
|
throw new Error(formatRuntimeFailure(result));
|
|
37530
37638
|
}
|
|
37531
37639
|
}
|
|
37640
|
+
function formatRuntimeProgress(event) {
|
|
37641
|
+
switch (event.type) {
|
|
37642
|
+
case "workflow.start":
|
|
37643
|
+
console.error(`Pipeline starting: ${event.workflowId} (${event.nodeIds.join(" -> ")})`);
|
|
37644
|
+
return;
|
|
37645
|
+
case "node.start":
|
|
37646
|
+
console.error([
|
|
37647
|
+
`Node starting: ${event.nodeId}`,
|
|
37648
|
+
event.runnerId ? `runner=${event.runnerId}` : "",
|
|
37649
|
+
event.profile ? `profile=${event.profile}` : "",
|
|
37650
|
+
`attempt=${event.attempt}`
|
|
37651
|
+
].filter(Boolean).join(" "));
|
|
37652
|
+
return;
|
|
37653
|
+
case "gate.finish":
|
|
37654
|
+
console.error(`Gate ${event.passed ? "passed" : "failed"}: ${event.nodeId}/${event.gateId}${event.reason ? ` (${event.reason})` : ""}`);
|
|
37655
|
+
return;
|
|
37656
|
+
case "node.finish":
|
|
37657
|
+
console.error(`Node finished: ${event.nodeId} ${event.status} exit=${event.exitCode}`);
|
|
37658
|
+
return;
|
|
37659
|
+
case "workflow.finish":
|
|
37660
|
+
console.error(`Pipeline finished: ${event.workflowId} ${event.outcome}`);
|
|
37661
|
+
return;
|
|
37662
|
+
default: {
|
|
37663
|
+
const _exhaustive = event;
|
|
37664
|
+
throw new Error(`Unhandled runtime event: ${String(_exhaustive)}`);
|
|
37665
|
+
}
|
|
37666
|
+
}
|
|
37667
|
+
}
|
|
37532
37668
|
function formatRuntimeResult(result) {
|
|
37533
|
-
|
|
37669
|
+
const lines = [
|
|
37534
37670
|
`Pipeline complete: ${result.outcome}`,
|
|
37535
37671
|
`Workflow: ${result.plan.workflowId}`,
|
|
37536
37672
|
`Nodes: ${result.nodes.map((node) => `${node.nodeId}:${node.status}`).join(", ")}`,
|
|
37537
37673
|
`Agent boundaries: ${result.agentInvocations.length}`
|
|
37538
|
-
]
|
|
37674
|
+
];
|
|
37675
|
+
const outputs = result.nodes.filter((node) => node.output.trim());
|
|
37676
|
+
if (outputs.length > 0) {
|
|
37677
|
+
lines.push("Node outputs:");
|
|
37678
|
+
for (const node of outputs) {
|
|
37679
|
+
appendIndentedSection(lines, node.nodeId, [node.output]);
|
|
37680
|
+
}
|
|
37681
|
+
}
|
|
37682
|
+
return lines.join(`
|
|
37539
37683
|
`);
|
|
37540
37684
|
}
|
|
37541
37685
|
function formatRuntimeFailure(result) {
|
package/dist/mastra/runner.js
CHANGED
|
@@ -7277,6 +7277,8 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options = {}) {
|
|
|
7277
7277
|
return [
|
|
7278
7278
|
"exec",
|
|
7279
7279
|
"--json",
|
|
7280
|
+
"-C",
|
|
7281
|
+
worktreePath,
|
|
7280
7282
|
...optionalModelArgs(harness, options.runner, options.actor),
|
|
7281
7283
|
...mcpArgs,
|
|
7282
7284
|
...skillArgs,
|
|
@@ -7285,9 +7287,7 @@ function harnessArgv(harness, prompt, worktreePath, contextFile, options = {}) {
|
|
|
7285
7287
|
"--config",
|
|
7286
7288
|
'approval_policy="never"',
|
|
7287
7289
|
"--skip-git-repo-check",
|
|
7288
|
-
prompt
|
|
7289
|
-
"-C",
|
|
7290
|
-
worktreePath
|
|
7290
|
+
prompt
|
|
7291
7291
|
];
|
|
7292
7292
|
case "opencode":
|
|
7293
7293
|
return contextFile ? [
|
|
@@ -32,9 +32,37 @@ export interface PipelineRuntimeResult {
|
|
|
32
32
|
outcome: "FAIL" | "PASS";
|
|
33
33
|
plan: WorkflowExecutionPlan;
|
|
34
34
|
}
|
|
35
|
+
export type PipelineRuntimeEvent = {
|
|
36
|
+
nodeIds: string[];
|
|
37
|
+
type: "workflow.start";
|
|
38
|
+
workflowId: string;
|
|
39
|
+
} | {
|
|
40
|
+
attempt: number;
|
|
41
|
+
nodeId: string;
|
|
42
|
+
profile?: string;
|
|
43
|
+
runnerId?: string;
|
|
44
|
+
type: "node.start";
|
|
45
|
+
} | {
|
|
46
|
+
attempt: number;
|
|
47
|
+
exitCode: number;
|
|
48
|
+
nodeId: string;
|
|
49
|
+
status: RuntimeNodeResult["status"];
|
|
50
|
+
type: "node.finish";
|
|
51
|
+
} | {
|
|
52
|
+
gateId: string;
|
|
53
|
+
nodeId: string;
|
|
54
|
+
passed: boolean;
|
|
55
|
+
reason?: string;
|
|
56
|
+
type: "gate.finish";
|
|
57
|
+
} | {
|
|
58
|
+
outcome: PipelineRuntimeResult["outcome"];
|
|
59
|
+
type: "workflow.finish";
|
|
60
|
+
workflowId: string;
|
|
61
|
+
};
|
|
35
62
|
export interface PipelineRuntimeOptions {
|
|
36
63
|
config?: PipelineConfig;
|
|
37
64
|
executor?: (plan: RunnerLaunchPlan) => AgentResult | Promise<AgentResult>;
|
|
65
|
+
reporter?: (event: PipelineRuntimeEvent) => void;
|
|
38
66
|
task: string;
|
|
39
67
|
workflowId?: string;
|
|
40
68
|
worktreePath?: string;
|