@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.
- package/CHANGELOG.md +7 -4
- package/README.md +12 -2
- package/agents/architecture-designer.agent.yaml +58 -0
- package/agents/architecture-inferrer.agent.yaml +60 -0
- package/agents/gap-identifier.agent.yaml +66 -0
- package/agents/handoff-writer.agent.yaml +64 -0
- package/agents/plan-creator.agent.yaml +63 -0
- package/agents/project-definer.agent.yaml +53 -0
- package/agents/project-inferrer.agent.yaml +63 -0
- package/agents/requirements-gatherer.agent.yaml +58 -0
- package/dist/agent-spec.d.ts +27 -0
- package/dist/agent-spec.d.ts.map +1 -0
- package/dist/agent-spec.js +112 -0
- package/dist/agent-spec.js.map +1 -0
- package/dist/checkpoint.d.ts +50 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +109 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/completion.d.ts +18 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +58 -0
- package/dist/completion.js.map +1 -0
- package/dist/dag.d.ts +56 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +178 -0
- package/dist/dag.js.map +1 -0
- package/dist/dispatch.d.ts +35 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/expression.d.ts +64 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +294 -0
- package/dist/expression.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +42 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +59 -0
- package/dist/output.js.map +1 -0
- package/dist/state.d.ts +66 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +227 -0
- package/dist/state.js.map +1 -0
- package/dist/step-agent.d.ts +35 -0
- package/dist/step-agent.d.ts.map +1 -0
- package/dist/step-agent.js +171 -0
- package/dist/step-agent.js.map +1 -0
- package/dist/step-command.d.ts +21 -0
- package/dist/step-command.d.ts.map +1 -0
- package/dist/step-command.js +150 -0
- package/dist/step-command.js.map +1 -0
- package/dist/step-foreach.d.ts +33 -0
- package/dist/step-foreach.d.ts.map +1 -0
- package/dist/step-foreach.js +112 -0
- package/dist/step-foreach.js.map +1 -0
- package/dist/step-gate.d.ts +18 -0
- package/dist/step-gate.d.ts.map +1 -0
- package/dist/step-gate.js +137 -0
- package/dist/step-gate.js.map +1 -0
- package/dist/step-loop.d.ts +32 -0
- package/dist/step-loop.d.ts.map +1 -0
- package/dist/step-loop.js +278 -0
- package/dist/step-loop.js.map +1 -0
- package/dist/step-monitor.d.ts +56 -0
- package/dist/step-monitor.d.ts.map +1 -0
- package/dist/step-monitor.js +317 -0
- package/dist/step-monitor.js.map +1 -0
- package/dist/step-parallel.d.ts +43 -0
- package/dist/step-parallel.d.ts.map +1 -0
- package/dist/step-parallel.js +122 -0
- package/dist/step-parallel.js.map +1 -0
- package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
- package/dist/step-pause.d.ts.map +1 -0
- package/dist/step-pause.js +22 -0
- package/dist/step-pause.js.map +1 -0
- package/dist/step-shared.d.ts +63 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +136 -0
- package/dist/step-shared.js.map +1 -0
- package/dist/step-transform.d.ts +11 -0
- package/dist/step-transform.d.ts.map +1 -0
- package/dist/step-transform.js +41 -0
- package/dist/step-transform.js.map +1 -0
- package/dist/template.d.ts +51 -0
- package/dist/template.d.ts.map +1 -0
- package/{src/template.ts → dist/template.js} +32 -38
- package/dist/template.js.map +1 -0
- package/dist/test-helpers.d.ts +17 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +46 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/tui.d.ts +47 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +143 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-discovery.d.ts +23 -0
- package/dist/workflow-discovery.d.ts.map +1 -0
- package/dist/workflow-discovery.js +103 -0
- package/dist/workflow-discovery.js.map +1 -0
- package/dist/workflow-executor.d.ts +50 -0
- package/dist/workflow-executor.d.ts.map +1 -0
- package/dist/workflow-executor.js +738 -0
- package/dist/workflow-executor.js.map +1 -0
- package/dist/workflow-sdk.d.ts +44 -0
- package/dist/workflow-sdk.d.ts.map +1 -0
- package/dist/workflow-sdk.js +457 -0
- package/dist/workflow-sdk.js.map +1 -0
- package/dist/workflow-spec.d.ts +33 -0
- package/dist/workflow-spec.d.ts.map +1 -0
- package/dist/workflow-spec.js +476 -0
- package/dist/workflow-spec.js.map +1 -0
- package/dist/workflows-dir.d.ts +3 -0
- package/dist/workflows-dir.d.ts.map +1 -0
- package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
- package/dist/workflows-dir.js.map +1 -0
- package/package.json +53 -38
- package/schemas/decomposition-specs.schema.json +48 -48
- package/schemas/execution-results.schema.json +48 -48
- package/schemas/investigation-findings.schema.json +43 -43
- package/schemas/pattern-analysis.schema.json +41 -41
- package/schemas/phase.schema.json +48 -0
- package/schemas/plan-breakdown.schema.json +20 -20
- package/schemas/quality-analysis.schema.json +33 -33
- package/schemas/research-findings.schema.json +42 -42
- package/schemas/structure-analysis.schema.json +40 -40
- package/schemas/synthesis.schema.json +31 -31
- package/schemas/verifier-output.schema.json +94 -94
- package/skill-narrative.md +107 -0
- package/skills/pi-workflows/SKILL.md +185 -0
- package/skills/pi-workflows/references/bundled-resources.md +87 -0
- package/templates/architecture-designer/task.md +117 -0
- package/templates/architecture-inferrer/task.md +61 -0
- package/templates/gap-identifier/task.md +103 -0
- package/templates/handoff-writer/task.md +91 -0
- package/templates/plan-creator/task.md +143 -0
- package/templates/project-definer/task.md +67 -0
- package/templates/project-inferrer/task.md +56 -0
- package/templates/requirements-gatherer/task.md +90 -0
- package/workflows/analyze-existing-project.workflow.yaml +94 -0
- package/workflows/create-handoff.workflow.yaml +66 -0
- package/workflows/create-phase.workflow.yaml +1 -1
- package/workflows/do-gap.workflow.yaml +4 -4
- package/workflows/fix-audit.workflow.yaml +1 -1
- package/workflows/gap-to-phase.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +81 -0
- package/workflows/plan-from-requirements.workflow.yaml +59 -0
- package/workflows/self-implement.workflow.yaml +3 -3
- package/workflows/typed-analysis.workflow.yaml +4 -4
- package/src/agent-spec.test.ts +0 -289
- package/src/agent-spec.ts +0 -122
- package/src/block-validation.test.ts +0 -350
- package/src/checkpoint.test.ts +0 -237
- package/src/checkpoint.ts +0 -140
- package/src/completion.test.ts +0 -143
- package/src/completion.ts +0 -68
- package/src/dag.test.ts +0 -350
- package/src/dag.ts +0 -219
- package/src/dispatch.test.ts +0 -475
- package/src/dispatch.ts +0 -353
- package/src/expression.test.ts +0 -353
- package/src/expression.ts +0 -332
- package/src/format.test.ts +0 -80
- package/src/format.ts +0 -41
- package/src/graduated-failure.test.ts +0 -556
- package/src/index.test.ts +0 -114
- package/src/index.ts +0 -516
- package/src/loop.test.ts +0 -551
- package/src/output.test.ts +0 -213
- package/src/output.ts +0 -70
- package/src/parallel-integration.test.ts +0 -175
- package/src/resume.test.ts +0 -192
- package/src/state.test.ts +0 -192
- package/src/state.ts +0 -254
- package/src/step-agent.test.ts +0 -327
- package/src/step-agent.ts +0 -178
- package/src/step-command.test.ts +0 -330
- package/src/step-command.ts +0 -132
- package/src/step-foreach.test.ts +0 -647
- package/src/step-foreach.ts +0 -148
- package/src/step-gate.test.ts +0 -185
- package/src/step-gate.ts +0 -116
- package/src/step-loop.test.ts +0 -626
- package/src/step-loop.ts +0 -323
- package/src/step-parallel.test.ts +0 -475
- package/src/step-parallel.ts +0 -168
- package/src/step-pause.test.ts +0 -30
- package/src/step-shared.test.ts +0 -355
- package/src/step-shared.ts +0 -157
- package/src/step-transform.test.ts +0 -155
- package/src/step-transform.ts +0 -47
- package/src/template-integration.test.ts +0 -72
- package/src/template.test.ts +0 -162
- package/src/test-helpers.ts +0 -51
- package/src/tui.test.ts +0 -355
- package/src/tui.ts +0 -182
- package/src/types.ts +0 -195
- package/src/verifier-schema.test.ts +0 -226
- package/src/workflow-discovery.test.ts +0 -105
- package/src/workflow-discovery.ts +0 -114
- package/src/workflow-executor.test.ts +0 -1532
- package/src/workflow-executor.ts +0 -810
- package/src/workflow-sdk.test.ts +0 -238
- package/src/workflow-sdk.ts +0 -262
- package/src/workflow-spec.test.ts +0 -576
- 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
|
-
});
|