Skip to content

Add generic YAML workflow engine - #14

Merged
5uck1ess merged 11 commits into
mainfrom
feat/yaml-workflow-engine
Apr 4, 2026
Merged

Add generic YAML workflow engine#14
5uck1ess merged 11 commits into
mainfrom
feat/yaml-workflow-engine

Conversation

@5uck1ess

@5uck1ess 5uck1ess commented Apr 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • YAML workflow engine (src/engine/) — deterministic execution of workflow YAML files, replacing the need for hardcoded Go implementations. Zero tokens spent on orchestration: branch evaluation, loop counting, variable interpolation, and budget tracking all happen in Go.
  • Triage-based phase skippingfeature.yml and bugfix.yml now classify task scope (TINY/SMALL/MEDIUM/LARGE) and skip unnecessary steps. A typo fix no longer runs a 14-step pipeline.
  • Scratchpads — iteration memory protocol (.devkit/scratchpads/current.md) that prevents Groundhog Day loops by recording what was tried and what failed.
  • Dirty-bit feedback loop — Stop hook that detects cross-domain changes (backend + frontend) and blocks completion if any touched domain lacks test evidence.

New files

  • src/engine/workflow.go — YAML types, parser, validator, interpolation, branch eval
  • src/engine/engine.goEngine.RunWorkflow() with loops, branches, parallel, budget
  • src/engine/engine_test.go — 18 tests
  • src/cmd/workflow.godevkit workflow <name> <description> CLI command
  • skills/scratchpad/SKILL.md — scratchpad protocol
  • hooks/dirty-bit.sh — cross-domain verification hook

Test plan

  • 18 engine unit tests pass (parsing, interpolation, branching, loops, budget, parallel, context cancellation)
  • All 12 real workflow YAML files parse successfully
  • Full test suite passes (go test ./...)
  • Manual: devkit workflow feature "add X" runs end-to-end
  • Manual: devkit workflow list shows all workflows
  • Manual: triage routes TINY tasks to quick-fix path

5uck1ess added 11 commits April 4, 2026 17:52
…bit hook

Replaces the need for hardcoded Go workflow implementations with a single
engine that reads and executes workflow YAML files deterministically —
zero tokens spent on orchestration, exact loop counting, and reliable
branch evaluation.

Engine (src/engine/):
- YAML parser with validation (types, duplicate IDs, branch targets)
- Sequential step execution with {{variable}} interpolation
- Branch evaluation (case-insensitive substring match → goto)
- Loop management (hard counter + until-string match)
- Parallel step dispatch via goroutines
- Budget enforcement, scratchpad setup/cleanup, session tracking

CLI: `devkit workflow <name> <description>` and `devkit workflow list`

Triage-based phase skipping:
- feature.yml: TINY/SMALL/MEDIUM/LARGE classification with fast paths
- bugfix.yml: TRIVIAL/NORMAL/COMPLEX classification with fast path

Scratchpads (skills/scratchpad/):
- Iteration memory protocol at .devkit/scratchpads/current.md
- Prevents Groundhog Day loops by recording what was tried
- Integrated into stuck skill recovery protocol

Dirty-bit feedback loop (hooks/dirty-bit.sh):
- Stop hook that detects cross-domain changes (backend/frontend/config/sql)
- Blocks completion if any touched domain lacks test evidence

18 engine tests covering parsing, interpolation, branching, loops,
budget, parallel dispatch, context cancellation, and all 12 real workflows.
…tion

Fixes from tri-review (Claude + Codex + Gemini consensus):

1. Fix nil deref on runner error in runLoop — no longer accesses
   result.CostUSD when err != nil
2. Fix data race on outputs map in runParallel — snapshot outputs
   before launching goroutines
3. Fix session status overwrite — track failed state, only mark
   "done" on clean exit
4. Fix silent loop failure — runLoop returns error when all
   iterations fail
5. Fix parallel error swallowing — runParallel returns error when
   ALL parallel steps fail
6. Add branch cycle detection — max 100 jumps before stopping

Additional fixes:
- Skip parallel-dispatched steps during sequential walk (no double execution)
- Evaluate branch conditions after loop steps (not just regular steps)
- Wire YAML budget config to RunConfig when CLI flag not set
- Propagate step errors from RunWorkflow return value

2 new tests: TestRunWorkflowLoopAllFail, TestRunWorkflowBranchCycleLimit
Validates workflow name against ^[a-zA-Z0-9_-]+$ before constructing
file paths, preventing directory traversal via crafted names like
"../../etc/passwd". Found by Gemini in tri-review.
New hook (go-review.sh):
- Detects error-path result access (accessing fields when err != nil)
- Flags goroutines with shared map access missing mutex/snapshot
- Warns on filepath.Join with unsanitized input variables
Registered as PostToolUse for *.go Edit/Write operations.

security-patterns.sh:
- Add filepath traversal pattern for Go (filepath.Join with user input)

subagent-stop.sh:
- Recognize go vet / go test -race as valid test evidence

These patterns are the top recurring LLM-generated bug categories
identified from the tri-review of the workflow engine PR.
From PR review toolkit analysis:

silent-failure-hunter:
- Fix missing stepErr on non-loop branch limit path
- Check os.MkdirAll error for scratchpad dir
- Explicit _ = os.Remove for intentional discard

pr-test-analyzer:
- Add budget enforcement inside runLoop (overBudget + addCost callbacks)
- Add TestRunWorkflowStepFailure (non-loop step error propagation)
- Add TestRunWorkflowParallelPartialFailure (some fail, some succeed)
- Add TestRunWorkflowParallelAllFail (all parallel steps fail)
- Add TestRunWorkflowBudgetInLoop (budget respected mid-loop)
- Make mock runner thread-safe with sync.Mutex

24 engine tests now pass (was 20).
- Replace grep -P (Perl regex, unavailable on macOS) with awk in
  go-review.sh for error-path detection pattern
- Remove unsupported "if" field from hooks.json go-review entry
  (hook self-filters via case statement)
- Run go mod tidy to promote yaml.v3 from indirect to direct
…etection

go-vet-stop.sh (Stop hook):
- Runs go vet on modified Go packages before session completes
- Runs go test -race to catch data races (would have caught the
  outputs map race in runParallel)
- 90s timeout, only triggers when Go files changed

shell-compat.sh (PreToolUse hook):
- Flags macOS-incompatible constructs in shell scripts at write-time
- Catches: grep -P, sed -i without '', readlink -f, stat --format,
  xargs -d, date -d, mktemp --suffix
- Would have caught the grep -P issue in go-review.sh

go-nil-return.sh (PostToolUse hook):
- Detects Go functions with error return type that only ever return nil
- Uses awk to parse function boundaries and return statements
- Would have caught runLoop/runParallel always returning nil error

All three hooks learned from bugs found during this PR's review cycle.
Type-design-analyzer recommendations implemented:

1. NewEngine() constructor — validates all 4 fields are non-nil/non-empty,
   fields unexported to prevent post-construction mutation

2. RunConfig validation — rejects negative BudgetUSD at RunWorkflow entry

3. WfStep mutual exclusion — parallel steps cannot have prompt or loop,
   enforced at parse time with clear error messages

4. Budget validation — negative budget.limit rejected at parse time

5. Workflow.Validate() method — allows engine-boundary validation for
   workflows constructed directly (not via Parse)

29 engine tests (was 24): added NewEngine validation, negative budget,
parallel+prompt mutual exclusion, parallel+loop mutual exclusion.
The branch evaluation logic was duplicated verbatim between the loop
and regular step paths. Extracted into a local evalBranch closure that
returns (stepIndex, error), called once after both paths. Removes ~20
lines of duplication while keeping identical behavior.

Found by code-simplifier agent.
go-vet-stop.sh:
- Replace `timeout` (GNU-only) with `perl -e 'alarm 60; exec @argv'`
  for macOS compatibility. Ironic that the shell-compat hook exists
  to catch exactly this.

shell-compat.sh:
- Fix session dedup: use PPID instead of $$ (each hook invocation
  is a new process, so $$ gives a unique PID every time, defeating
  the dedup mechanism).

go-nil-return.sh:
- Remove dead gsub code on function declaration line.
- Properly count all braces on the declaration line (handles inline
  struct literals) instead of hardcoding brace_depth=1.
@5uck1ess
5uck1ess merged commit 7429b7d into main Apr 4, 2026
1 check passed
@5uck1ess
5uck1ess deleted the feat/yaml-workflow-engine branch April 4, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant