Plan EE1.
apps/daemon/src/plugins/simulate.ts ships two pure helpers:
simulatePipeline({ pipeline, signals, iterationCap? })
\u2192 SimulatePipelineResult { stages[], totalIterations, outcome }
parseSignalKv(['critique.score=4', 'build.passing=true', ...])
\u2192 { signals, warnings[] } honouring the closed UntilSignals
vocabulary (critique.score / iterations / user.confirmed /
preview.ok / build.passing / tests.passing).
The simulator walks every stage in pipeline.stages:
- Single-shot stages (repeat=false) run exactly once \u2192
outcome='single'.
- Repeat:true stages without an until expression also collapse
to outcome='single' (single iteration).
- Repeat:true stages with an until expression iterate until the
expression satisfies (\u2192 'converged'), the iterationCap is
hit (\u2192 'cap'), or the expression fails to parse (\u2192
'unparsable'). Cap defaults to 10.
Caller-supplied signals can be a constant snapshot OR a generator
function (stageId, iteration) \u2192 signals so authors can model
'score grows over iterations' / 'build.passing flips on iter 3'
scenarios deterministically.
Aggregate outcome on the result rolls up to one of:
'all-converged' | 'all-single' | 'mixed' | 'cap-hit' | 'unparsable'.
CLI: `od plugin simulate <pluginId> [-s key=value ...] [--cap <n>] [--json]`.
- Repeatable -s flags so a shell can pin every signal in one
call. Unknown signal keys surface as warnings (typo guard).
- Exit 4 on cap-hit / unparsable so CI can wire into a pipeline
check easily.
- --json emits the full SimulatePipelineResult.
printPluginHelp() updated.
Daemon tests: 1753 \u2192 1766 (+13 cases on plugins-simulate:
single-shot stages, repeat=true converged, repeat=true cap-hit,
unparsable until, signal generator function (per-iteration drift),
boolean compound until (build.passing == true && tests.passing
== true), aggregate 'mixed' outcome, totalIterations sum,
parseSignalKv vocabulary + warnings on unknown / malformed /
type-mismatch keys).
Co-authored-by: Tom Huang <1043269994@qq.com>