@juicesharp/rpiv-workflow 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +449 -0
  3. package/api.ts +557 -0
  4. package/audit.ts +217 -0
  5. package/built-ins.ts +65 -0
  6. package/command.ts +137 -0
  7. package/docs/cover.png +0 -0
  8. package/docs/cover.svg +120 -0
  9. package/docs/workflow-authoring.md +629 -0
  10. package/docs/workflow-basics.md +122 -0
  11. package/docs-protocol.ts +106 -0
  12. package/fanout.ts +96 -0
  13. package/host.ts +97 -0
  14. package/index.ts +230 -0
  15. package/internal-utils.ts +69 -0
  16. package/internal.ts +27 -0
  17. package/layers.ts +33 -0
  18. package/lifecycle.ts +274 -0
  19. package/load/cache.test.ts +82 -0
  20. package/load/cache.ts +40 -0
  21. package/load/index.ts +159 -0
  22. package/load/merge.ts +136 -0
  23. package/load/normalize.ts +73 -0
  24. package/load/paths.ts +32 -0
  25. package/load/resolve-default.ts +43 -0
  26. package/load/shape-guards.test.ts +74 -0
  27. package/load/shape-guards.ts +42 -0
  28. package/messages.ts +185 -0
  29. package/outcomes/collectors/directory-path.test.ts +64 -0
  30. package/outcomes/collectors/directory-path.ts +40 -0
  31. package/outcomes/collectors/index.ts +21 -0
  32. package/outcomes/collectors/tool-call.test.ts +110 -0
  33. package/outcomes/collectors/tool-call.ts +63 -0
  34. package/outcomes/collectors/transcript-path.test.ts +70 -0
  35. package/outcomes/collectors/transcript-path.ts +53 -0
  36. package/outcomes/collectors/union.test.ts +59 -0
  37. package/outcomes/collectors/union.ts +55 -0
  38. package/outcomes/collectors/url.test.ts +67 -0
  39. package/outcomes/collectors/url.ts +45 -0
  40. package/outcomes/collectors/workspace-diff.test.ts +107 -0
  41. package/outcomes/collectors/workspace-diff.ts +123 -0
  42. package/outcomes/git-commit.test.ts +194 -0
  43. package/outcomes/git-commit.ts +192 -0
  44. package/outcomes/index.ts +22 -0
  45. package/outcomes/parsers/index.ts +11 -0
  46. package/outcomes/parsers/json-body.test.ts +80 -0
  47. package/outcomes/parsers/json-body.ts +50 -0
  48. package/outcomes/side-effect.ts +26 -0
  49. package/output-spec.ts +170 -0
  50. package/output.ts +98 -0
  51. package/package.json +83 -0
  52. package/preview.ts +120 -0
  53. package/routing.ts +79 -0
  54. package/runner/chain-advance.ts +185 -0
  55. package/runner/index.ts +7 -0
  56. package/runner/runner.ts +356 -0
  57. package/runner/script-stage.ts +240 -0
  58. package/runner/stage-lifecycle.ts +447 -0
  59. package/sessions/extraction.ts +297 -0
  60. package/sessions/index.ts +7 -0
  61. package/sessions/sessions.ts +269 -0
  62. package/sessions/spawn.ts +135 -0
  63. package/state/index.ts +27 -0
  64. package/state/paths.ts +46 -0
  65. package/state/reads.ts +190 -0
  66. package/state/state.ts +115 -0
  67. package/state/writes.ts +58 -0
  68. package/transcript.ts +156 -0
  69. package/triggers.ts +27 -0
  70. package/typebox-adapter.ts +48 -0
  71. package/types.ts +237 -0
  72. package/validate-output.ts +120 -0
  73. package/validate-workflow.ts +491 -0
@@ -0,0 +1,122 @@
1
+ # Workflow Basics
2
+
3
+ A workflow chains Pi skills into a typed multi-stage graph with audited JSONL state, predicate routing, and per-stage output validation. Workflows are **skill-agnostic** — they name skills by their installed name, and the runner dispatches `/skill:<name>` via Pi's native skill loader.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Running workflows](#running-workflows)
8
+ - [File structure](#file-structure)
9
+ - [Layer merging](#layer-merging)
10
+ - [Config files](#config-files)
11
+ - [Pack files](#pack-files)
12
+ - [Example](#example)
13
+
14
+ ## Running workflows
15
+
16
+ ```bash
17
+ /wf # Preview every loaded workflow
18
+ /wf <name> # Preview one workflow's stage graph
19
+ /wf <name> <input> # Run a workflow with <input> piped to the start stage
20
+ ```
21
+
22
+ Running `/wf` without arguments shows a list of every loaded workflow and its stages. Running `/wf <name>` without input shows that workflow's stage graph in detail. The `<input>` string becomes the start stage's prompt.
23
+
24
+ ## File structure
25
+
26
+ ```
27
+ <cwd>/.rpiv-workflow/
28
+ ├── workflows.config.ts # The project's workflow config (hand-edited)
29
+ └── workflows/ # Pack files (installable bundles)
30
+ ├── my-pipeline.ts
31
+ └── ship.ts
32
+
33
+ ~/.config/rpiv-workflow/
34
+ ├── workflows.config.ts # User-level config
35
+ └── workflows/ # User-level packs
36
+ ```
37
+
38
+ Every workflow file is TypeScript, loaded via `jiti` (no build step required). Import the authoring DSL from `@juicesharp/rpiv-workflow`:
39
+
40
+ ```typescript
41
+ import { defineWorkflow, produces, acts, gate, gt, eq } from "@juicesharp/rpiv-workflow";
42
+ ```
43
+
44
+ ## Layer merging
45
+
46
+ The loader merges workflows from five layers. Each later layer overrides earlier by workflow name:
47
+
48
+ ```
49
+ built-in (registered by sibling packages like rpiv-pi)
50
+ ← user packs (~/.config/rpiv-workflow/workflows/*.ts, alpha-sorted)
51
+ ← user config (~/.config/rpiv-workflow/workflows.config.ts)
52
+ ← project packs (<cwd>/.rpiv-workflow/workflows/*.ts, alpha-sorted)
53
+ ← project config (<cwd>/.rpiv-workflow/workflows.config.ts)
54
+ ```
55
+
56
+ Within a layer, the config file wins by workflow name over pack files. Only the config file may set the `default` workflow (the one `/wf <input>` runs without specifying a name). Defaults cascade: `project config > user config > first registered workflow`.
57
+
58
+ ## Config files
59
+
60
+ The config file (`workflows.config.ts`) is the one TypeScript file you hand-edit. It accepts three default-export shapes:
61
+
62
+ ```typescript
63
+ // 1. A single Workflow
64
+ import { defineWorkflow, produces, acts } from "@juicesharp/rpiv-workflow";
65
+ export default defineWorkflow({
66
+ name: "ship",
67
+ start: "implement",
68
+ stages: { implement: acts(), commit: acts() },
69
+ edges: { implement: "commit", commit: "stop" },
70
+ });
71
+
72
+ // 2. A Workflow[] with a single entry
73
+ export default [/* one workflow */];
74
+
75
+ // 3. The envelope form — required when shipping multiple workflows
76
+ export default {
77
+ workflows: [/* many */],
78
+ default: "ship", // which one `/wf <input>` runs without a name
79
+ };
80
+ ```
81
+
82
+ ## Pack files
83
+
84
+ Pack files (`workflows/*.ts`) are installable bundles others can drop in. They accept only `Workflow | Workflow[]`. Packs **cannot** set `default` — that lives in the config file.
85
+
86
+ ```typescript
87
+ // workflows/my-pipeline.ts
88
+ import { defineWorkflow, produces, acts } from "@juicesharp/rpiv-workflow";
89
+ export default defineWorkflow({
90
+ name: "my-pipeline",
91
+ start: "research",
92
+ stages: { research: produces({ outcome: myOutcome }), implement: acts() },
93
+ edges: { research: "implement", implement: "stop" },
94
+ });
95
+ ```
96
+
97
+ This is what makes installable workflow packs safe: a pack contributes new workflows without overriding the user's default.
98
+
99
+ ## Example
100
+
101
+ A minimal workflow that chains two skills:
102
+
103
+ ```typescript
104
+ import { defineWorkflow, produces, acts } from "@juicesharp/rpiv-workflow";
105
+
106
+ export default defineWorkflow({
107
+ name: "review-and-ship",
108
+ start: "code-review",
109
+ stages: {
110
+ "code-review": produces({ outcome: myOutcome }),
111
+ commit: acts(),
112
+ },
113
+ edges: {
114
+ "code-review": "commit",
115
+ commit: "stop",
116
+ },
117
+ });
118
+ ```
119
+
120
+ Save this as `.rpiv-workflow/workflows.config.ts` in your project, then run `/wf review-and-ship implement auth feature`.
121
+
122
+ For the full DSL reference (all stage factories, routing, outcomes, validators), see [workflow-authoring.md](./workflow-authoring.md).
@@ -0,0 +1,106 @@
1
+ /**
2
+ * System prompt protocol for rpiv-workflow documentation delivery.
3
+ *
4
+ * Prepends a documentation reference block to the system prompt every turn
5
+ * via `before_agent_start`, following the same pattern as rpiv-args'
6
+ * skill-invocation protocol (`packages/rpiv-args/args.ts:439`). The block
7
+ * tells the agent where to find rpiv-workflow's authoring docs and when to
8
+ * read them — the agent reads the docs on-demand via the `read` tool.
9
+ *
10
+ * The protocol is small (~200 bytes) and identical every turn, so it
11
+ * benefits from Pi's prompt caching (same amortization as
12
+ * `SKILL_INVOCATION_PROTOCOL` in rpiv-args).
13
+ *
14
+ * This module has zero Pi type imports — the host interface declares only
15
+ * the `on("before_agent_start", ...)` shape needed for registration.
16
+ * At runtime, Pi passes the full ExtensionAPI which structurally satisfies
17
+ * this interface.
18
+ */
19
+
20
+ import { dirname, join } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Path resolution
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Absolute path to the rpiv-workflow package root. Resolved from this
29
+ * file's location via `import.meta.url` — works in both development
30
+ * (monorepo source) and production (installed npm package).
31
+ */
32
+ const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url));
33
+ const DOCS_DIR = join(PACKAGE_ROOT, "docs");
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Protocol
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /**
40
+ * System prompt protocol prepended every turn. Cached after first build
41
+ * so bytes are identical every turn → prompt-cache hit after turn 1
42
+ * (same posture as rpiv-args' `SKILL_INVOCATION_PROTOCOL`).
43
+ *
44
+ * Mirrors Pi's own "When asked about:" routing table pattern — path
45
+ * references + routing instructions, no inline content.
46
+ */
47
+ let protocolCache: string | undefined;
48
+
49
+ export function getDocsProtocol(): string {
50
+ if (protocolCache) return protocolCache;
51
+
52
+ protocolCache = [
53
+ "",
54
+ "rpiv-workflow documentation (read only when the user asks about creating, modifying, or troubleshooting workflows):",
55
+ `- Workflow basics: ${join(DOCS_DIR, "workflow-basics.md")}`,
56
+ `- Authoring DSL reference: ${join(DOCS_DIR, "workflow-authoring.md")}`,
57
+ "- When asked about workflows, read the relevant docs file(s) before generating any workflow code",
58
+ "- Generated workflow files must pass validateWorkflow() — verify before writing",
59
+ "",
60
+ ].join("\n");
61
+
62
+ return protocolCache;
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Host interface
67
+ // ---------------------------------------------------------------------------
68
+
69
+ /**
70
+ * Minimal host interface for docs-protocol registration. Declares only the
71
+ * `on` shape needed for `before_agent_start`. At runtime, Pi passes the
72
+ * full ExtensionAPI which structurally satisfies this.
73
+ *
74
+ * Not exported as part of rpiv-workflow's public type surface — internal
75
+ * to the extension wiring in `index.ts`.
76
+ */
77
+ export interface DocsProtocolHost {
78
+ on(event: "before_agent_start", handler: (event: { systemPrompt: string }) => { systemPrompt: string }): void;
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Handler
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * `before_agent_start` handler. Read-then-prepend — never replace,
87
+ * replacement clobbers prior extensions (rpiv-args, rpiv-pi guidance, etc.).
88
+ *
89
+ * Exported for testing.
90
+ */
91
+ export function handleBeforeAgentStart(event: { systemPrompt: string }): { systemPrompt: string } {
92
+ return { systemPrompt: getDocsProtocol() + event.systemPrompt };
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Registration
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /**
100
+ * Register the docs-protocol `before_agent_start` hook with the host.
101
+ * Called from the extension's default export alongside
102
+ * `registerWorkflowCommand`.
103
+ */
104
+ export function registerDocsProtocol(host: DocsProtocolHost): void {
105
+ host.on("before_agent_start", handleBeforeAgentStart);
106
+ }
package/fanout.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Fanout iteration. When a stage opts in via `StageDef.fanout`, the runner
3
+ * calls the user's `FanoutFn` to get a list of units and iterates one Pi
4
+ * session per unit. This module owns the iteration loop; `runner.ts`
5
+ * injects its primitives via `FanoutDeps` so the module never imports
6
+ * back (cycle-free).
7
+ *
8
+ * No markdown regex, no per-convention counter, no cap — rpiv-workflow
9
+ * stays convention-agnostic. Consumers (rpiv-pi etc.) own the FanoutFn
10
+ * body and any safety bounds it needs.
11
+ */
12
+
13
+ import type { FanoutUnit } from "./api.js";
14
+ import { buildLifecycleContext, skillStageRef } from "./lifecycle.js";
15
+ import { MSG_STAGE_COMPLETE, STATUS_FANOUT_UNIT, STATUS_KEY } from "./messages.js";
16
+ import type { FanoutSession, RunContext, RunnerCtx } from "./types.js";
17
+
18
+ export interface FanoutDeps {
19
+ runFanoutSession: (ctx: RunnerCtx, session: FanoutSession) => Promise<void>;
20
+ /**
21
+ * Resume the chain after the fanout node's units finish. Receives the
22
+ * fanout node's name so the routing layer can look up the outgoing
23
+ * edge from it.
24
+ */
25
+ advanceAfter: (curCtx: RunnerCtx, completedName: string, completedIdx: number, run: RunContext) => Promise<void>;
26
+ }
27
+
28
+ /**
29
+ * `skill` is the bundled skill body (threaded by the runner), not the node
30
+ * name. Aliased nodes (e.g. `implement-after-revise` invoking `implement`)
31
+ * tag unit rows + prompts with the skill body so audit consumers don't see
32
+ * two labels for the same work. Caller verifies node + fanout shape before
33
+ * invoking (see `runStage`).
34
+ *
35
+ * `currentName` is the fanout node's name in the workflow — passed to
36
+ * `advanceAfter` once the final unit completes so the routing layer can
37
+ * look up the outgoing edge from it.
38
+ *
39
+ * `units` was already produced by the user's `FanoutFn`; the iteration loop
40
+ * walks it in order. `p` is the 1-based index into `units` for the next
41
+ * session to run — the continuation-style self-call increments it.
42
+ */
43
+ export async function runFanout(
44
+ curCtx: RunnerCtx,
45
+ stageIdx: number,
46
+ currentName: string,
47
+ skill: string,
48
+ p: number,
49
+ units: readonly FanoutUnit[],
50
+ run: RunContext,
51
+ deps: FanoutDeps,
52
+ ): Promise<void> {
53
+ const { cwd, runId, totalStages, state } = run;
54
+
55
+ if (p > units.length) {
56
+ curCtx.ui.notify(MSG_STAGE_COMPLETE(skill), "info");
57
+ await deps.advanceAfter(curCtx, currentName, stageIdx, run);
58
+ return;
59
+ }
60
+
61
+ const unit = units[p - 1]!;
62
+ curCtx.ui.setStatus(STATUS_KEY, STATUS_FANOUT_UNIT(stageIdx + 1, totalStages, skill, unit.label));
63
+
64
+ const lifecycleCtx = buildLifecycleContext({
65
+ cwd,
66
+ runId,
67
+ workflow: run.workflow.name,
68
+ totalStages: run.totalStages,
69
+ trigger: run.trigger,
70
+ state,
71
+ });
72
+ await run.lifecycle.fire(
73
+ curCtx,
74
+ "onFanoutUnitStart",
75
+ skillStageRef(currentName, stageIdx + 1, skill),
76
+ unit,
77
+ p,
78
+ lifecycleCtx,
79
+ );
80
+
81
+ await deps.runFanoutSession(curCtx, {
82
+ cwd,
83
+ runId,
84
+ state,
85
+ prompt: `/skill:${skill} ${unit.prompt}`,
86
+ stageName: currentName,
87
+ skill,
88
+ lifecycle: run.lifecycle,
89
+ runIdentity: { workflow: run.workflow.name, totalStages: run.totalStages, trigger: run.trigger },
90
+ unitIndex: p,
91
+ label: unit.label,
92
+ id: unit.id,
93
+ stageIndex: stageIdx,
94
+ onSuccess: (freshCtx) => runFanout(freshCtx, stageIdx, currentName, skill, p + 1, units, run, deps),
95
+ });
96
+ }
package/host.ts ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Host ports — the contract the workflow runtime needs from its host
3
+ * environment, expressed in workflow-domain vocabulary.
4
+ *
5
+ * The package never re-exports `@earendil-works/pi-coding-agent` types
6
+ * from its public surface. Pi's `ExtensionAPI` / `ExtensionCommandContext`
7
+ * / `ReplacedSessionContext` structurally satisfy these ports, so
8
+ * embedders pass their Pi handles directly without casting; consumers
9
+ * wanting to drive the runtime from a non-Pi adapter implement these two
10
+ * interfaces.
11
+ *
12
+ * - `WorkflowHost` — registry-level host (default-export ctor + continue-policy sender).
13
+ * - `WorkflowContext` — per-command ctx passed into `runWorkflow`, also the
14
+ * replacement ctx delivered to `newSession`'s `withSession`
15
+ * callback. `sendUserMessage` is optional at the type
16
+ * level (the outer command ctx may not carry one) but
17
+ * the runtime guarantees it is present inside
18
+ * `withSession`.
19
+ *
20
+ * Compile-time tripwire: `host.test.ts` asserts Pi's concrete types
21
+ * extend these ports. If Pi's API drifts (a method renames, a signature
22
+ * tightens), `npm run check` fails immediately on that file.
23
+ */
24
+
25
+ /**
26
+ * Registry-level host. Default-exported function receives this; the
27
+ * runner also uses it for continue-policy stages (sends into the
28
+ * already-streaming agent) and for skill-registration preflight.
29
+ *
30
+ * The three methods we touch on Pi's `ExtensionAPI`. Anything beyond
31
+ * these is invisible to the runtime.
32
+ */
33
+ export interface WorkflowHost {
34
+ /** Register a slash command. Used by the `/wf` entry point. */
35
+ registerCommand(
36
+ name: string,
37
+ options: {
38
+ description?: string;
39
+ handler: (args: string, ctx: WorkflowContext) => Promise<void>;
40
+ },
41
+ ): void;
42
+ /**
43
+ * Send a user message into the active agent stream. Used by the
44
+ * continue-policy session handler.
45
+ *
46
+ * Pi declares this `void` at the type level but returns a Promise at
47
+ * runtime; we declare `void | Promise<void>` so `await` is safe in
48
+ * either world.
49
+ */
50
+ sendUserMessage(content: string): void | Promise<void>;
51
+ /** Enumerate currently registered slash commands. Used by skill-registration preflight. */
52
+ getCommands(): ReadonlyArray<{ name: string; source: string }>;
53
+ }
54
+
55
+ /**
56
+ * Per-command host ctx. Embedders hand this to `runWorkflow`; the
57
+ * runner threads it (and any replacement ctx returned by `newSession`)
58
+ * through stages.
59
+ *
60
+ * Exhaustive list of members the runtime touches — adding any reach
61
+ * outside this list is a port-widening decision, not an oversight.
62
+ *
63
+ * `sendUserMessage` is declared optional because the outer command ctx
64
+ * Pi delivers to a `/wf` handler does not carry one — only the
65
+ * replacement ctx inside `newSession`'s `withSession` callback does. The
66
+ * runtime guarantees it is present in that callback; callers outside
67
+ * `withSession` must not rely on it.
68
+ */
69
+ export interface WorkflowContext {
70
+ cwd: string;
71
+ hasUI: boolean;
72
+ ui: {
73
+ notify(message: string, level?: "info" | "warning" | "error"): void;
74
+ setStatus(key: string, text: string | undefined): void;
75
+ };
76
+ sessionManager: {
77
+ getBranch(): unknown;
78
+ };
79
+ isIdle(): boolean;
80
+ waitForIdle(): Promise<void>;
81
+ /**
82
+ * Open a fresh session and run `withSession` on the replacement ctx.
83
+ * Returns `{ cancelled: true }` if the host declined to spawn (user
84
+ * dismissed the swap, etc.). `cancelled: false` implies the outer
85
+ * ctx is now invalidated — all further work runs on the replacement
86
+ * delivered to `withSession`.
87
+ */
88
+ newSession(options: {
89
+ withSession: (replacement: WorkflowContext) => Promise<void>;
90
+ }): Promise<{ cancelled: boolean }>;
91
+ /**
92
+ * Present on the replacement ctx delivered inside `withSession`; not
93
+ * present on the outer command ctx. The runtime narrows internally
94
+ * before calling.
95
+ */
96
+ sendUserMessage?(content: string): Promise<void>;
97
+ }
package/index.ts ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * rpiv-workflow — Pi extension entry point.
3
+ *
4
+ * Registers the `/wf` slash command and (optionally) exposes the workflow
5
+ * runtime as a programmatic API for sibling packages that want to
6
+ * contribute built-in workflows via `registerBuiltIns(...)`.
7
+ *
8
+ * Skill-agnostic: the runner sends `/skill:<name>` via Pi's native skill
9
+ * dispatch — workflows can name any skill installed in Pi's search path
10
+ * (`~/.pi/agent/skills/`, `<cwd>/.pi/skills/`, or settings-declared
11
+ * `skillPaths[]`). This package ships ZERO built-in workflows. Bundles
12
+ * like `@juicesharp/rpiv-pi` opt in by calling `registerBuiltIns(...)`
13
+ * from their own extension entry.
14
+ *
15
+ * ─── Public surface, grouped by audience ────────────────────────────────
16
+ *
17
+ * 1. Authoring DSL — `./api.js`, `./predicates.js`, `./typebox-adapter.js`
18
+ * What a `workflows.config.ts` author imports to declare a workflow:
19
+ * `defineWorkflow`, `produces`, `acts`, `terminal`, `defineRoute`, `gate`,
20
+ * `Workflow`, `StageDef`, `EdgeFn`, `EdgeTarget`, `EdgeContext`,
21
+ * `StageSchema`, `StageKind`, `SessionPolicy`, `OutputSpec`,
22
+ * `READS_DATA`, the runtime-mirror `*_VALUES` arrays, the
23
+ * `gt`/`gte`/`lt`/`lte`/`eq` predicate helpers, and `typeboxSchema`
24
+ * (the TypeBox adapter).
25
+ *
26
+ * 2. Runner (programmatic embedders) — `./runner/index.js`, `./host.js`
27
+ * Drive a workflow from outside `/wf`: `runWorkflow`,
28
+ * `RunWorkflowOptions`, `RunWorkflowResult`. Embedders type their
29
+ * host handles against `WorkflowHost` / `WorkflowContext` (the host
30
+ * ports) — Pi's `ExtensionAPI` / `ExtensionCommandContext` /
31
+ * `ReplacedSessionContext` structurally satisfy them, so the
32
+ * values pass through without casting.
33
+ *
34
+ * 3. Loader (programmatic embedders) — `./load/index.js`
35
+ * Materialise the merged workflow registry: `loadWorkflows`,
36
+ * `LoadedWorkflows`, `Issue`, `LoadIssue`, `ConfigLayer`,
37
+ * `OverlayPaths`, `projectOverlayPaths`, `userOverlayPaths`.
38
+ *
39
+ * 4. Built-in registry (sibling packages) — `./built-ins.js`
40
+ * Contribute workflows to the lowest config layer:
41
+ * `registerBuiltIns`. (`getBuiltIns` is test-only and lives on
42
+ * `@juicesharp/rpiv-workflow/internal`.)
43
+ *
44
+ * 5. Output envelope + bundled outcomes — `./output.js`,
45
+ * `./outcomes/index.js`, `./handle.js`
46
+ * Inter-stage data channel (`Output<K, D>`, `OutputMeta`,
47
+ * `Artifact`, `ArtifactHandle` + constructors `fs`/`url`/
48
+ * `opaque`/`inline`/`handleToString`) + bundled outcomes
49
+ * (`sideEffectOutcome`, `gitCommitOutcome`, `GitCommitData`,
50
+ * `gitHeadSnapshot`, `GitHeadSnapshot`) + the bundled
51
+ * collector/parser catalog wireable into any custom `OutputSpec`:
52
+ * - collectors: `transcriptPathCollector` (regex over assistant
53
+ * text), `toolCallCollector` (universal tool_use observer),
54
+ * `workspaceDiffCollector` (git status diff pre/post),
55
+ * `gitCommitCollector` (commit detection), the wrappers
56
+ * `directoryPathCollector` / `urlCollector`, plus composition
57
+ * `unionCollectors` and the empty-list primitive `noopCollector`.
58
+ * - parsers: `jsonBodyParser` (parses primary fs body),
59
+ * `gitCommitParser`.
60
+ * The `.rpiv/artifacts/<bucket>/<file>.md` outcome + the
61
+ * markdown-frontmatter parser live in `@juicesharp/rpiv-pi`
62
+ * (`rpivArtifactMdOutcome` / `frontmatterParser`) — those are
63
+ * rpiv conventions, not framework defaults.
64
+ *
65
+ * 6. Custom-outcome authoring surface — `./output.js`
66
+ * `OutputSpec<Snapshot, Kind, Data>` (collector + optional parser),
67
+ * `ArtifactCollector`, `ArtifactParser`, `CollectCtx`,
68
+ * `CollectResult`, `ParseCtx`, `ParseResult`, `SnapshotCtx`,
69
+ * `SnapshotFn`. Sugar: `defineCollector` / `defineParser`.
70
+ *
71
+ * 7. Validation surfaces — `./validate-workflow.js`,
72
+ * `./validate-output.js`
73
+ * `validateWorkflow`, `WorkflowValidationIssue`,
74
+ * `validateOutputData`, `SchemaValidationFailure`.
75
+ *
76
+ * 8. Persistence (low-level — JSONL inspect) — `./state/index.js`
77
+ * Read past runs at `<cwd>/.rpiv/workflows/<run-id>.jsonl`:
78
+ * `listRuns`, `readHeader`, `readLastStage`, `listArtifacts`,
79
+ * `stateFilePath`, `workflowsDir`, `RunSummary`,
80
+ * `WorkflowHeader`, `WorkflowStage`. `recordStage` lives on
81
+ * `@juicesharp/rpiv-workflow/internal` (test-only — rpiv-pi's
82
+ * `[I3]` regression test pokes it directly; runner owns row
83
+ * writes, embedders never need it).
84
+ *
85
+ * 9. Runtime types — `./types.js`
86
+ * `RunState`.
87
+ *
88
+ * Per-module deep imports (`from "@juicesharp/rpiv-workflow/api.js"`)
89
+ * are NOT supported across the package boundary.
90
+ *
91
+ * ─── Pi-coupling boundary ───────────────────────────────────────────────
92
+ *
93
+ * The package's public type surface names ZERO `@earendil-works/pi-coding-agent`
94
+ * types. Every host capability the runtime needs is declared as a
95
+ * workflow-owned port in `./host.js`:
96
+ *
97
+ * • `WorkflowHost` — registry-level (default export + continue sends)
98
+ * • `WorkflowContext` — per-command ctx for `runWorkflow`; also the
99
+ * replacement ctx delivered to `newSession`'s
100
+ * `withSession` callback. `sendUserMessage` is
101
+ * optional at the type level (the outer command
102
+ * ctx omits it); the runtime guarantees it is
103
+ * present inside `withSession`.
104
+ *
105
+ * Pi's `ExtensionAPI` / `ExtensionCommandContext` are structurally
106
+ * compatible with these ports — embedders pass their existing Pi handles
107
+ * directly. A future non-Pi host implements the three port interfaces.
108
+ *
109
+ * The package no longer imports any value from
110
+ * `@earendil-works/pi-coding-agent` — `parseFrontmatter` moved to
111
+ * `@juicesharp/rpiv-pi` along with the rpiv-flavoured outcome
112
+ * (`rpivArtifactMdOutcome`). The peer dep stays for `pi-tui` types
113
+ * structural-compatibility only.
114
+ *
115
+ * `host.test.ts` carries a compile-time tripwire that fails immediately
116
+ * if Pi's types drift below the port's required shape.
117
+ */
118
+
119
+ import { registerWorkflowCommand } from "./command.js";
120
+ import { type DocsProtocolHost, registerDocsProtocol } from "./docs-protocol.js";
121
+ import type { WorkflowHost } from "./host.js";
122
+
123
+ export {
124
+ type ActsScriptFn,
125
+ acts,
126
+ type DefineRouteOptions,
127
+ defineRoute,
128
+ defineWorkflow,
129
+ type EdgeContext,
130
+ type EdgeFn,
131
+ type EdgeTarget,
132
+ type FanoutContext,
133
+ type FanoutFn,
134
+ type FanoutUnit,
135
+ gate,
136
+ marksReadsData,
137
+ ON_INVALID_VALUES,
138
+ type OnInvalid,
139
+ type ProducesScriptFn,
140
+ produces,
141
+ READS_DATA,
142
+ type ScriptContext,
143
+ SESSION_POLICIES,
144
+ type SessionPolicy,
145
+ STAGE_KINDS,
146
+ type StageDef,
147
+ type StageKind,
148
+ type StageSchema,
149
+ terminal,
150
+ type Workflow,
151
+ } from "./api.js";
152
+ export { registerBuiltIns } from "./built-ins.js";
153
+ export {
154
+ type Artifact,
155
+ type ArtifactHandle,
156
+ fs,
157
+ handleToString,
158
+ inline,
159
+ opaque,
160
+ url,
161
+ } from "./handle.js";
162
+ export type { WorkflowContext, WorkflowHost } from "./host.js";
163
+ export { type LifecycleContext, type LifecycleListeners, registerLifecycle, type StageRef } from "./lifecycle.js";
164
+ export type { ConfigLayer, Issue, LoadedWorkflows, LoadIssue, OverlayPaths } from "./load/index.js";
165
+ export { loadWorkflows, projectOverlayPaths, userOverlayPaths } from "./load/index.js";
166
+ export {
167
+ type DirectoryPathCollectorOpts,
168
+ directoryPathCollector,
169
+ type GitCommitData,
170
+ type GitHeadSnapshot,
171
+ gitCommitCollector,
172
+ gitCommitOutcome,
173
+ gitCommitParser,
174
+ gitHeadSnapshot,
175
+ jsonBodyParser,
176
+ noopCollector,
177
+ sideEffectOutcome,
178
+ type ToolCall,
179
+ type ToolCallCollectorOpts,
180
+ type TranscriptPathCollectorOpts,
181
+ toolCallCollector,
182
+ transcriptPathCollector,
183
+ type UrlCollectorOpts,
184
+ unionCollectors,
185
+ urlCollector,
186
+ type WorkspaceDiffCollectorOpts,
187
+ type WorkspaceDiffSnapshot,
188
+ workspaceDiffCollector,
189
+ } from "./outcomes/index.js";
190
+ export type {
191
+ ArtifactCollector,
192
+ ArtifactParser,
193
+ CollectCtx,
194
+ CollectResult,
195
+ Output,
196
+ OutputMeta,
197
+ OutputSpec,
198
+ ParseCtx,
199
+ ParseResult,
200
+ SnapshotCtx,
201
+ SnapshotFn,
202
+ } from "./output.js";
203
+ export { defineCollector, defineParser } from "./output-spec.js";
204
+ export { eq, gt, gte, lt, lte, type Predicate } from "./predicates.js";
205
+ export { type RunWorkflowOptions, type RunWorkflowResult, runWorkflow } from "./runner/index.js";
206
+ export {
207
+ listArtifacts,
208
+ listRuns,
209
+ type RunSummary,
210
+ readHeader,
211
+ readLastStage,
212
+ stateFilePath,
213
+ type WorkflowHeader,
214
+ type WorkflowStage,
215
+ workflowsDir,
216
+ } from "./state/index.js";
217
+ export { DEFAULT_TRIGGER, type RunTrigger } from "./triggers.js";
218
+ export { typeboxSchema } from "./typebox-adapter.js";
219
+ export type { RunState } from "./types.js";
220
+ export { type SchemaValidationFailure, validateOutputData } from "./validate-output.js";
221
+ export { validateWorkflow, type WorkflowValidationIssue } from "./validate-workflow.js";
222
+
223
+ export default function (host: WorkflowHost): void {
224
+ registerWorkflowCommand(host);
225
+ // Pi passes the full ExtensionAPI at runtime, which has on().
226
+ // WorkflowHost doesn't declare on() to keep the programmatic-embedder
227
+ // surface narrow. Cast to satisfy the type — Pi always provides the
228
+ // full API.
229
+ registerDocsProtocol(host as unknown as DocsProtocolHost);
230
+ }