-
Notifications
You must be signed in to change notification settings - Fork 0
Pipeline Plan 305
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:
-
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 -
scripts/sw-ruflo-adapter-test.sh(new) — 11 mock-based unit tests covering detection, init, cleanup, circuit-breaker, export visibility, and pipefail safety -
scripts/sw-pipeline.sh(3 edits) — Source line after GitHub API modules (~line 135),ruflo_initinrun_pipeline()(~line 1562),ruflo_cleanupincleanup_on_exit()(~line 675)
Key design decisions:
- Fast path detection (
command -v ruflo) before slow npx fallback - Circuit-breaker pattern in
ruflo_with_timeoutdisables ruflo for remainder of pipeline on timeout - All integration points use
type ... >/dev/null 2>&1guards +|| true— zero breakage when ruflo is absent -
RUFLO_AVAILABLEis 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
┌─────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────┘
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
| 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 |
# 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 | 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 |
Ruflo adapter module — detection, MCP lifecycle, circuit-breaker.
Unit tests using mock binaries — no real ruflo needed.
- Add source line after existing lib modules (~line 135)
- Add
ruflo_initcall insiderun_pipeline()(~line 1562) - Add
ruflo_cleanupcall insidecleanup_on_exit()(~line 675)
The module follows Shipwright's standard lib pattern:
-
_RUFLO_ADAPTER_LOADEDguard sentinel - Fallback helper definitions (warn, info, emit_event)
-
RUFLO_AVAILABLE=falsedefault - 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 viacommand -v ruflo, fallback vianpx -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|| trueguards -
ruflo_with_timeout(): Uses_timeoutif available (from helpers.sh), falls back totimeoutcommand - All stderr suppressed with
&>/dev/nullor2>/dev/null
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 || trueAdd 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
fiAdd 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
fiCreate scripts/sw-ruflo-adapter-test.sh with these test cases:
- Module guard prevents double-sourcing
-
ruflo_detectwith mock ruflo binary -> RUFLO_AVAILABLE=true -
ruflo_detectwith no ruflo binary -> RUFLO_AVAILABLE=false -
ruflo_initwith mock ruflo -> exports RUFLO_AVAILABLE -
ruflo_initwithout ruflo -> no-op -
ruflo_cleanupkills MCP PID -
ruflo_cleanupno-op when unavailable -
ruflo_with_timeoutcircuit-breaks -
ruflo_availableexit codes -
RUFLO_AVAILABLEvisible in subshell after export - Safe under
set -euo pipefail
- Make test executable
- Run
scripts/sw-ruflo-adapter-test.sh - Run
npm testfor regression check
- Task 1: Create
scripts/lib/ruflo-adapter.shwith module guard, detection, init, cleanup, timeout, and available functions - Task 2: Add source line in
sw-pipeline.shafter GitHub API modules section (~line 135) - Task 3: Add
ruflo_initcall inrun_pipeline()function (~line 1562) - Task 4: Add
ruflo_cleanupcall incleanup_on_exit()function (~line 675) - Task 5: Create
scripts/sw-ruflo-adapter-test.shwith 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)
-
Unit tests (11 tests): Mock-based tests in
sw-ruflo-adapter-test.shcovering all exported functions, edge cases, andset -euo pipefailsafety -
Integration tests (1 test): Verify
sw-pipeline.shsources 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
- 100% of exported functions tested
- Both available and unavailable code paths exercised
- Circuit-breaker (timeout) path tested
- 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
-
source scripts/lib/ruflo-adapter.sh && ruflo_init && echo $RUFLO_AVAILABLEcorrectly detects installation - MCP server starts and stops cleanly with pipeline lifecycle
-
ruflo_with_timeoutcircuit breaker setsRUFLO_AVAILABLE=falseon timeout -
RUFLO_AVAILABLEis exported and visible in subshells - All new functions use
|| trueguards -- compatible withset -euo pipefail - Full pipeline run is identical when ruflo is not installed
- Follows Shipwright's module guard pattern (
_RUFLO_ADAPTER_LOADEDsentinel) - All unit tests pass
-
npm testpasses with no regressions