jfl 0.2.2 → 0.2.4

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.
Files changed (153) hide show
  1. package/README.md +399 -423
  2. package/clawdbot-plugin/clawdbot.plugin.json +12 -1
  3. package/clawdbot-plugin/index.js +5 -5
  4. package/clawdbot-plugin/index.ts +5 -5
  5. package/dist/commands/context-hub.d.ts +4 -0
  6. package/dist/commands/context-hub.d.ts.map +1 -1
  7. package/dist/commands/context-hub.js +704 -83
  8. package/dist/commands/context-hub.js.map +1 -1
  9. package/dist/commands/digest.d.ts +6 -0
  10. package/dist/commands/digest.d.ts.map +1 -0
  11. package/dist/commands/digest.js +81 -0
  12. package/dist/commands/digest.js.map +1 -0
  13. package/dist/commands/flows.d.ts +7 -0
  14. package/dist/commands/flows.d.ts.map +1 -0
  15. package/dist/commands/flows.js +264 -0
  16. package/dist/commands/flows.js.map +1 -0
  17. package/dist/commands/hooks.d.ts +11 -0
  18. package/dist/commands/hooks.d.ts.map +1 -0
  19. package/dist/commands/hooks.js +303 -0
  20. package/dist/commands/hooks.js.map +1 -0
  21. package/dist/commands/improve.d.ts +11 -0
  22. package/dist/commands/improve.d.ts.map +1 -0
  23. package/dist/commands/improve.js +77 -0
  24. package/dist/commands/improve.js.map +1 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +42 -11
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/peter.d.ts +15 -0
  29. package/dist/commands/peter.d.ts.map +1 -0
  30. package/dist/commands/peter.js +198 -0
  31. package/dist/commands/peter.js.map +1 -0
  32. package/dist/commands/ralph.d.ts +3 -1
  33. package/dist/commands/ralph.d.ts.map +1 -1
  34. package/dist/commands/ralph.js +40 -5
  35. package/dist/commands/ralph.js.map +1 -1
  36. package/dist/commands/scope.d.ts +7 -0
  37. package/dist/commands/scope.d.ts.map +1 -0
  38. package/dist/commands/scope.js +227 -0
  39. package/dist/commands/scope.js.map +1 -0
  40. package/dist/commands/service-validate.js +7 -1
  41. package/dist/commands/service-validate.js.map +1 -1
  42. package/dist/commands/session.d.ts +2 -1
  43. package/dist/commands/session.d.ts.map +1 -1
  44. package/dist/commands/session.js +519 -49
  45. package/dist/commands/session.js.map +1 -1
  46. package/dist/commands/update.d.ts.map +1 -1
  47. package/dist/commands/update.js +25 -6
  48. package/dist/commands/update.js.map +1 -1
  49. package/dist/dashboard/components.d.ts +7 -0
  50. package/dist/dashboard/components.d.ts.map +1 -0
  51. package/dist/dashboard/components.js +163 -0
  52. package/dist/dashboard/components.js.map +1 -0
  53. package/dist/dashboard/index.d.ts +12 -0
  54. package/dist/dashboard/index.d.ts.map +1 -0
  55. package/dist/dashboard/index.js +132 -0
  56. package/dist/dashboard/index.js.map +1 -0
  57. package/dist/dashboard/pages.d.ts +7 -0
  58. package/dist/dashboard/pages.d.ts.map +1 -0
  59. package/dist/dashboard/pages.js +742 -0
  60. package/dist/dashboard/pages.js.map +1 -0
  61. package/dist/dashboard/styles.d.ts +7 -0
  62. package/dist/dashboard/styles.d.ts.map +1 -0
  63. package/dist/dashboard/styles.js +497 -0
  64. package/dist/dashboard/styles.js.map +1 -0
  65. package/dist/index.js +196 -8
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/flow-engine.d.ts +34 -0
  68. package/dist/lib/flow-engine.d.ts.map +1 -0
  69. package/dist/lib/flow-engine.js +321 -0
  70. package/dist/lib/flow-engine.js.map +1 -0
  71. package/dist/lib/hook-transformer.d.ts +11 -0
  72. package/dist/lib/hook-transformer.d.ts.map +1 -0
  73. package/dist/lib/hook-transformer.js +74 -0
  74. package/dist/lib/hook-transformer.js.map +1 -0
  75. package/dist/lib/map-event-bus.d.ts +50 -0
  76. package/dist/lib/map-event-bus.d.ts.map +1 -0
  77. package/dist/lib/map-event-bus.js +366 -0
  78. package/dist/lib/map-event-bus.js.map +1 -0
  79. package/dist/lib/memory-indexer.d.ts.map +1 -1
  80. package/dist/lib/memory-indexer.js +26 -2
  81. package/dist/lib/memory-indexer.js.map +1 -1
  82. package/dist/lib/model-pricing.d.ts +11 -0
  83. package/dist/lib/model-pricing.d.ts.map +1 -0
  84. package/dist/lib/model-pricing.js +27 -0
  85. package/dist/lib/model-pricing.js.map +1 -0
  86. package/dist/lib/peter-parker-bridge.d.ts +34 -0
  87. package/dist/lib/peter-parker-bridge.d.ts.map +1 -0
  88. package/dist/lib/peter-parker-bridge.js +145 -0
  89. package/dist/lib/peter-parker-bridge.js.map +1 -0
  90. package/dist/lib/peter-parker-config.d.ts +13 -0
  91. package/dist/lib/peter-parker-config.d.ts.map +1 -0
  92. package/dist/lib/peter-parker-config.js +86 -0
  93. package/dist/lib/peter-parker-config.js.map +1 -0
  94. package/dist/lib/service-gtm.d.ts +7 -0
  95. package/dist/lib/service-gtm.d.ts.map +1 -1
  96. package/dist/lib/service-gtm.js.map +1 -1
  97. package/dist/lib/service-utils.d.ts.map +1 -1
  98. package/dist/lib/service-utils.js +33 -17
  99. package/dist/lib/service-utils.js.map +1 -1
  100. package/dist/lib/stratus-client.d.ts +1 -0
  101. package/dist/lib/stratus-client.d.ts.map +1 -1
  102. package/dist/lib/stratus-client.js +33 -2
  103. package/dist/lib/stratus-client.js.map +1 -1
  104. package/dist/lib/stratus-rollout-test.d.ts +10 -0
  105. package/dist/lib/stratus-rollout-test.d.ts.map +1 -0
  106. package/dist/lib/stratus-rollout-test.js +412 -0
  107. package/dist/lib/stratus-rollout-test.js.map +1 -0
  108. package/dist/lib/telemetry-digest.d.ts +10 -0
  109. package/dist/lib/telemetry-digest.d.ts.map +1 -0
  110. package/dist/lib/telemetry-digest.js +359 -0
  111. package/dist/lib/telemetry-digest.js.map +1 -0
  112. package/dist/lib/telemetry.d.ts +35 -0
  113. package/dist/lib/telemetry.d.ts.map +1 -0
  114. package/dist/lib/telemetry.js +320 -0
  115. package/dist/lib/telemetry.js.map +1 -0
  116. package/dist/lib/training-tuples.d.ts +33 -0
  117. package/dist/lib/training-tuples.d.ts.map +1 -0
  118. package/dist/lib/training-tuples.js +273 -0
  119. package/dist/lib/training-tuples.js.map +1 -0
  120. package/dist/mcp/context-hub-mcp.js +139 -22
  121. package/dist/mcp/context-hub-mcp.js.map +1 -1
  122. package/dist/types/flows.d.ts +62 -0
  123. package/dist/types/flows.d.ts.map +1 -0
  124. package/dist/types/flows.js +10 -0
  125. package/dist/types/flows.js.map +1 -0
  126. package/dist/types/map.d.ts +42 -0
  127. package/dist/types/map.d.ts.map +1 -0
  128. package/dist/types/map.js +39 -0
  129. package/dist/types/map.js.map +1 -0
  130. package/dist/types/telemetry-digest.d.ts +73 -0
  131. package/dist/types/telemetry-digest.d.ts.map +1 -0
  132. package/dist/types/telemetry-digest.js +5 -0
  133. package/dist/types/telemetry-digest.js.map +1 -0
  134. package/dist/types/telemetry.d.ts +69 -0
  135. package/dist/types/telemetry.d.ts.map +1 -0
  136. package/dist/types/telemetry.js +5 -0
  137. package/dist/types/telemetry.js.map +1 -0
  138. package/dist/ui/event-dashboard.d.ts +12 -0
  139. package/dist/ui/event-dashboard.d.ts.map +1 -0
  140. package/dist/ui/event-dashboard.js +342 -0
  141. package/dist/ui/event-dashboard.js.map +1 -0
  142. package/dist/utils/jfl-paths.d.ts +1 -0
  143. package/dist/utils/jfl-paths.d.ts.map +1 -1
  144. package/dist/utils/jfl-paths.js +1 -0
  145. package/dist/utils/jfl-paths.js.map +1 -1
  146. package/dist/utils/settings-validator.d.ts +3 -2
  147. package/dist/utils/settings-validator.d.ts.map +1 -1
  148. package/dist/utils/settings-validator.js +25 -6
  149. package/dist/utils/settings-validator.js.map +1 -1
  150. package/package.json +3 -2
  151. package/scripts/session/session-end.sh +10 -0
  152. package/scripts/session/session-init.sh +16 -0
  153. package/scripts/test-map-eventbus.sh +357 -0
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env bash
2
+ # MAP Event Bus + Peter Parker — Integration Test
3
+ #
4
+ # Run from the jfl-cli directory:
5
+ # ./scripts/test-map-eventbus.sh
6
+ #
7
+ # What this tests:
8
+ # 1. Context Hub starts with event bus
9
+ # 2. Publish events via REST
10
+ # 3. Retrieve events with pattern filtering
11
+ # 4. SSE real-time streaming
12
+ # 5. WebSocket streaming (if wscat installed)
13
+ # 6. Peter Parker config generation (all 3 profiles)
14
+ # 7. Peter Parker status reads from live bus
15
+ # 8. MCP tool simulation (events_publish, events_recent)
16
+ # 9. Event persistence across restart
17
+ # 10. Pi CLI detection check
18
+
19
+ set -euo pipefail
20
+
21
+ RED='\033[0;31m'
22
+ GREEN='\033[0;32m'
23
+ YELLOW='\033[1;33m'
24
+ CYAN='\033[0;36m'
25
+ GRAY='\033[0;90m'
26
+ NC='\033[0m'
27
+
28
+ PASS=0
29
+ FAIL=0
30
+ SKIP=0
31
+
32
+ pass() { ((PASS++)); echo -e " ${GREEN}✓${NC} $1"; }
33
+ fail() { ((FAIL++)); echo -e " ${RED}✗${NC} $1"; }
34
+ skip() { ((SKIP++)); echo -e " ${YELLOW}→${NC} $1 (skipped)"; }
35
+
36
+ PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
37
+ cd "$PROJECT_ROOT"
38
+
39
+ echo -e "\n${CYAN}═══════════════════════════════════════════════════════${NC}"
40
+ echo -e "${CYAN} MAP Event Bus + Peter Parker — Integration Tests${NC}"
41
+ echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}\n"
42
+
43
+ # ─── 0. Build check ──────────────────────────────────────────────────
44
+ echo -e "${YELLOW}[0] Build${NC}"
45
+ if [ -f dist/index.js ] && [ -f dist/lib/map-event-bus.js ]; then
46
+ pass "dist/ exists with MAP modules"
47
+ else
48
+ echo -e "${RED}Build missing. Run: npm run build${NC}"
49
+ exit 1
50
+ fi
51
+
52
+ # ─── 1. Start Context Hub ────────────────────────────────────────────
53
+ echo -e "\n${YELLOW}[1] Context Hub Startup${NC}"
54
+ node dist/index.js context-hub stop 2>/dev/null || true
55
+ sleep 1
56
+ OUTPUT=$(node dist/index.js context-hub start 2>&1)
57
+ if echo "$OUTPUT" | grep -q "started"; then
58
+ pass "Context Hub started"
59
+ else
60
+ fail "Context Hub failed to start"
61
+ echo "$OUTPUT"
62
+ exit 1
63
+ fi
64
+
65
+ sleep 1
66
+
67
+ TOKEN=$(cat .jfl/context-hub.token 2>/dev/null)
68
+ if [ -z "$TOKEN" ]; then
69
+ fail "No auth token found"
70
+ exit 1
71
+ fi
72
+ pass "Auth token exists"
73
+
74
+ # Find port from health check
75
+ PORT=""
76
+ for p in 4521 4200 4201 4300; do
77
+ if curl -s "http://localhost:$p/health" 2>/dev/null | grep -q "ok"; then
78
+ PORT=$p
79
+ break
80
+ fi
81
+ done
82
+
83
+ if [ -z "$PORT" ]; then
84
+ fail "Cannot find Context Hub port"
85
+ exit 1
86
+ fi
87
+ pass "Context Hub responding on port $PORT"
88
+
89
+ BASE="http://localhost:$PORT"
90
+ AUTH="Authorization: Bearer $TOKEN"
91
+
92
+ # ─── 2. Publish Events ───────────────────────────────────────────────
93
+ echo -e "\n${YELLOW}[2] Publish Events${NC}"
94
+
95
+ # Publish various event types
96
+ RESP=$(curl -s -X POST "$BASE/api/events" \
97
+ -H "$AUTH" -H "Content-Type: application/json" \
98
+ -d '{"type":"session:started","source":"test-runner","data":{"branch":"test"}}')
99
+ if echo "$RESP" | grep -q '"id"'; then
100
+ pass "session:started event published"
101
+ else
102
+ fail "Failed to publish session:started"
103
+ fi
104
+
105
+ RESP=$(curl -s -X POST "$BASE/api/events" \
106
+ -H "$AUTH" -H "Content-Type: application/json" \
107
+ -d '{"type":"task:completed","source":"test-runner","data":{"task":"build","duration":12}}')
108
+ if echo "$RESP" | grep -q '"id"'; then
109
+ pass "task:completed event published"
110
+ else
111
+ fail "Failed to publish task:completed"
112
+ fi
113
+
114
+ RESP=$(curl -s -X POST "$BASE/api/events" \
115
+ -H "$AUTH" -H "Content-Type: application/json" \
116
+ -d '{"type":"peter:started","source":"peter-parker","data":{"profile":"balanced"}}')
117
+ if echo "$RESP" | grep -q '"id"'; then
118
+ pass "peter:started event published"
119
+ else
120
+ fail "Failed to publish peter:started"
121
+ fi
122
+
123
+ RESP=$(curl -s -X POST "$BASE/api/events" \
124
+ -H "$AUTH" -H "Content-Type: application/json" \
125
+ -d '{"type":"decision:made","source":"claude-code","data":{"decision":"use-postgres","reason":"cost"}}')
126
+ if echo "$RESP" | grep -q '"id"'; then
127
+ pass "decision:made event published"
128
+ else
129
+ fail "Failed to publish decision:made"
130
+ fi
131
+
132
+ # Test validation
133
+ RESP=$(curl -s -X POST "$BASE/api/events" \
134
+ -H "$AUTH" -H "Content-Type: application/json" \
135
+ -d '{"data":{"bad":"no type or source"}}')
136
+ if echo "$RESP" | grep -q '"error"'; then
137
+ pass "Rejects event without type/source"
138
+ else
139
+ fail "Should reject event without type/source"
140
+ fi
141
+
142
+ # Test no auth
143
+ RESP=$(curl -s -X POST "$BASE/api/events" \
144
+ -H "Content-Type: application/json" \
145
+ -d '{"type":"custom","source":"x","data":{}}')
146
+ if echo "$RESP" | grep -q "Unauthorized"; then
147
+ pass "Rejects unauthenticated request"
148
+ else
149
+ fail "Should reject unauthenticated request"
150
+ fi
151
+
152
+ # ─── 3. Retrieve Events ──────────────────────────────────────────────
153
+ echo -e "\n${YELLOW}[3] Retrieve & Filter Events${NC}"
154
+
155
+ RESP=$(curl -s "$BASE/api/events?limit=10" -H "$AUTH")
156
+ COUNT=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo 0)
157
+ if [ "$COUNT" -ge 4 ]; then
158
+ pass "Retrieved $COUNT events (expected >= 4)"
159
+ else
160
+ fail "Expected >= 4 events, got $COUNT"
161
+ fi
162
+
163
+ # Pattern filter
164
+ RESP=$(curl -s "$BASE/api/events?pattern=peter:*&limit=10" -H "$AUTH")
165
+ COUNT=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo 0)
166
+ if [ "$COUNT" -ge 1 ]; then
167
+ pass "Pattern filter peter:* returned $COUNT event(s)"
168
+ else
169
+ fail "Pattern filter peter:* should return >= 1"
170
+ fi
171
+
172
+ RESP=$(curl -s "$BASE/api/events?pattern=session:*&limit=10" -H "$AUTH")
173
+ COUNT=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo 0)
174
+ if [ "$COUNT" -ge 1 ]; then
175
+ pass "Pattern filter session:* returned $COUNT event(s)"
176
+ else
177
+ fail "Pattern filter session:* should return >= 1"
178
+ fi
179
+
180
+ # Since filter
181
+ PAST=$(date -u -v-1H +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "1 hour ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
182
+ if [ -n "$PAST" ]; then
183
+ RESP=$(curl -s "$BASE/api/events?since=$PAST&limit=10" -H "$AUTH")
184
+ COUNT=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo 0)
185
+ if [ "$COUNT" -ge 4 ]; then
186
+ pass "Since filter returned $COUNT recent events"
187
+ else
188
+ fail "Since filter should return >= 4 recent events"
189
+ fi
190
+ fi
191
+
192
+ # ─── 4. SSE Streaming ────────────────────────────────────────────────
193
+ echo -e "\n${YELLOW}[4] SSE Real-Time Streaming${NC}"
194
+
195
+ SSE_OUT=$(mktemp)
196
+ curl -s -N -m 3 "$BASE/api/events/stream?patterns=test:*" -H "$AUTH" > "$SSE_OUT" 2>&1 &
197
+ SSE_PID=$!
198
+ sleep 0.5
199
+
200
+ # Publish while SSE is listening
201
+ curl -s -X POST "$BASE/api/events" \
202
+ -H "$AUTH" -H "Content-Type: application/json" \
203
+ -d '{"type":"custom","source":"sse-test","data":{"live":"stream"}}' > /dev/null
204
+
205
+ # This won't match test:* pattern, publish one that does
206
+ curl -s -X POST "$BASE/api/events" \
207
+ -H "$AUTH" -H "Content-Type: application/json" \
208
+ -d '{"type":"custom","source":"test:probe","data":{"sse":"verify"}}' > /dev/null
209
+
210
+ sleep 2
211
+ wait $SSE_PID 2>/dev/null || true
212
+
213
+ if grep -q "retry:" "$SSE_OUT"; then
214
+ pass "SSE connection established (retry header)"
215
+ else
216
+ fail "SSE did not send retry header"
217
+ fi
218
+ rm -f "$SSE_OUT"
219
+
220
+ # ─── 5. WebSocket ────────────────────────────────────────────────────
221
+ echo -e "\n${YELLOW}[5] WebSocket Streaming${NC}"
222
+
223
+ if command -v wscat &>/dev/null; then
224
+ WS_OUT=$(mktemp)
225
+ wscat -c "ws://localhost:$PORT/ws/events?patterns=*&token=$TOKEN" --wait 2 > "$WS_OUT" 2>&1 &
226
+ WS_PID=$!
227
+ sleep 1
228
+
229
+ curl -s -X POST "$BASE/api/events" \
230
+ -H "$AUTH" -H "Content-Type: application/json" \
231
+ -d '{"type":"custom","source":"ws-test","data":{"ws":"verify"}}' > /dev/null
232
+
233
+ sleep 1
234
+ kill $WS_PID 2>/dev/null || true
235
+ wait $WS_PID 2>/dev/null || true
236
+
237
+ if grep -q "ws-test" "$WS_OUT"; then
238
+ pass "WebSocket received event"
239
+ else
240
+ fail "WebSocket did not receive event"
241
+ fi
242
+ rm -f "$WS_OUT"
243
+ else
244
+ skip "WebSocket test (install: npm i -g wscat)"
245
+ fi
246
+
247
+ # ─── 6. Peter Parker Config ──────────────────────────────────────────
248
+ echo -e "\n${YELLOW}[6] Peter Parker Config Generation${NC}"
249
+
250
+ for PROFILE in cost balanced quality; do
251
+ FLAG="--$PROFILE"
252
+ node dist/index.js peter setup $FLAG 2>/dev/null
253
+ if [ -f .ralph-tui/config.toml ] && grep -q "Generated by Peter Parker" .ralph-tui/config.toml; then
254
+ AGENT_COUNT=$(grep -c '^\[\[agents\]\]' .ralph-tui/config.toml)
255
+ if [ "$AGENT_COUNT" -eq 10 ]; then
256
+ pass "$PROFILE profile: 10 agents generated"
257
+ else
258
+ fail "$PROFILE profile: expected 10 agents, got $AGENT_COUNT"
259
+ fi
260
+ else
261
+ fail "$PROFILE profile: config not generated"
262
+ fi
263
+ done
264
+
265
+ # Verify builder is default
266
+ if grep -A3 'name = "builder"' .ralph-tui/config.toml | grep -q 'default = true'; then
267
+ pass "Builder agent is marked as default"
268
+ else
269
+ fail "Builder should be default agent"
270
+ fi
271
+
272
+ # ─── 7. Peter Parker Status ──────────────────────────────────────────
273
+ echo -e "\n${YELLOW}[7] Peter Parker Status${NC}"
274
+
275
+ OUTPUT=$(node dist/index.js peter status 2>&1)
276
+ if echo "$OUTPUT" | grep -q "Config profile"; then
277
+ pass "Peter status shows config profile"
278
+ else
279
+ fail "Peter status missing config profile"
280
+ fi
281
+
282
+ if echo "$OUTPUT" | grep -q "Recent events"; then
283
+ pass "Peter status shows recent events"
284
+ else
285
+ fail "Peter status missing recent events"
286
+ fi
287
+
288
+ # ─── 8. Event Persistence ────────────────────────────────────────────
289
+ echo -e "\n${YELLOW}[8] Event Persistence${NC}"
290
+
291
+ if [ -f .jfl/map-events.jsonl ]; then
292
+ LINE_COUNT=$(wc -l < .jfl/map-events.jsonl | tr -d ' ')
293
+ if [ "$LINE_COUNT" -ge 4 ]; then
294
+ pass "map-events.jsonl has $LINE_COUNT persisted events"
295
+ else
296
+ fail "Expected >= 4 persisted events, got $LINE_COUNT"
297
+ fi
298
+ else
299
+ fail "map-events.jsonl not created"
300
+ fi
301
+
302
+ # Restart and check events survive
303
+ node dist/index.js context-hub stop 2>/dev/null
304
+ sleep 1
305
+ node dist/index.js context-hub start 2>/dev/null
306
+ sleep 2
307
+
308
+ # Token regenerates on restart — re-read it
309
+ TOKEN=$(cat .jfl/context-hub.token 2>/dev/null)
310
+ AUTH="Authorization: Bearer $TOKEN"
311
+
312
+ RESP=$(curl -s "$BASE/api/events?limit=50" -H "$AUTH" 2>/dev/null)
313
+ COUNT=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])" 2>/dev/null || echo 0)
314
+ if [ "$COUNT" -ge 4 ]; then
315
+ pass "Events survived restart ($COUNT recovered)"
316
+ else
317
+ fail "Events lost on restart (got $COUNT)"
318
+ fi
319
+
320
+ # ─── 9. Pi CLI Detection ─────────────────────────────────────────────
321
+ echo -e "\n${YELLOW}[9] Pi CLI Detection${NC}"
322
+
323
+ if grep -q '"pi"' src/commands/session.ts && grep -q '"--yolo"' src/commands/session.ts; then
324
+ pass "Pi provider in session.ts"
325
+ else
326
+ fail "Pi provider missing from session.ts"
327
+ fi
328
+
329
+ if command -v pi &>/dev/null; then
330
+ pass "Pi CLI found in PATH"
331
+ else
332
+ skip "Pi CLI not installed (detection code is correct)"
333
+ fi
334
+
335
+ # ─── 10. Ralph Integration ───────────────────────────────────────────
336
+ echo -e "\n${YELLOW}[10] Ralph Integration${NC}"
337
+
338
+ if grep -q "PeterParkerBridge" src/commands/ralph.ts && grep -q "\-\-listen" src/commands/ralph.ts; then
339
+ pass "Ralph injects --listen and starts bridge"
340
+ else
341
+ fail "Ralph missing --listen injection or bridge"
342
+ fi
343
+
344
+ if command -v ralph-tui &>/dev/null; then
345
+ pass "ralph-tui found in PATH"
346
+ else
347
+ skip "ralph-tui not installed (integration code is correct)"
348
+ fi
349
+
350
+ # ─── Summary ─────────────────────────────────────────────────────────
351
+ echo -e "\n${CYAN}═══════════════════════════════════════════════════════${NC}"
352
+ echo -e " ${GREEN}Passed: $PASS${NC} ${RED}Failed: $FAIL${NC} ${YELLOW}Skipped: $SKIP${NC}"
353
+ echo -e "${CYAN}═══════════════════════════════════════════════════════${NC}\n"
354
+
355
+ if [ "$FAIL" -gt 0 ]; then
356
+ exit 1
357
+ fi