Skip to content

Pipeline Plan 305

ezigus edited this page Apr 4, 2026 · 1 revision

Plan is written to .claude/pipeline-artifacts/plan.md.

Summary of the plan:

3 files touched — 1 new module, 1 new test file, 1 modified file:

  1. scripts/lib/ruflo-adapter.sh (new) — Module with guard pattern (_RUFLO_ADAPTER_LOADED), 7 functions: ruflo_detect, ruflo_init, ruflo_cleanup, ruflo_with_timeout, ruflo_available, plus 2 stubs (ruflo_import_memory, ruflo_export_memory) for Issue 2

  2. scripts/sw-ruflo-adapter-test.sh (new) — 11 mock-based unit tests covering detection, init, cleanup, circuit-breaker, export visibility, and pipefail safety

  3. scripts/sw-pipeline.sh (3 edits) — Source line after GitHub API modules (~line 135), ruflo_init in run_pipeline() (~line 1562), ruflo_cleanup in cleanup_on_exit() (~line 675)

Key design decisions:

  • Fast path detection (command -v ruflo) before slow npx fallback
  • Circuit-breaker pattern in ruflo_with_timeout disables ruflo for remainder of pipeline on timeout
  • All integration points use type ... >/dev/null 2>&1 guards + || true — zero breakage when ruflo is absent
  • RUFLO_AVAILABLE is exported for subshell visibility (needed by sw loop iterations) debase conventions

Alternative C: Node.js wrapper for ruflo integration

  • Pros: Better npm/npx integration
  • Cons: Breaks shell-first convention, adds runtime dependency, inconsistent with all other lib/ modules
  • Rejected: Wrong paradigm for this codebase

Component Diagram

┌─────────────────────────────────────────────────┐
│                 sw-pipeline.sh                   │
│  (sources lib modules, runs pipeline stages)     │
│                                                  │
│  ┌─ source (optional) ──────────────────────┐   │
│  │                                           │   │
│  │  scripts/lib/ruflo-adapter.sh            │   │
│  │  ┌────────────────────────────────────┐  │   │
│  │  │ ruflo_detect()   → sets RUFLO_AVAILABLE│ │
│  │  │ ruflo_init()     → detect + start MCP  │ │
│  │  │ ruflo_cleanup()  → export mem + stop   │ │
│  │  │ ruflo_with_timeout() → circuit breaker │ │
│  │  │ ruflo_available() → boolean check      │ │
│  │  └────────────────────────────────────┘  │   │
│  └───────────────────────────────────────────┘   │
│                                                  │
│  run_pipeline() ─── ruflo_init() at start        │
│  cleanup_on_exit() ─── ruflo_cleanup() at end    │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│         scripts/sw-ruflo-adapter-test.sh         │
│  (mock-based unit tests, no real ruflo needed)   │
└─────────────────────────────────────────────────┘

Data Flow

Pipeline start
  → source ruflo-adapter.sh (sets RUFLO_AVAILABLE=false)
  → run_pipeline()
    → ruflo_init()
      → ruflo_detect()
        → command -v ruflo (fast path)
        → OR npx -y ruflo@latest mcp status (fallback)
        → sets RUFLO_AVAILABLE=true/false
      → if available: start MCP server (background)
      → ruflo_import_memory() [stub for Issue 2]
      → export RUFLO_AVAILABLE
  → [stages run, can check ruflo_available()]
  → cleanup_on_exit()
    → ruflo_cleanup()
      → ruflo_export_memory() [stub for Issue 2]
      → kill MCP server PID

Error Boundaries

Component Error Handling Propagation
ruflo_detect() All calls use &>/dev/null, returns 1 on failure Caller gets RUFLO_AVAILABLE=false, pipeline continues
ruflo_init() Calls `ruflo_detect
ruflo_cleanup() Early-exit guard if not available, `kill ...
ruflo_with_timeout() timeout wrapper, sets RUFLO_AVAILABLE=false on failure Circuit-breaks: disables ruflo for remainder
Source line in sw-pipeline.sh `-f ... && source ... 2>/dev/null

Interface Contracts

# Detection — returns 0 if ruflo available, 1 if not. Sets RUFLO_AVAILABLE.
ruflo_detect() -> exit_code: 0|1, side_effect: RUFLO_AVAILABLE=true|false

# Initialization — call once at pipeline start. No-op if ruflo unavailable.
ruflo_init() -> exit_code: always 0, side_effects: RUFLO_AVAILABLE exported, RUFLO_MCP_PID set

# Cleanup — call in exit trap. No-op if ruflo wasn't active.
ruflo_cleanup() -> exit_code: always 0, side_effects: MCP process killed

# Timeout wrapper — runs command with timeout, circuit-breaks on failure.
# $1: timeout in seconds (default 30), $@: command to run
ruflo_with_timeout(timeout_s, ...cmd) -> exit_code: 0|1, side_effect: RUFLO_AVAILABLE=false on timeout

# Boolean check — use in conditionals
ruflo_available() -> exit_code: 0 (true) | 1 (false)

# Stubs for Issue 2 (memory bridge)
ruflo_import_memory() -> exit_code: always 0 (no-op stub)
ruflo_export_memory() -> exit_code: always 0 (no-op stub)

Risk Analysis

Risk Impact Mitigation
npx detection is slow (~5-10s) Slows pipeline startup Fast path: check command -v ruflo first; npx only as fallback
MCP server fails to start Could block pipeline Background start with &, sleep 2 for readiness, all subsequent calls use ruflo_available() guard
Stale MCP PID after crash Orphaned process PID-based cleanup in exit trap; `kill ... 2>/dev/null
timeout not available on macOS ruflo_with_timeout fails Use Shipwright's existing _timeout helper from helpers.sh if available, fall back to direct timeout command
set -euo pipefail incompatibility Module source could crash pipeline All functions use `
Breaking existing pipeline behavior Regression for all users Zero functional change when ruflo absent — adapter only sets variables and defines functions

Files to Create

1. scripts/lib/ruflo-adapter.sh (new)

Ruflo adapter module — detection, MCP lifecycle, circuit-breaker.

2. scripts/sw-ruflo-adapter-test.sh (new)

Unit tests using mock binaries — no real ruflo needed.

Files to Modify

3. scripts/sw-pipeline.sh

  • Add source line after existing lib modules (~line 135)
  • Add ruflo_init call inside run_pipeline() (~line 1562)
  • Add ruflo_cleanup call inside cleanup_on_exit() (~line 675)

Implementation Steps

Step 1: Create scripts/lib/ruflo-adapter.sh

The module follows Shipwright's standard lib pattern:

  • _RUFLO_ADAPTER_LOADED guard sentinel
  • Fallback helper definitions (warn, info, emit_event)
  • RUFLO_AVAILABLE=false default
  • Core functions: ruflo_detect, ruflo_init, ruflo_cleanup, ruflo_with_timeout, ruflo_available
  • Stub functions: ruflo_import_memory, ruflo_export_memory (for Issue 2)

Key implementation details:

  • ruflo_detect(): Fast path via command -v ruflo, fallback via npx -y ruflo@latest mcp status
  • ruflo_init(): Calls detect, starts MCP server in background, exports RUFLO_AVAILABLE
  • ruflo_cleanup(): Exports memory (stub), kills MCP PID, safe with || true guards
  • ruflo_with_timeout(): Uses _timeout if available (from helpers.sh), falls back to timeout command
  • All stderr suppressed with &>/dev/null or 2>/dev/null

Step 2: Source adapter in sw-pipeline.sh

Add after line 134 (after GitHub API modules section):

# --- Ruflo Adapter (optional) ---
# shellcheck source=lib/ruflo-adapter.sh
[[ -f "$SCRIPT_DIR/lib/ruflo-adapter.sh" ]] && source "$SCRIPT_DIR/lib/ruflo-adapter.sh" 2>/dev/null || true

Step 3: Add ruflo_init to run_pipeline()

Add after audit_init (~line 1562) using the existing type-check pattern:

    # Initialize ruflo adapter (no-op if unavailable)
    if type ruflo_init >/dev/null 2>&1; then
        ruflo_init || true
    fi

Step 4: Add ruflo_cleanup to cleanup_on_exit()

Add early in cleanup_on_exit() after the guard (~line 675), before heartbeat stop:

    # Cleanup ruflo MCP server
    if type ruflo_cleanup >/dev/null 2>&1; then
        ruflo_cleanup || true
    fi

Step 5: Create test file

Create scripts/sw-ruflo-adapter-test.sh with these test cases:

  1. Module guard prevents double-sourcing
  2. ruflo_detect with mock ruflo binary -> RUFLO_AVAILABLE=true
  3. ruflo_detect with no ruflo binary -> RUFLO_AVAILABLE=false
  4. ruflo_init with mock ruflo -> exports RUFLO_AVAILABLE
  5. ruflo_init without ruflo -> no-op
  6. ruflo_cleanup kills MCP PID
  7. ruflo_cleanup no-op when unavailable
  8. ruflo_with_timeout circuit-breaks
  9. ruflo_available exit codes
  10. RUFLO_AVAILABLE visible in subshell after export
  11. Safe under set -euo pipefail

Step 6: Verify and run tests

  • Make test executable
  • Run scripts/sw-ruflo-adapter-test.sh
  • Run npm test for regression check

Task Checklist

  • Task 1: Create scripts/lib/ruflo-adapter.sh with module guard, detection, init, cleanup, timeout, and available functions
  • Task 2: Add source line in sw-pipeline.sh after GitHub API modules section (~line 135)
  • Task 3: Add ruflo_init call in run_pipeline() function (~line 1562)
  • Task 4: Add ruflo_cleanup call in cleanup_on_exit() function (~line 675)
  • Task 5: Create scripts/sw-ruflo-adapter-test.sh with mock-based unit tests
  • Task 6: Make test file executable
  • Task 7: Verify test registration in npm test / vitest config
  • Task 8: Run sw-ruflo-adapter-test.sh -- all tests pass
  • Task 9: Run full npm test -- no regressions
  • Task 10: Verify pipeline runs identically without ruflo installed (RUFLO_AVAILABLE=false path)

Testing Approach

Test Pyramid Breakdown

  • Unit tests (11 tests): Mock-based tests in sw-ruflo-adapter-test.sh covering all exported functions, edge cases, and set -euo pipefail safety
  • Integration tests (1 test): Verify sw-pipeline.sh sources the adapter without error and pipeline runs cleanly with adapter present but ruflo absent
  • E2E (0): Not needed -- this is a no-op adapter when ruflo is absent, and ruflo won't be installed in CI

Coverage Targets

  • 100% of exported functions tested
  • Both available and unavailable code paths exercised
  • Circuit-breaker (timeout) path tested

Critical Paths to Test

  • Happy path: Mock ruflo binary present -> detect succeeds -> init starts MCP -> cleanup kills it
  • Error case 1: No ruflo binary -> detect fails -> init is no-op -> pipeline unaffected
  • Error case 2: ruflo_with_timeout exceeds limit -> RUFLO_AVAILABLE set to false -> circuit broken
  • Edge case 1: Double-source guard prevents re-initialization
  • Edge case 2: cleanup called when RUFLO_MCP_PID is empty -> no error
  • Edge case 3: RUFLO_AVAILABLE exported and visible in subshell

Definition of Done

  • source scripts/lib/ruflo-adapter.sh && ruflo_init && echo $RUFLO_AVAILABLE correctly detects installation
  • MCP server starts and stops cleanly with pipeline lifecycle
  • ruflo_with_timeout circuit breaker sets RUFLO_AVAILABLE=false on timeout
  • RUFLO_AVAILABLE is exported and visible in subshells
  • All new functions use || true guards -- compatible with set -euo pipefail
  • Full pipeline run is identical when ruflo is not installed
  • Follows Shipwright's module guard pattern (_RUFLO_ADAPTER_LOADED sentinel)
  • All unit tests pass
  • npm test passes with no regressions

Clone this wiki locally