@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 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
- return failedRuntimeResult(context, nodes, startHook);
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((result) => result.status === "failed");
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
- return failedRuntimeResult(context, nodes, failure);
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
- return failedRuntimeResult(context, nodes, hookFailure);
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
- return {
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
- return nodeFailure(node.id, attempt, startHook.evidence, last.output);
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
- return nodeFailure(node.id, attempt, successHook.evidence, last.output);
37040
+ const result3 = nodeFailure(node.id, attempt, successHook.evidence, last.output);
37041
+ emitNodeFinish(context, result3);
37042
+ return result3;
36971
37043
  }
36972
- return {
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
- return nodeFailure(node.id, attempt, evidence, last.output);
37063
+ const result2 = nodeFailure(node.id, attempt, evidence, last.output);
37064
+ emitNodeFinish(context, result2);
37065
+ return result2;
36990
37066
  }
36991
37067
  }
36992
- return nodeFailure(node.id, maxAttempts, last.evidence, last.output);
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
- return [
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
- ].join(`
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) {
@@ -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;
@@ -80,6 +80,11 @@ orchestrator:
80
80
  hooks: {}
81
81
 
82
82
  workflows:
83
+ inspect:
84
+ nodes:
85
+ - id: inspect
86
+ kind: agent
87
+ profile: pipeline-inspector
83
88
  default:
84
89
  nodes:
85
90
  - id: research
package/package.json CHANGED
@@ -69,7 +69,7 @@
69
69
  "prepack": "bun run build:cli"
70
70
  },
71
71
  "type": "module",
72
- "version": "1.2.2",
72
+ "version": "1.3.1",
73
73
  "description": "",
74
74
  "main": "index.js",
75
75
  "keywords": [],