@davidorex/pi-workflows 0.2.0 → 0.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.
Files changed (216) hide show
  1. package/CHANGELOG.md +7 -4
  2. package/README.md +12 -2
  3. package/agents/architecture-designer.agent.yaml +58 -0
  4. package/agents/architecture-inferrer.agent.yaml +60 -0
  5. package/agents/gap-identifier.agent.yaml +66 -0
  6. package/agents/handoff-writer.agent.yaml +64 -0
  7. package/agents/plan-creator.agent.yaml +63 -0
  8. package/agents/project-definer.agent.yaml +53 -0
  9. package/agents/project-inferrer.agent.yaml +63 -0
  10. package/agents/requirements-gatherer.agent.yaml +58 -0
  11. package/dist/agent-spec.d.ts +27 -0
  12. package/dist/agent-spec.d.ts.map +1 -0
  13. package/dist/agent-spec.js +112 -0
  14. package/dist/agent-spec.js.map +1 -0
  15. package/dist/checkpoint.d.ts +50 -0
  16. package/dist/checkpoint.d.ts.map +1 -0
  17. package/dist/checkpoint.js +109 -0
  18. package/dist/checkpoint.js.map +1 -0
  19. package/dist/completion.d.ts +18 -0
  20. package/dist/completion.d.ts.map +1 -0
  21. package/dist/completion.js +58 -0
  22. package/dist/completion.js.map +1 -0
  23. package/dist/dag.d.ts +56 -0
  24. package/dist/dag.d.ts.map +1 -0
  25. package/dist/dag.js +178 -0
  26. package/dist/dag.js.map +1 -0
  27. package/dist/dispatch.d.ts +35 -0
  28. package/dist/dispatch.d.ts.map +1 -0
  29. package/dist/dispatch.js +311 -0
  30. package/dist/dispatch.js.map +1 -0
  31. package/dist/expression.d.ts +64 -0
  32. package/dist/expression.d.ts.map +1 -0
  33. package/dist/expression.js +294 -0
  34. package/dist/expression.js.map +1 -0
  35. package/dist/format.d.ts +19 -0
  36. package/dist/format.d.ts.map +1 -0
  37. package/dist/format.js +42 -0
  38. package/dist/format.js.map +1 -0
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +724 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/output.d.ts +12 -0
  44. package/dist/output.d.ts.map +1 -0
  45. package/dist/output.js +59 -0
  46. package/dist/output.js.map +1 -0
  47. package/dist/state.d.ts +66 -0
  48. package/dist/state.d.ts.map +1 -0
  49. package/dist/state.js +227 -0
  50. package/dist/state.js.map +1 -0
  51. package/dist/step-agent.d.ts +35 -0
  52. package/dist/step-agent.d.ts.map +1 -0
  53. package/dist/step-agent.js +171 -0
  54. package/dist/step-agent.js.map +1 -0
  55. package/dist/step-command.d.ts +21 -0
  56. package/dist/step-command.d.ts.map +1 -0
  57. package/dist/step-command.js +150 -0
  58. package/dist/step-command.js.map +1 -0
  59. package/dist/step-foreach.d.ts +33 -0
  60. package/dist/step-foreach.d.ts.map +1 -0
  61. package/dist/step-foreach.js +112 -0
  62. package/dist/step-foreach.js.map +1 -0
  63. package/dist/step-gate.d.ts +18 -0
  64. package/dist/step-gate.d.ts.map +1 -0
  65. package/dist/step-gate.js +137 -0
  66. package/dist/step-gate.js.map +1 -0
  67. package/dist/step-loop.d.ts +32 -0
  68. package/dist/step-loop.d.ts.map +1 -0
  69. package/dist/step-loop.js +278 -0
  70. package/dist/step-loop.js.map +1 -0
  71. package/dist/step-monitor.d.ts +56 -0
  72. package/dist/step-monitor.d.ts.map +1 -0
  73. package/dist/step-monitor.js +317 -0
  74. package/dist/step-monitor.js.map +1 -0
  75. package/dist/step-parallel.d.ts +43 -0
  76. package/dist/step-parallel.d.ts.map +1 -0
  77. package/dist/step-parallel.js +122 -0
  78. package/dist/step-parallel.js.map +1 -0
  79. package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
  80. package/dist/step-pause.d.ts.map +1 -0
  81. package/dist/step-pause.js +22 -0
  82. package/dist/step-pause.js.map +1 -0
  83. package/dist/step-shared.d.ts +63 -0
  84. package/dist/step-shared.d.ts.map +1 -0
  85. package/dist/step-shared.js +136 -0
  86. package/dist/step-shared.js.map +1 -0
  87. package/dist/step-transform.d.ts +11 -0
  88. package/dist/step-transform.d.ts.map +1 -0
  89. package/dist/step-transform.js +41 -0
  90. package/dist/step-transform.js.map +1 -0
  91. package/dist/template.d.ts +51 -0
  92. package/dist/template.d.ts.map +1 -0
  93. package/{src/template.ts → dist/template.js} +32 -38
  94. package/dist/template.js.map +1 -0
  95. package/dist/test-helpers.d.ts +17 -0
  96. package/dist/test-helpers.d.ts.map +1 -0
  97. package/dist/test-helpers.js +46 -0
  98. package/dist/test-helpers.js.map +1 -0
  99. package/dist/tui.d.ts +47 -0
  100. package/dist/tui.d.ts.map +1 -0
  101. package/dist/tui.js +143 -0
  102. package/dist/tui.js.map +1 -0
  103. package/dist/types.d.ts +165 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +3 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/workflow-discovery.d.ts +23 -0
  108. package/dist/workflow-discovery.d.ts.map +1 -0
  109. package/dist/workflow-discovery.js +103 -0
  110. package/dist/workflow-discovery.js.map +1 -0
  111. package/dist/workflow-executor.d.ts +50 -0
  112. package/dist/workflow-executor.d.ts.map +1 -0
  113. package/dist/workflow-executor.js +738 -0
  114. package/dist/workflow-executor.js.map +1 -0
  115. package/dist/workflow-sdk.d.ts +44 -0
  116. package/dist/workflow-sdk.d.ts.map +1 -0
  117. package/dist/workflow-sdk.js +457 -0
  118. package/dist/workflow-sdk.js.map +1 -0
  119. package/dist/workflow-spec.d.ts +33 -0
  120. package/dist/workflow-spec.d.ts.map +1 -0
  121. package/dist/workflow-spec.js +476 -0
  122. package/dist/workflow-spec.js.map +1 -0
  123. package/dist/workflows-dir.d.ts +3 -0
  124. package/dist/workflows-dir.d.ts.map +1 -0
  125. package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
  126. package/dist/workflows-dir.js.map +1 -0
  127. package/package.json +53 -38
  128. package/schemas/decomposition-specs.schema.json +48 -48
  129. package/schemas/execution-results.schema.json +48 -48
  130. package/schemas/investigation-findings.schema.json +43 -43
  131. package/schemas/pattern-analysis.schema.json +41 -41
  132. package/schemas/phase.schema.json +48 -0
  133. package/schemas/plan-breakdown.schema.json +20 -20
  134. package/schemas/quality-analysis.schema.json +33 -33
  135. package/schemas/research-findings.schema.json +42 -42
  136. package/schemas/structure-analysis.schema.json +40 -40
  137. package/schemas/synthesis.schema.json +31 -31
  138. package/schemas/verifier-output.schema.json +94 -94
  139. package/skill-narrative.md +107 -0
  140. package/skills/pi-workflows/SKILL.md +185 -0
  141. package/skills/pi-workflows/references/bundled-resources.md +87 -0
  142. package/templates/architecture-designer/task.md +117 -0
  143. package/templates/architecture-inferrer/task.md +61 -0
  144. package/templates/gap-identifier/task.md +103 -0
  145. package/templates/handoff-writer/task.md +91 -0
  146. package/templates/plan-creator/task.md +143 -0
  147. package/templates/project-definer/task.md +67 -0
  148. package/templates/project-inferrer/task.md +56 -0
  149. package/templates/requirements-gatherer/task.md +90 -0
  150. package/workflows/analyze-existing-project.workflow.yaml +94 -0
  151. package/workflows/create-handoff.workflow.yaml +66 -0
  152. package/workflows/create-phase.workflow.yaml +1 -1
  153. package/workflows/do-gap.workflow.yaml +4 -4
  154. package/workflows/fix-audit.workflow.yaml +1 -1
  155. package/workflows/gap-to-phase.workflow.yaml +1 -1
  156. package/workflows/init-new-project.workflow.yaml +81 -0
  157. package/workflows/plan-from-requirements.workflow.yaml +59 -0
  158. package/workflows/self-implement.workflow.yaml +3 -3
  159. package/workflows/typed-analysis.workflow.yaml +4 -4
  160. package/src/agent-spec.test.ts +0 -289
  161. package/src/agent-spec.ts +0 -122
  162. package/src/block-validation.test.ts +0 -350
  163. package/src/checkpoint.test.ts +0 -237
  164. package/src/checkpoint.ts +0 -140
  165. package/src/completion.test.ts +0 -143
  166. package/src/completion.ts +0 -68
  167. package/src/dag.test.ts +0 -350
  168. package/src/dag.ts +0 -219
  169. package/src/dispatch.test.ts +0 -475
  170. package/src/dispatch.ts +0 -353
  171. package/src/expression.test.ts +0 -353
  172. package/src/expression.ts +0 -332
  173. package/src/format.test.ts +0 -80
  174. package/src/format.ts +0 -41
  175. package/src/graduated-failure.test.ts +0 -556
  176. package/src/index.test.ts +0 -114
  177. package/src/index.ts +0 -516
  178. package/src/loop.test.ts +0 -551
  179. package/src/output.test.ts +0 -213
  180. package/src/output.ts +0 -70
  181. package/src/parallel-integration.test.ts +0 -175
  182. package/src/resume.test.ts +0 -192
  183. package/src/state.test.ts +0 -192
  184. package/src/state.ts +0 -254
  185. package/src/step-agent.test.ts +0 -327
  186. package/src/step-agent.ts +0 -178
  187. package/src/step-command.test.ts +0 -330
  188. package/src/step-command.ts +0 -132
  189. package/src/step-foreach.test.ts +0 -647
  190. package/src/step-foreach.ts +0 -148
  191. package/src/step-gate.test.ts +0 -185
  192. package/src/step-gate.ts +0 -116
  193. package/src/step-loop.test.ts +0 -626
  194. package/src/step-loop.ts +0 -323
  195. package/src/step-parallel.test.ts +0 -475
  196. package/src/step-parallel.ts +0 -168
  197. package/src/step-pause.test.ts +0 -30
  198. package/src/step-shared.test.ts +0 -355
  199. package/src/step-shared.ts +0 -157
  200. package/src/step-transform.test.ts +0 -155
  201. package/src/step-transform.ts +0 -47
  202. package/src/template-integration.test.ts +0 -72
  203. package/src/template.test.ts +0 -162
  204. package/src/test-helpers.ts +0 -51
  205. package/src/tui.test.ts +0 -355
  206. package/src/tui.ts +0 -182
  207. package/src/types.ts +0 -195
  208. package/src/verifier-schema.test.ts +0 -226
  209. package/src/workflow-discovery.test.ts +0 -105
  210. package/src/workflow-discovery.ts +0 -114
  211. package/src/workflow-executor.test.ts +0 -1532
  212. package/src/workflow-executor.ts +0 -810
  213. package/src/workflow-sdk.test.ts +0 -238
  214. package/src/workflow-sdk.ts +0 -262
  215. package/src/workflow-spec.test.ts +0 -576
  216. package/src/workflow-spec.ts +0 -471
package/src/tui.ts DELETED
@@ -1,182 +0,0 @@
1
- /**
2
- * TUI progress widget for workflow execution.
3
- * Shows step status, timing, cost, and parallel execution indicators.
4
- */
5
- import type { Theme } from "@mariozechner/pi-coding-agent";
6
- import type { TUI, Component } from "@mariozechner/pi-tui";
7
- import type { WorkflowSpec, ExecutionState, StepResult } from "./types.ts";
8
- import { formatDuration, formatCost, formatTokens } from "./format.ts";
9
-
10
- export interface StepActivity {
11
- tool: string; // "read", "edit", "bash", etc.
12
- preview: string; // "src/index.ts", "npm test", etc.
13
- timestamp: number;
14
- }
15
-
16
- export interface StepOutputSummary {
17
- tasks?: Array<{ name: string; status: string; files?: string[] }>;
18
- testCount?: number;
19
- note?: string;
20
- }
21
-
22
- export interface ProgressWidgetState {
23
- spec: WorkflowSpec;
24
- state: ExecutionState;
25
- currentStep?: string; // name of the currently running step (comma-separated for parallel)
26
- startTime: number; // Date.now() when workflow started
27
- parallelSubSteps?: Record<string, import("./types.ts").StepResult>; // live sub-step results for parallel step
28
- resumedSteps?: number; // number of steps carried from a prior run (resume indicator)
29
- activities: Map<string, StepActivity[]>; // stepName → recent tool calls (ring buffer, last 5)
30
- outputSummaries: Map<string, StepOutputSummary>; // stepName → parsed output after completion
31
- }
32
-
33
- /**
34
- * Create a widget factory for ctx.ui.setWidget().
35
- * Returns a function that pi calls to get the component.
36
- *
37
- * The returned component renders a compact progress view:
38
- * ─────────────────────────────────────
39
- * ● bugfix step 2/3 1m32s
40
- * ✓ diagnose 42s $0.03 12k tok
41
- * ▸ fix 50s 8k tok...
42
- * · verify
43
- * ─────────────────────────────────────
44
- */
45
- export function createProgressWidget(
46
- widgetState: ProgressWidgetState,
47
- ): (tui: TUI, theme: Theme) => Component & { dispose?(): void } {
48
- return (tui: TUI, theme: Theme) => {
49
- /** Pulse interval (ms) for the elapsed-time ticker. Balances update frequency vs overhead. */
50
- const PULSE_INTERVAL_MS = 800;
51
- let pulseOn = true;
52
- const interval = setInterval(() => {
53
- pulseOn = !pulseOn;
54
- tui.requestRender();
55
- }, PULSE_INTERVAL_MS);
56
-
57
- return {
58
- render(width: number): string[] {
59
- const lines: string[] = [];
60
- const stepNames = Object.keys(widgetState.spec.steps);
61
- const totalSteps = stepNames.length;
62
-
63
- // Parse current steps (may be comma-separated for parallel)
64
- const currentSteps = widgetState.currentStep?.split(", ") ?? [];
65
- const parallelCount = currentSteps.length;
66
-
67
- // Determine current step number
68
- let currentStepNum = 0;
69
- if (currentSteps.length > 0 && currentSteps[0]) {
70
- const idx = stepNames.indexOf(currentSteps[0]);
71
- currentStepNum = idx >= 0 ? idx + 1 : 0;
72
- } else {
73
- // Count completed steps
74
- currentStepNum = Object.values(widgetState.state.steps).filter(
75
- (s) => s.status === "completed",
76
- ).length;
77
- }
78
-
79
- const elapsed = formatDuration(Date.now() - widgetState.startTime);
80
- const workflowName = theme.bold(widgetState.spec.name);
81
- const indicator = pulseOn
82
- ? theme.fg("accent", "\u25cf")
83
- : theme.fg("dim", "\u25cf");
84
-
85
- const parallelTag = parallelCount > 1 ? ` [${parallelCount} parallel]` : "";
86
- const headerLine = `${indicator} ${workflowName} step ${currentStepNum}/${totalSteps}${parallelTag} ${theme.fg("dim", elapsed)}`;
87
- lines.push(headerLine.length > width ? headerLine.slice(0, width) : headerLine);
88
-
89
- // Resumed indicator
90
- if (widgetState.resumedSteps) {
91
- const resumedLine = ` ${theme.fg("dim", "\u21bb")} Resumed: ${widgetState.resumedSteps} steps from prior run`;
92
- lines.push(resumedLine.length > width ? resumedLine.slice(0, width) : resumedLine);
93
- }
94
-
95
- // Paused indicator
96
- if (widgetState.state.status === "paused") {
97
- const pausedLine = ` ${theme.fg("accent", "\u23f8")} Paused`;
98
- lines.push(pausedLine.length > width ? pausedLine.slice(0, width) : pausedLine);
99
- }
100
-
101
- // Step lines
102
- for (const stepName of stepNames) {
103
- const stepResult: StepResult | undefined = widgetState.state.steps[stepName];
104
- let line: string;
105
-
106
- if (stepResult && stepResult.status === "skipped") {
107
- // Skipped step: ⊘ stepName [skipped]
108
- line = ` ${theme.fg("dim", "\u2298")} ${stepName} ${theme.fg("dim", "[skipped]")}`;
109
- } else if (stepResult && stepResult.status === "completed") {
110
- const dur = formatDuration(stepResult.durationMs);
111
- const cost = formatCost(stepResult.usage.cost);
112
- const tok = formatTokens(stepResult.usage.input + stepResult.usage.output);
113
- // Show step type indicator for gate/transform
114
- const typeTag = stepResult.agent === "gate" ? " [gate]" : stepResult.agent === "transform" ? " [transform]" : "";
115
- const truncTag = stepResult.truncated ? ` ${theme.fg("warning", "[truncated]")}` : "";
116
- line = ` ${theme.fg("success", "\u2713")} ${stepName}${typeTag} ${theme.fg("dim", dur)} ${theme.fg("dim", cost)} ${theme.fg("dim", tok)}${truncTag}`;
117
- lines.push(line.length > width ? line.slice(0, width) : line);
118
-
119
- // Render output summary sub-lines (capped at 3)
120
- const summary = widgetState.outputSummaries?.get(stepName);
121
- if (summary) {
122
- let summaryLines = 0;
123
- const MAX_SUMMARY_LINES = 3;
124
- if (summary.tasks) {
125
- for (const task of summary.tasks) {
126
- if (summaryLines >= MAX_SUMMARY_LINES) break;
127
- const files = task.files?.join(", ") || "";
128
- const statusIcon = task.status === "done" ? "\u2713" : task.status === "failed" ? "\u2717" : "\u00b7";
129
- const taskLine = ` ${theme.fg("dim", statusIcon)} ${theme.fg("dim", task.name)}${files ? " " + theme.fg("dim", files) : ""}`;
130
- lines.push(taskLine.length > width ? taskLine.slice(0, width) : taskLine);
131
- summaryLines++;
132
- }
133
- }
134
- if (summary.testCount && summaryLines < MAX_SUMMARY_LINES) {
135
- const testLine = ` ${theme.fg("dim", `${summary.testCount} tests pass`)}`;
136
- lines.push(testLine.length > width ? testLine.slice(0, width) : testLine);
137
- summaryLines++;
138
- }
139
- if (summary.note && summaryLines < MAX_SUMMARY_LINES) {
140
- const noteLine = ` ${theme.fg("dim", summary.note)}`;
141
- lines.push(noteLine.length > width ? noteLine.slice(0, width) : noteLine);
142
- }
143
- }
144
- continue; // skip the push below, already pushed
145
- } else if (stepResult && stepResult.status === "failed") {
146
- const dur = formatDuration(stepResult.durationMs);
147
- const errorPreview = stepResult.error || "Unknown error";
148
- line = ` ${theme.fg("error", "\u2717")} ${stepName} ${theme.fg("dim", dur)} ${errorPreview}`;
149
- } else if (stepResult && stepResult.agent === "parallel") {
150
- // Completed parallel step — show with sub-step count
151
- const dur = formatDuration(stepResult.durationMs);
152
- const cost = formatCost(stepResult.usage.cost);
153
- const tok = formatTokens(stepResult.usage.input + stepResult.usage.output);
154
- line = ` ${theme.fg("success", "\u2713")} ${stepName} [parallel] ${theme.fg("dim", dur)} ${theme.fg("dim", cost)} ${theme.fg("dim", tok)}`;
155
- } else if (currentSteps.includes(stepName)) {
156
- const stepElapsed = formatDuration(Date.now() - widgetState.startTime);
157
- line = ` ${theme.fg("accent", "\u25b8")} ${theme.fg("accent", stepName)} ${theme.fg("dim", stepElapsed + "...")}`;
158
- lines.push(line.length > width ? line.slice(0, width) : line);
159
-
160
- // Render most recent tool activity under running step
161
- const activities = widgetState.activities?.get(stepName);
162
- if (activities && activities.length > 0) {
163
- const latest = activities[activities.length - 1];
164
- const actLine = ` ${theme.fg("dim", latest.tool)} ${theme.fg("dim", latest.preview)}`;
165
- lines.push(actLine.length > width ? actLine.slice(0, width) : actLine);
166
- }
167
- continue; // skip the push below, already pushed
168
- } else {
169
- line = ` ${theme.fg("dim", "\u00b7")} ${stepName}`;
170
- }
171
-
172
- lines.push(line.length > width ? line.slice(0, width) : line);
173
- }
174
-
175
- return lines;
176
- },
177
- dispose() {
178
- clearInterval(interval);
179
- },
180
- };
181
- };
182
- }
package/src/types.ts DELETED
@@ -1,195 +0,0 @@
1
- // ── Workflow Spec (parsed from YAML) ──
2
-
3
- export interface WorkflowSpec {
4
- name: string;
5
- description: string;
6
- version?: string;
7
- input?: Record<string, unknown>; // JSON Schema object
8
- output?: Record<string, unknown>; // JSON Schema object
9
- triggerTurn?: boolean; // default: true
10
- completion?: CompletionSpec; // controls post-completion message to main LLM
11
- steps: Record<string, StepSpec>; // ordered (YAML preserves insertion order)
12
- artifacts?: Record<string, ArtifactSpec>; // persistent files written after workflow completion
13
- // Set by discovery, not by YAML:
14
- source: "user" | "project";
15
- filePath: string;
16
- }
17
-
18
- export interface ArtifactSpec {
19
- path: string; // destination path, may contain ${{ }} expressions
20
- from: string; // ${{ }} expression for the data source
21
- schema?: string; // optional JSON Schema to validate before writing
22
- }
23
-
24
- export interface RetryConfig {
25
- maxAttempts?: number; // default 1 (no retry). Total attempts, not retries.
26
- onExhausted?: 'fail' | 'skip'; // default 'fail'
27
- steeringMessage?: string; // optional custom steering text for retries (agent steps only)
28
- }
29
-
30
- export interface StepSpec {
31
- agent?: string; // optional — not needed for gate/transform steps
32
- model?: string; // model override
33
- input?: Record<string, unknown>; // values may contain ${{ }} expressions
34
- output?: StepOutputSpec;
35
- when?: string; // ${{ }} expression, evaluated as truthy/falsy
36
- timeout?: { seconds: number };
37
- retry?: RetryConfig; // per-step retry config
38
- loop?: LoopSpec;
39
- gate?: GateSpec;
40
- transform?: TransformSpec;
41
- parallel?: Record<string, StepSpec>; // named sub-steps to run concurrently
42
- pause?: string | boolean; // pause step — string is message to display, true = pause with no message
43
- command?: string; // shell command to run (captures stdout as output)
44
- forEach?: string; // ${{ }} expression resolving to an array
45
- as?: string; // variable name to bind each element (default: "item")
46
- workflow?: string; // phase 6 — not yet supported
47
- }
48
- // Note: exactly one of agent, gate, transform, loop, or parallel must be set.
49
- // workflow is phase 6.
50
-
51
- export interface LoopSpec {
52
- maxAttempts: number; // max iterations (required)
53
- attempts?: string; // ${{ }} expression overriding maxAttempts at runtime
54
- steps: Record<string, StepSpec>; // steps to execute each iteration
55
- onExhausted?: StepSpec; // step to run if all attempts fail
56
- }
57
-
58
- export interface GateSpec {
59
- check: string; // shell command to run
60
- onPass?: "continue" | "break"; // default: "continue" (proceed to next step)
61
- onFail?: "continue" | "break" | "fail"; // default: "fail" (stop the workflow)
62
- }
63
-
64
- export interface TransformSpec {
65
- /**
66
- * A mapping of output field names to ${{ }} expressions.
67
- * The result is an object with each field resolved.
68
- * No LLM invocation — pure data transformation.
69
- */
70
- mapping: Record<string, unknown>;
71
- }
72
-
73
- export interface StepOutputSpec {
74
- format?: "json" | "text"; // default: "text"
75
- schema?: string; // path to JSON Schema file, relative to workflow spec
76
- path?: string; // output file path — may contain ${{ }} expressions
77
- }
78
-
79
- // ── Agent Spec (loaded from .md frontmatter or .agent.yaml) ──
80
-
81
- export interface AgentSpec {
82
- name: string;
83
- description?: string;
84
- role?: string; // sensor | reasoning | action | quality
85
- systemPrompt?: string; // compiled system prompt (from template rendering)
86
- promptTemplate?: string; // system prompt template path
87
- taskTemplate?: string; // task prompt template path
88
- model?: string;
89
- thinking?: string;
90
- tools?: string[];
91
- extensions?: string[];
92
- skills?: string[];
93
- output?: string; // output file path
94
- inputSchema?: Record<string, unknown>; // JSON Schema for input
95
- outputFormat?: "json" | "text"; // what the agent produces
96
- outputSchema?: string; // path to output JSON Schema
97
- }
98
-
99
- // ── Execution State ──
100
-
101
- export interface ExecutionState {
102
- input: unknown;
103
- steps: Record<string, StepResult>;
104
- status: "running" | "completed" | "failed" | "paused";
105
- loop?: LoopState; // set when inside a loop
106
- // Resume support:
107
- workflowName?: string; // which workflow this run belongs to
108
- specVersion?: string; // workflow spec version at time of run
109
- startedAt?: string; // ISO timestamp of run start
110
- updatedAt?: string; // ISO timestamp of last state write
111
- }
112
-
113
- export interface LoopState {
114
- stepName: string; // name of the loop step
115
- iteration: number; // current iteration (0-based)
116
- maxAttempts: number;
117
- priorAttempts: LoopAttempt[]; // results from previous iterations
118
- }
119
-
120
- export interface LoopAttempt {
121
- iteration: number;
122
- steps: Record<string, StepResult>; // results of all steps in this iteration
123
- }
124
-
125
- export interface StepResult {
126
- step: string;
127
- agent: string;
128
- status: "completed" | "failed" | "skipped";
129
- output?: unknown; // parsed structured output (if schema-bound)
130
- textOutput?: string; // raw text from last assistant message
131
- outputPath?: string; // absolute path to persisted output JSON
132
- sessionLog?: string; // path to session log file
133
- usage: StepUsage;
134
- durationMs: number;
135
- error?: string; // error message if failed
136
- truncated?: boolean; // true if stdout exceeded MAX_STDOUT_BYTES and data was lost
137
- warnings?: string[]; // non-fatal issues encountered during step execution
138
- attempt?: number; // which attempt this result is from (1-based)
139
- totalAttempts?: number; // total attempts made
140
- priorErrors?: string[]; // error messages from prior failed attempts
141
- }
142
-
143
- export interface StepUsage {
144
- input: number;
145
- output: number;
146
- cacheRead: number;
147
- cacheWrite: number;
148
- cost: number;
149
- turns: number;
150
- }
151
-
152
- // ── Workflow Result (returned to caller) ──
153
-
154
- export interface WorkflowResult {
155
- workflow: string;
156
- runId: string;
157
- status: "completed" | "failed" | "paused";
158
- steps: Record<string, StepResult>;
159
- output?: unknown; // final workflow output (last step's output, or explicit)
160
- totalUsage: StepUsage;
161
- totalDurationMs: number;
162
- runDir: string; // absolute path to .workflows/runs/<name>/runs/<run-id>/
163
- artifacts?: Record<string, string>; // name → absolute path of written artifact files
164
- }
165
-
166
- // ── Completion Spec ──
167
-
168
- export interface CompletionSpec {
169
- message?: string; // LLM instruction text (may contain ${{ }} expressions)
170
- include?: string[]; // expression paths to resolve and attach as data
171
- template?: string; // full template with ${{ }} (mutually exclusive with message)
172
- }
173
-
174
- // ── Expression Scope (what ${{ }} expressions resolve against) ──
175
-
176
- export interface ExpressionScope {
177
- input: unknown;
178
- steps: Record<string, StepResult>;
179
- [key: string]: unknown; // allows use as Record<string, unknown>
180
- }
181
-
182
- // ── Completion Scope (wider scope for completion templates) ──
183
-
184
- export interface CompletionScope {
185
- input: unknown;
186
- steps: Record<string, StepResult>;
187
- totalUsage: StepUsage;
188
- totalDurationMs: number;
189
- runDir: string;
190
- runId: string;
191
- workflow: string;
192
- status: "completed" | "failed";
193
- output?: unknown;
194
- [key: string]: unknown; // allows use as Record<string, unknown>
195
- }
@@ -1,226 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert";
3
- import path from "node:path";
4
- import { validateFromFile } from "@davidorex/pi-project/src/schema-validator.ts";
5
-
6
- const schemaPath = path.resolve(import.meta.dirname, "..", "schemas", "verifier-output.schema.json");
7
-
8
- function validVerifierOutput() {
9
- return {
10
- status: "passed",
11
- score: "3/3",
12
- truths: [
13
- { truth: "Tests pass", status: "verified", evidence: "npm test exited 0" },
14
- { truth: "File exists", status: "verified", evidence: "ls confirmed" },
15
- { truth: "Schema valid", status: "verified", evidence: "ajv validates" },
16
- ],
17
- criteria_results: [
18
- {
19
- criterion: "All tests pass",
20
- verify_method: "command",
21
- status: "passed",
22
- expected_outcome: "exit code 0",
23
- actual_outcome: "exit code 0",
24
- evidence: "npm test output: 3 passing",
25
- },
26
- {
27
- criterion: "File structure correct",
28
- verify_method: "inspect",
29
- status: "passed",
30
- evidence: "All expected files present",
31
- },
32
- {
33
- criterion: "UI renders correctly",
34
- verify_method: "inspect",
35
- status: "skipped",
36
- evidence: "Requires visual inspection",
37
- },
38
- ],
39
- };
40
- }
41
-
42
- describe("verifier-output schema validation", () => {
43
- it("validates a complete verifier output", () => {
44
- const data = validVerifierOutput();
45
- const result = validateFromFile(schemaPath, data, "test");
46
- assert.deepStrictEqual(result, data);
47
- });
48
-
49
- it("validates with all optional fields present", () => {
50
- const data = {
51
- ...validVerifierOutput(),
52
- artifacts: [
53
- { path: "src/index.ts", status: "verified", exists: true, substantive: true, wired: true, details: "Main entry point" },
54
- { path: "src/missing.ts", status: "missing", exists: false },
55
- ],
56
- requirements_coverage: [
57
- { requirement_id: "REQ-001", status: "satisfied", supporting_truths: ["Tests pass"] },
58
- { requirement_id: "REQ-002", status: "needs_human" },
59
- ],
60
- human_verification: [
61
- { name: "UI layout", test: "Open browser and check layout", expected: "Grid renders correctly", why_human: "Requires visual inspection" },
62
- ],
63
- gaps: [
64
- { truth: "Performance benchmark", status: "failed", reason: "No benchmark data available" },
65
- ],
66
- };
67
- const result = validateFromFile(schemaPath, data, "test");
68
- assert.ok(result);
69
- });
70
-
71
- it("validates with empty optional arrays", () => {
72
- const data = {
73
- ...validVerifierOutput(),
74
- artifacts: [],
75
- requirements_coverage: [],
76
- human_verification: [],
77
- gaps: [],
78
- };
79
- const result = validateFromFile(schemaPath, data, "test");
80
- assert.ok(result);
81
- });
82
-
83
- it("rejects missing required field: status", () => {
84
- const data = validVerifierOutput();
85
- delete (data as any).status;
86
- assert.throws(
87
- () => validateFromFile(schemaPath, data, "test"),
88
- (err: any) => err.message.includes("Validation failed"),
89
- );
90
- });
91
-
92
- it("rejects missing required field: score", () => {
93
- const data = validVerifierOutput();
94
- delete (data as any).score;
95
- assert.throws(
96
- () => validateFromFile(schemaPath, data, "test"),
97
- (err: any) => err.message.includes("Validation failed"),
98
- );
99
- });
100
-
101
- it("rejects missing required field: truths", () => {
102
- const data = validVerifierOutput();
103
- delete (data as any).truths;
104
- assert.throws(
105
- () => validateFromFile(schemaPath, data, "test"),
106
- (err: any) => err.message.includes("Validation failed"),
107
- );
108
- });
109
-
110
- it("rejects missing required field: criteria_results", () => {
111
- const data = validVerifierOutput();
112
- delete (data as any).criteria_results;
113
- assert.throws(
114
- () => validateFromFile(schemaPath, data, "test"),
115
- (err: any) => err.message.includes("Validation failed"),
116
- );
117
- });
118
-
119
- it("rejects invalid status enum value", () => {
120
- const data = validVerifierOutput();
121
- data.status = "invalid_status" as any;
122
- assert.throws(
123
- () => validateFromFile(schemaPath, data, "test"),
124
- (err: any) => err.message.includes("Validation failed"),
125
- );
126
- });
127
-
128
- it("rejects invalid truth status enum value", () => {
129
- const data = validVerifierOutput();
130
- data.truths[0].status = "bad_status" as any;
131
- assert.throws(
132
- () => validateFromFile(schemaPath, data, "test"),
133
- (err: any) => err.message.includes("Validation failed"),
134
- );
135
- });
136
-
137
- it("rejects invalid verify_method enum value", () => {
138
- const data = validVerifierOutput();
139
- data.criteria_results[0].verify_method = "magic" as any;
140
- assert.throws(
141
- () => validateFromFile(schemaPath, data, "test"),
142
- (err: any) => err.message.includes("Validation failed"),
143
- );
144
- });
145
-
146
- it("rejects invalid criteria_results status enum value", () => {
147
- const data = validVerifierOutput();
148
- data.criteria_results[0].status = "invalid" as any;
149
- assert.throws(
150
- () => validateFromFile(schemaPath, data, "test"),
151
- (err: any) => err.message.includes("Validation failed"),
152
- );
153
- });
154
-
155
- it("rejects invalid artifact status enum value", () => {
156
- const data = {
157
- ...validVerifierOutput(),
158
- artifacts: [{ path: "test.ts", status: "unknown" }],
159
- };
160
- assert.throws(
161
- () => validateFromFile(schemaPath, data, "test"),
162
- (err: any) => err.message.includes("Validation failed"),
163
- );
164
- });
165
- });
166
-
167
- describe("plan-breakdown schema validation", () => {
168
- const planSchemaPath = path.resolve(import.meta.dirname, "..", "schemas", "plan-breakdown.schema.json");
169
-
170
- it("validates a valid plan breakdown", () => {
171
- const data = {
172
- plans: [
173
- {
174
- name: "implement-auth",
175
- intent: "Add authentication",
176
- tasks: ["Create auth module", "Add login endpoint"],
177
- acceptance_criteria: ["Login works", "Tests pass"],
178
- files_to_change: ["src/auth.ts"],
179
- context_needed: ["existing user model"],
180
- parallel_group: "core",
181
- },
182
- ],
183
- };
184
- const result = validateFromFile(planSchemaPath, data, "test");
185
- assert.ok(result);
186
- });
187
-
188
- it("rejects missing required plan fields", () => {
189
- const data = { plans: [{ name: "test" }] };
190
- assert.throws(
191
- () => validateFromFile(planSchemaPath, data, "test"),
192
- (err: any) => err.message.includes("Validation failed"),
193
- );
194
- });
195
- });
196
-
197
- describe("execution-results schema validation", () => {
198
- const execSchemaPath = path.resolve(import.meta.dirname, "..", "schemas", "execution-results.schema.json");
199
-
200
- it("validates a valid execution result", () => {
201
- const data = {
202
- status: "complete",
203
- tasks: [
204
- { name: "implement feature", status: "done", files_modified: ["src/index.ts"], commit_hash: "abc123", notes: "Done" },
205
- ],
206
- decisions: [
207
- { id: "D-001", decision: "Use ESM", rationale: "Modern standard", status: "decided" },
208
- ],
209
- issues: [
210
- { severity: "info", description: "Minor lint warning" },
211
- ],
212
- test_count: 42,
213
- commit_hash: "abc123",
214
- };
215
- const result = validateFromFile(execSchemaPath, data, "test");
216
- assert.ok(result);
217
- });
218
-
219
- it("rejects invalid status enum", () => {
220
- const data = { status: "unknown", tasks: [] };
221
- assert.throws(
222
- () => validateFromFile(execSchemaPath, data, "test"),
223
- (err: any) => err.message.includes("Validation failed"),
224
- );
225
- });
226
- });