ripplo 0.7.23 → 0.7.25

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{A as re,C as de,F as C,H as ce,I as ue,K as pe,L as fe,N as h,O as Re,b as G,ba as w,ca as T,da as ve,e as Q,ea as q,fa as Pe,h as y,ha as _,i as A,ia as xe,ja as he,l as H,la as be,na as Ee,pa as Ie,q as X,qa as Ce,ra as Te,sa as We,t as I,ta as Le,ua as De,v as Z,va as Ae,w as ee,wa as He,ya as Fe,za as qe}from"./chunk-MOJRP3IN.js";import{H as te,N as ie,P as ae,Q as se,R as F,Z as le,ba as me,c as J,ca as ye,da as ge,ea as ke,fa as we,g as K,ga as Se,h as V,k as Y,n as ne,p as oe}from"./chunk-R7CQWEBF.js";import{b as _e}from"./chunk-BJVAPI4P.js";import{c as B,d as E,e as l,f as D,g as z}from"./chunk-EVYYSTR7.js";import"./chunk-IHSHBPJY.js";import"./chunk-7UWDMECF.js";import sn from"path";import{createClient as ln}from"graphql-sse";import dn,{AbortError as cn}from"p-retry";import{graphql as N}from"gql.tada";import{graphql as Pr}from"gql.tada";import{print as xr}from"graphql";async function $e({config:e,cwd:n,headed:r,pool:t,runId:o,signal:i,testSlug:a,workClass:s,workflowSlug:d}){let{fingerprint:c,result:u}=await y(n);if(u.isErr())return await C(e,o,`lockfile:${u.error.kind}`),{kind:"dispatch-error",reason:{detail:`lockfile:${u.error.kind}`,type:"generic"}};let f=vr(u.value,{testSlug:a,workflowSlug:d});if(f==null)return await C(e,o,`no-test:${d}/${a}`),{kind:"dispatch-error",reason:{detail:`no-test:${d}/${a}`,type:"generic"}};let m=await w({headed:r,workClass:s,task:()=>t.execute({headed:r,lockfileFingerprint:c,runId:o,testRef:f},i)});return!m.serverNotified&&m.outcome.kind==="error"&&await C(e,o,m.outcome.detail),m.outcome}function vr(e,{testSlug:n,workflowSlug:r}){let o=e.workflows.find(i=>Y(i.name)===r)?.tests.find(i=>i.slug===n);return o==null?void 0:ne(o)}var hr=6e4,br=Pr(`
2
+ import{A as re,C as de,F as C,H as ce,I as ue,K as pe,L as fe,N as h,O as Re,b as G,ca as ve,e as Q,ea as Pe,ga as xe,h as y,ha as he,i as A,ia as be,ja as Ee,ka as Ie,l as H,la as Ce,ma as Te,na as We,pa as w,q as X,qa as T,ra as Le,sa as q,t as I,ta as De,v as Z,va as _,w as ee,wa as Ae,xa as He,ya as Fe,za as qe}from"./chunk-D7HOYI2S.js";import{D as te,F as ie,G as ae,H as F,P as se,Z as le,ba as me,c as J,ca as ye,da as ge,ea as ke,fa as we,g as K,ga as Se,h as V,k as Y,n as ne,p as oe}from"./chunk-NXJP3Q4N.js";import{b as _e}from"./chunk-BJVAPI4P.js";import{c as B,d as E,e as l,f as D,g as z}from"./chunk-EVYYSTR7.js";import"./chunk-IHSHBPJY.js";import"./chunk-7UWDMECF.js";import sn from"path";import{createClient as ln}from"graphql-sse";import dn,{AbortError as cn}from"p-retry";import{graphql as N}from"gql.tada";import{graphql as Pr}from"gql.tada";import{print as xr}from"graphql";async function $e({config:e,cwd:n,headed:r,pool:t,runId:o,signal:i,testSlug:a,workClass:s,workflowSlug:d}){let{fingerprint:c,result:u}=await y(n);if(u.isErr())return await C(e,o,`lockfile:${u.error.kind}`),{kind:"dispatch-error",reason:{detail:`lockfile:${u.error.kind}`,type:"generic"}};let f=vr(u.value,{testSlug:a,workflowSlug:d});if(f==null)return await C(e,o,`no-test:${d}/${a}`),{kind:"dispatch-error",reason:{detail:`no-test:${d}/${a}`,type:"generic"}};let m=await w({headed:r,workClass:s,task:()=>t.execute({headed:r,lockfileFingerprint:c,runId:o,testRef:f},i)});return!m.serverNotified&&m.outcome.kind==="error"&&await C(e,o,m.outcome.detail),m.outcome}function vr(e,{testSlug:n,workflowSlug:r}){let o=e.workflows.find(i=>Y(i.name)===r)?.tests.find(i=>i.slug===n);return o==null?void 0:ne(o)}var hr=6e4,br=Pr(`
3
3
  subscription RunRequestedWatch($devSessionId: String!) {
4
4
  runRequested(devSessionId: $devSessionId) {
5
5
  runId
@@ -9,7 +9,7 @@ import{A as re,C as de,F as C,H as ce,I as ue,K as pe,L as fe,N as h,O as Re,b a
9
9
  }
10
10
  }
11
11
  `);async function Oe(e){let n=Math.min(1e3*2**e,hr)+Cr();await new Promise(r=>setTimeout(r,n))}function je({claim:e,cwd:n,devSessionId:r,onSubscriptionDead:t,pool:o,sseClient:i,sseHealth:a}){let s=new Set;return i.subscribe({query:xr(br),variables:{devSessionId:r}},{complete:()=>{},error:d=>{let c=d instanceof Error?d.message:String(d);l.warn("runRequested SSE failed: %s; re-registering session",c),t(c)},next:d=>{Er({claim:e,cwd:n,event:d.data?.runRequested,inFlightRunIds:s,pool:o})}},{connected:()=>{a.onConnected()},connecting:d=>{a.onConnecting(d),d&&l.debug("runRequested SSE reconnecting")}})}function Er({claim:e,cwd:n,event:r,inFlightRunIds:t,pool:o}){if(r==null)return;if(t.has(r.runId)){l.warn({runId:r.runId,workflowSlug:r.workflowSlug},"duplicate runRequested event; skipping dispatch");return}t.add(r.runId),process.stdout.write(`ripplo: run ${r.workflowSlug}
12
- `),G(n);let i=I(n).match(s=>s,s=>{l.error({failure:s,runId:r.runId},"failed to load env for run dispatch; skipping")});if(i==null){t.delete(r.runId);return}let a=Ir(e(r.runId),r);a.started(),$e({config:i,cwd:n,headed:a.headed,pool:o,runId:r.runId,signal:a.signal,testSlug:r.testSlug,workClass:a.workClass,workflowSlug:r.workflowSlug}).then(s=>{a.finished(s)}).catch(s=>{l.error({err:s,runId:r.runId,workflowSlug:r.workflowSlug},"run execution failed; daemon continuing"),a.finished({detail:s instanceof Error?s.message:String(s),kind:"error"})}).finally(()=>{t.delete(r.runId)})}function Ir(e,n){return e==null?(l.debug({runId:n.runId,workflowSlug:n.workflowSlug},"dispatching unclaimed run (externally triggered or replayed)"),{headed:!1,signal:new AbortController().signal,workClass:"p0-ui",finished:()=>{},started:()=>{}}):{headed:e.headed,signal:xe(e.token),workClass:"p1-cli",finished:r=>{e.finished(n.runId,r)},started:()=>{e.started(n.runId)}}}function Cr(){return Math.floor(Math.random()*2700)+300}import{graphql as Tr}from"gql.tada";import{print as Wr}from"graphql";var Lr=Tr(`
12
+ `),G(n);let i=I(n).match(s=>s,s=>{l.error({failure:s,runId:r.runId},"failed to load env for run dispatch; skipping")});if(i==null){t.delete(r.runId);return}let a=Ir(e(r.runId),r);a.started(),$e({config:i,cwd:n,headed:a.headed,pool:o,runId:r.runId,signal:a.signal,testSlug:r.testSlug,workClass:a.workClass,workflowSlug:r.workflowSlug}).then(s=>{a.finished(s)}).catch(s=>{l.error({err:s,runId:r.runId,workflowSlug:r.workflowSlug},"run execution failed; daemon continuing"),a.finished({detail:s instanceof Error?s.message:String(s),kind:"error"})}).finally(()=>{t.delete(r.runId)})}function Ir(e,n){return e==null?(l.debug({runId:n.runId,workflowSlug:n.workflowSlug},"dispatching unclaimed run (externally triggered or replayed)"),{headed:!1,signal:new AbortController().signal,workClass:"p0-ui",finished:()=>{},started:()=>{}}):{headed:e.headed,signal:Ae(e.token),workClass:"p1-cli",finished:r=>{e.finished(n.runId,r)},started:()=>{e.started(n.runId)}}}function Cr(){return Math.floor(Math.random()*2700)+300}import{graphql as Tr}from"gql.tada";import{print as Wr}from"graphql";var Lr=Tr(`
13
13
  subscription TeleportRequestedWatch($devSessionId: String!) {
14
14
  teleportRequested(devSessionId: $devSessionId) {
15
15
  runId
@@ -19,7 +19,7 @@ import{A as re,C as de,F as C,H as ce,I as ue,K as pe,L as fe,N as h,O as Re,b a
19
19
  }
20
20
  }
21
21
  `);function Ne({cwd:e,devSessionId:n,onSubscriptionDead:r,sseClient:t}){let o=new Set,i=t.subscribe({query:Wr(Lr),variables:{devSessionId:n}},{complete:()=>{},error:a=>{let s=a instanceof Error?a.message:String(a);l.warn("teleportRequested SSE failed: %s; re-registering session",s),r(s)},next:a=>{Dr({cwd:e,event:a.data?.teleportRequested,running:o})}});return()=>{o.forEach(a=>{a.abort()}),o.clear(),i()}}function Dr({cwd:e,event:n,running:r}){if(n==null)return;let t=`${n.workflowSlug}/${n.testSlug}`,o=new AbortController;r.add(o),process.stdout.write(`ripplo: teleport ${t} step ${String(n.stepIndex+1)}
22
- `),Fe({cwd:e,ref:t,signal:o.signal,step:n.stepIndex+1}).then(i=>{i.kind!=="done"&&l.warn({kind:i.kind,ref:t},"teleport could not open")}).catch(i=>{l.error({err:i,ref:t},"teleport failed; daemon continuing")}).finally(()=>{r.delete(o)})}function Me(){let e=!0,n,r=null,t=()=>{r!=null&&(clearTimeout(r),r=null)};return{markUnhealthy:o=>{e=!1,n=o,t()},onConnected:()=>{t(),r=setTimeout(()=>{e=!0,n=void 0,r=null},1e4)},onConnecting:()=>{t()},snapshot:()=>({detail:n,ok:e})}}import Ar from"fs";import Ge from"net";import{err as Qe,ok as Hr}from"neverthrow";async function Je({cwd:e,onConnection:n}){z(e);let r=Re(e),t=await Ue({onConnection:n,socketPath:r});return t.isOk()||t.error.kind!=="stale-candidate"?t.map(Be(r)).mapErr(ze(r)):await Fr(r)?Qe({kind:"already-running",socketPath:r}):(Ke(r),(await Ue({onConnection:n,socketPath:r})).map(Be(r)).mapErr(ze(r)))}function Fr(e){return new Promise(n=>{let r=Ge.connect(e);r.once("connect",()=>{r.destroy(),n(!0)}),r.once("error",()=>{n(!1)})})}async function Ue(e){let n=await qr(e);return n.kind==="listening"?Hr(n.server):Qe(n.error)}function qr({onConnection:e,socketPath:n}){return new Promise(r=>{let t=Ge.createServer(e);t.once("error",o=>{r({error:o.code==="EADDRINUSE"?{kind:"stale-candidate"}:{kind:"listen-failed",message:o.message},kind:"failed"})}),t.listen(n,()=>{r({kind:"listening",server:t})})})}function Be(e){return n=>({socketPath:e,close:()=>{n.close(),Ke(e)}})}function Ke(e){Ar.rmSync(e,{force:!0})}function ze(e){return n=>n.kind==="stale-candidate"?{kind:"already-running",socketPath:e}:{kind:"bind-failed",message:n.message}}function $(e){return{priority:e,next:()=>null,onResult:()=>{}}}function Ve({config:e,executor:n,explore:r,exploreConcurrency:t,pool:o}){return _r({appUrl:e.appUrl,cwd:e.cwd,explore:r&&n==="local",exploreConcurrency:t,executeTrail:async(i,a)=>(await o).exploreTrail(i,a)})}function _r({appUrl:e,cwd:n,executeTrail:r,explore:t,exploreConcurrency:o}){let i={notify:()=>{}},a=t?$r({appUrl:e,cwd:n,executeTrail:r,notifyWork:()=>{i.notify()}}):pe(),s=qe({exploreConcurrency:o,exploreEnabled:t,now:Date.now,probe:Or,setTimer:jr,sources:[$("p0"),$("p1"),a],execute:({job:c,priority:u,signal:f})=>w({headed:!1,workClass:Nr(u),task:()=>c.run(f)})});q(()=>{s.notifyQueueChange()}),i.notify=()=>{s.notifyQueueChange()};let d=t?ue({onChange:c=>{s.setExplorerHolder(c)}}):null;return{status:()=>({explorer:Mr({explore:t,holder:s.explorerHolder()}),exploring:s.explorationActive(),progress:a.status()}),stop:()=>{d?.stop(),s.stop(),q(null),a.stop()}}}function $r({appUrl:e,cwd:n,executeTrail:r,notifyWork:t}){return fe({cwd:n,executeTrail:r,notifyWork:t,loadLockfile:async()=>{let{fingerprint:o,result:i}=await y(n);return i.match(a=>({fingerprint:o,lockfile:a}),()=>null)},probeApp:async()=>await re(e)==null})}function Or(){let e=ve();return{headedActive:e.headedActive,interactiveActive:e.active,interactiveQueued:e.queued,poolSize:e.poolSize}}function jr(e,n){let r=setTimeout(e,n);return()=>{clearTimeout(r)}}function Nr(e){switch(e){case"p0":return"p0-ui";case"p1":return"p1-cli";case"p2":return"p2-cover";case"p3":return"p3-explore"}}function Mr({explore:e,holder:n}){return e?n?"holder":"standby":"off"}async function Ye({cwd:e,finding:n}){let r={at:new Date().toISOString(),kind:"resolution",signature:n.signature};await F(h(e),[r]).match(()=>{},t=>{l.warn("explore findings log append failed: %s",t.kind)})}async function Xe({cwd:e,finding:n,outcome:r}){let t=r.kind==="clean"?[...r.rows,{at:new Date().toISOString(),kind:"resolution",signature:n.signature}]:r.rows;t.length!==0&&await F(h(e),t).match(()=>{},o=>{l.warn("explore findings log append failed: %s",o.kind)})}function Ze({config:e,cwd:n,pool:r}){let t={cwd:n,executeTrail:(o,i)=>w({headed:!1,workClass:"p1-cli",task:async()=>(await r).exploreTrail(o,i)}),loadLockfile:async()=>{let o=await y(n);return o.result.match(i=>({fingerprint:o.fingerprint,lockfile:i}),()=>null)},pushResolved:o=>ce(e,o,"resolved").catch(i=>{l.warn({error:i},"explore finding status push failed")})};return(o,i)=>Ur(t,{findingId:o,signal:i})}async function Ur(e,n){let r=await Br(e.cwd,n.findingId);if("reply"in r)return r.reply;let t=await e.loadLockfile();if(t==null)return{kind:"error",reason:"lockfile-unavailable"};let o=zr(t,r.finding);if("reply"in o)return o.reply;let i=await e.executeTrail(o.assign,n.signal),a=await Qr({cwd:e.cwd,finding:r.finding,outcome:i});return(a.kind==="resolved"||a.kind==="unreachable")&&await e.pushResolved(r.finding.signature),a}async function Br(e,n){return se(h(e)).match(r=>{let o=[...ae(r).findings.entries()].find(([a])=>ie(a)===n);if(o==null)return{reply:{kind:"finding-not-found"}};let[,i]=o;return i.resolvedAt!=null?{reply:{kind:"finding-not-found"}}:{finding:i.latest}},()=>({reply:{kind:"unreplayable",reason:"ledger-unreadable"}}))}function zr(e,n){let r=le(e.lockfile,{sweep:!1}).find(o=>o.name===n.baseState);if(r==null)return{reply:{kind:"unreplayable",reason:"base-state-missing"}};let t=Gr(e.lockfile,n);return t==null?{reply:{kind:"unreplayable",reason:"action-missing"}}:{assign:{baseStateTest:r.test,lensId:n.lensId,lockfileFingerprint:e.fingerprint,lockfileHash:V(e.lockfile),maxLength:t.length,shrinkBudget:0,stepRuns:t}}}function Gr(e,n){let t=oe(e).map(a=>`${a.test}#${String(a.index)}`),i=n.trail.map((a,s)=>{let d=t.indexOf(a),c=n.trailParams[s];return d===-1||c==null?null:{idx:d,params:c}}).flatMap(a=>a==null?[]:[a]);return i.length===n.trail.length?i:null}async function Qr({cwd:e,finding:n,outcome:r}){return r.kind==="aborted"?{kind:"aborted"}:r.kind==="error"?Jr({cwd:e,finding:n,reason:r.reason}):(await Xe({cwd:e,finding:n,outcome:r}),r.kind==="clean"?{kind:"resolved"}:r.kind==="flaky"?{kind:"flaky"}:Kr(n,r))}async function Jr({cwd:e,finding:n,reason:r}){return r==="empty-trail"?(await Ye({cwd:e,finding:n}),{kind:"unreachable"}):{kind:"error",reason:r??"trail-error"}}function Kr(e,n){let r=n.rows.find(o=>o.kind==="finding"),t=r?.kind==="finding"?r.runId:void 0;return r!=null&&r.signature===e.signature?{kind:"still-failing",runId:t}:{kind:"diverged",runId:t}}import{fork as Vr}from"child_process";import er from"os";import{CancellationTokenSource as rr,ResponseError as nr}from"vscode-jsonrpc/node";function or(e,n){return()=>{let r=Vr(e,["run-worker"],{cwd:n,stdio:["ignore","inherit","inherit","ipc"]});return{connection:He(r),kill:()=>{r.kill("SIGKILL")},onExit:t=>{r.once("exit",t)}}}}function tr(e){let n={closed:!1,lockfileProvider:e.lockfileProvider,nextId:0,size:e.initialSize,spawn:e.spawn,waiters:[],workers:[]};return{close:async()=>{n.closed=!0,await Promise.all([...n.workers].map(r=>O(r,n)))},execute:(r,t)=>Xr(r,t,n),exploreTrail:(r,t)=>rn(r,t,n),routeSpan:(r,t)=>{let o=n.workers.find(i=>i.runId===r);o?.worker.connection.sendNotification(De,{runId:r,span:t})},setSize:r=>{on(r,n)}}}var Yr=1e4,ir=-32800;async function Xr(e,n,r){let t=await ar(r);if(t.runId=e.runId,!t.alive)return W(t,r),{outcome:{detail:"worker-exit",kind:"error"},serverNotified:!1};let o=new rr,i=()=>{o.cancel()};n.aborted&&o.cancel(),n.addEventListener("abort",i,{once:!0});try{let a=await t.worker.connection.sendRequest(Te,e,o.token),s=Ee.safeParse(a);return s.success?s.data:{outcome:{detail:"bad-outcome-frame",kind:"error"},serverNotified:!1}}catch(a){return en(a,t)}finally{n.removeEventListener("abort",i),o.dispose(),W(t,r)}}async function ar(e){let n=e.workers.find(r=>r.alive&&r.runId===void 0);if(n!=null)return n.runId="pending",n;if(e.workers.length<e.size){let r=Zr(e);return r.runId="pending",await r.ready,r}return new Promise(r=>{e.waiters.push(t=>{t.runId="pending",r(t)})})}function Zr(e){let n=e.spawn(),r={resolve:()=>{}},t={alive:!0,id:e.nextId,ready:new Promise(o=>{r.resolve=o,n.connection.onNotification(Ce,o)}),runId:void 0,worker:n};return e.nextId+=1,n.connection.onRequest(We,async o=>{let i=be.safeParse(o);return i.success?e.lockfileProvider(i.data.fingerprint):{unavailable:"bad-lockfile-request"}}),n.onExit(()=>{t.alive=!1,e.workers=e.workers.filter(o=>o!==t),n.connection.dispose(),r.resolve()}),e.workers.push(t),t}function W(e,n){if(e.runId=void 0,!e.alive||n.closed)return;if(n.workers.length>n.size){O(e,n);return}let r=n.waiters.shift();r?.(e)}async function O(e,n){if(n.workers=n.workers.filter(o=>o!==e),!e.alive)return;let r=new Promise(o=>{e.worker.onExit(o)});await e.worker.connection.sendNotification(Ae).catch(()=>{});let t=setTimeout(()=>{e.worker.kill()},Yr);await r,clearTimeout(t)}function en(e,n){return e instanceof nr&&e.code===ir?{outcome:{detail:"aborted",kind:"error"},serverNotified:!1}:n.alive?{outcome:{detail:e instanceof Error?e.message:String(e),kind:"error"},serverNotified:!1}:{outcome:{detail:"worker-exit",kind:"error"},serverNotified:!1}}async function rn(e,n,r){let t=await ar(r);if(t.runId="explore",!t.alive)return W(t,r),{kind:"error",rows:[],trail:[]};let o=new rr,i=()=>{o.cancel()};n.aborted&&o.cancel(),n.addEventListener("abort",i,{once:!0});try{let a=await t.worker.connection.sendRequest(Le,e,o.token),s=Ie.safeParse(a);return s.success?s.data:{kind:"error",rows:[],trail:[]}}catch(a){return nn(a)}finally{n.removeEventListener("abort",i),o.dispose(),W(t,r)}}function nn(e){return e instanceof nr&&e.code===ir?{kind:"aborted",rows:[],trail:[]}:{kind:"error",reason:`transport:${e instanceof Error?e.message.slice(0,120):"unknown"}`,rows:[],trail:[]}}function on(e,n){n.size=e,e>er.availableParallelism()&&l.warn({cores:er.availableParallelism(),size:e},"worker pool size exceeds available cores");let r=n.workers.filter(o=>o.alive&&o.runId===void 0),t=n.workers.length-e;r.slice(0,Math.max(0,t)).forEach(o=>{O(o,n)})}import L from"fs";import{graphql as sr}from"gql.tada";import{print as lr}from"graphql";var tn=sr(`
22
+ `),Fe({cwd:e,ref:t,signal:o.signal,step:n.stepIndex+1}).then(i=>{i.kind!=="done"&&l.warn({kind:i.kind,ref:t},"teleport could not open")}).catch(i=>{l.error({err:i,ref:t},"teleport failed; daemon continuing")}).finally(()=>{r.delete(o)})}function Me(){let e=!0,n,r=null,t=()=>{r!=null&&(clearTimeout(r),r=null)};return{markUnhealthy:o=>{e=!1,n=o,t()},onConnected:()=>{t(),r=setTimeout(()=>{e=!0,n=void 0,r=null},1e4)},onConnecting:()=>{t()},snapshot:()=>({detail:n,ok:e})}}import Ar from"fs";import Ge from"net";import{err as Qe,ok as Hr}from"neverthrow";async function Je({cwd:e,onConnection:n}){z(e);let r=Re(e),t=await Ue({onConnection:n,socketPath:r});return t.isOk()||t.error.kind!=="stale-candidate"?t.map(Be(r)).mapErr(ze(r)):await Fr(r)?Qe({kind:"already-running",socketPath:r}):(Ke(r),(await Ue({onConnection:n,socketPath:r})).map(Be(r)).mapErr(ze(r)))}function Fr(e){return new Promise(n=>{let r=Ge.connect(e);r.once("connect",()=>{r.destroy(),n(!0)}),r.once("error",()=>{n(!1)})})}async function Ue(e){let n=await qr(e);return n.kind==="listening"?Hr(n.server):Qe(n.error)}function qr({onConnection:e,socketPath:n}){return new Promise(r=>{let t=Ge.createServer(e);t.once("error",o=>{r({error:o.code==="EADDRINUSE"?{kind:"stale-candidate"}:{kind:"listen-failed",message:o.message},kind:"failed"})}),t.listen(n,()=>{r({kind:"listening",server:t})})})}function Be(e){return n=>({socketPath:e,close:()=>{n.close(),Ke(e)}})}function Ke(e){Ar.rmSync(e,{force:!0})}function ze(e){return n=>n.kind==="stale-candidate"?{kind:"already-running",socketPath:e}:{kind:"bind-failed",message:n.message}}function $(e){return{priority:e,next:()=>null,onResult:()=>{}}}function Ve({config:e,executor:n,explore:r,exploreConcurrency:t,pool:o}){return _r({appUrl:e.appUrl,cwd:e.cwd,explore:r&&n==="local",exploreConcurrency:t,executeTrail:async(i,a)=>(await o).exploreTrail(i,a)})}function _r({appUrl:e,cwd:n,executeTrail:r,explore:t,exploreConcurrency:o}){let i={notify:()=>{}},a=t?$r({appUrl:e,cwd:n,executeTrail:r,notifyWork:()=>{i.notify()}}):pe(),s=qe({exploreConcurrency:o,exploreEnabled:t,now:Date.now,probe:Or,setTimer:jr,sources:[$("p0"),$("p1"),a],execute:({job:c,priority:u,signal:f})=>w({headed:!1,workClass:Nr(u),task:()=>c.run(f)})});q(()=>{s.notifyQueueChange()}),i.notify=()=>{s.notifyQueueChange()};let d=t?ue({onChange:c=>{s.setExplorerHolder(c)}}):null;return{status:()=>({explorer:Mr({explore:t,holder:s.explorerHolder()}),exploring:s.explorationActive(),progress:a.status()}),stop:()=>{d?.stop(),s.stop(),q(null),a.stop()}}}function $r({appUrl:e,cwd:n,executeTrail:r,notifyWork:t}){return fe({cwd:n,executeTrail:r,notifyWork:t,loadLockfile:async()=>{let{fingerprint:o,result:i}=await y(n);return i.match(a=>({fingerprint:o,lockfile:a}),()=>null)},probeApp:async()=>await re(e)==null})}function Or(){let e=Le();return{headedActive:e.headedActive,interactiveActive:e.active,interactiveQueued:e.queued,poolSize:e.poolSize}}function jr(e,n){let r=setTimeout(e,n);return()=>{clearTimeout(r)}}function Nr(e){switch(e){case"p0":return"p0-ui";case"p1":return"p1-cli";case"p2":return"p2-cover";case"p3":return"p3-explore"}}function Mr({explore:e,holder:n}){return e?n?"holder":"standby":"off"}async function Ye({cwd:e,finding:n}){let r={at:new Date().toISOString(),kind:"resolution",signature:n.signature};await F(h(e),[r]).match(()=>{},t=>{l.warn("explore findings log append failed: %s",t.kind)})}async function Xe({cwd:e,finding:n,outcome:r}){let t=r.kind==="clean"?[...r.rows,{at:new Date().toISOString(),kind:"resolution",signature:n.signature}]:r.rows;t.length!==0&&await F(h(e),t).match(()=>{},o=>{l.warn("explore findings log append failed: %s",o.kind)})}function Ze({config:e,cwd:n,pool:r}){let t={cwd:n,executeTrail:(o,i)=>w({headed:!1,workClass:"p1-cli",task:async()=>(await r).exploreTrail(o,i)}),loadLockfile:async()=>{let o=await y(n);return o.result.match(i=>({fingerprint:o.fingerprint,lockfile:i}),()=>null)},pushResolved:o=>ce(e,o,"resolved").catch(i=>{l.warn({error:i},"explore finding status push failed")})};return(o,i)=>Ur(t,{findingId:o,signal:i})}async function Ur(e,n){let r=await Br(e.cwd,n.findingId);if("reply"in r)return r.reply;let t=await e.loadLockfile();if(t==null)return{kind:"error",reason:"lockfile-unavailable"};let o=zr(t,r.finding);if("reply"in o)return o.reply;let i=await e.executeTrail(o.assign,n.signal),a=await Qr({cwd:e.cwd,finding:r.finding,outcome:i});return(a.kind==="resolved"||a.kind==="unreachable")&&await e.pushResolved(r.finding.signature),a}async function Br(e,n){return ae(h(e)).match(r=>{let o=[...ie(r).findings.entries()].find(([a])=>te(a)===n);if(o==null)return{reply:{kind:"finding-not-found"}};let[,i]=o;return i.resolvedAt!=null?{reply:{kind:"finding-not-found"}}:{finding:i.latest}},()=>({reply:{kind:"unreplayable",reason:"ledger-unreadable"}}))}function zr(e,n){let r=se(e.lockfile,{sweep:!1}).find(o=>o.name===n.baseState);if(r==null)return{reply:{kind:"unreplayable",reason:"base-state-missing"}};let t=Gr(e.lockfile,n);return t==null?{reply:{kind:"unreplayable",reason:"action-missing"}}:{assign:{baseStateTest:r.test,lensId:n.lensId,lockfileFingerprint:e.fingerprint,lockfileHash:V(e.lockfile),maxLength:t.length,shrinkBudget:0,stepRuns:t}}}function Gr(e,n){let t=oe(e).map(a=>`${a.test}#${String(a.index)}`),i=n.trail.map((a,s)=>{let d=t.indexOf(a),c=n.trailParams[s];return d===-1||c==null?null:{idx:d,params:c}}).flatMap(a=>a==null?[]:[a]);return i.length===n.trail.length?i:null}async function Qr({cwd:e,finding:n,outcome:r}){return r.kind==="aborted"?{kind:"aborted"}:r.kind==="error"?Jr({cwd:e,finding:n,reason:r.reason}):(await Xe({cwd:e,finding:n,outcome:r}),r.kind==="clean"?{kind:"resolved"}:r.kind==="flaky"?{kind:"flaky"}:Kr(n,r))}async function Jr({cwd:e,finding:n,reason:r}){return r==="empty-trail"?(await Ye({cwd:e,finding:n}),{kind:"unreachable"}):{kind:"error",reason:r??"trail-error"}}function Kr(e,n){let r=n.rows.find(o=>o.kind==="finding"),t=r?.kind==="finding"?r.runId:void 0;return r!=null&&r.signature===e.signature?{kind:"still-failing",runId:t}:{kind:"diverged",runId:t}}import{fork as Vr}from"child_process";import er from"os";import{CancellationTokenSource as rr,ResponseError as nr}from"vscode-jsonrpc/node";function or(e,n){return()=>{let r=Vr(e,["run-worker"],{cwd:n,stdio:["ignore","inherit","inherit","ipc"]});return{connection:We(r),kill:()=>{r.kill("SIGKILL")},onExit:t=>{r.once("exit",t)}}}}function tr(e){let n={closed:!1,lockfileProvider:e.lockfileProvider,nextId:0,size:e.initialSize,spawn:e.spawn,waiters:[],workers:[]};return{close:async()=>{n.closed=!0,await Promise.all([...n.workers].map(r=>O(r,n)))},execute:(r,t)=>Xr(r,t,n),exploreTrail:(r,t)=>rn(r,t,n),routeSpan:(r,t)=>{let o=n.workers.find(i=>i.runId===r);o?.worker.connection.sendNotification(Ce,{runId:r,span:t})},setSize:r=>{on(r,n)}}}var Yr=1e4,ir=-32800;async function Xr(e,n,r){let t=await ar(r);if(t.runId=e.runId,!t.alive)return W(t,r),{outcome:{detail:"worker-exit",kind:"error"},serverNotified:!1};let o=new rr,i=()=>{o.cancel()};n.aborted&&o.cancel(),n.addEventListener("abort",i,{once:!0});try{let a=await t.worker.connection.sendRequest(be,e,o.token),s=Pe.safeParse(a);return s.success?s.data:{outcome:{detail:"bad-outcome-frame",kind:"error"},serverNotified:!1}}catch(a){return en(a,t)}finally{n.removeEventListener("abort",i),o.dispose(),W(t,r)}}async function ar(e){let n=e.workers.find(r=>r.alive&&r.runId===void 0);if(n!=null)return n.runId="pending",n;if(e.workers.length<e.size){let r=Zr(e);return r.runId="pending",await r.ready,r}return new Promise(r=>{e.waiters.push(t=>{t.runId="pending",r(t)})})}function Zr(e){let n=e.spawn(),r={resolve:()=>{}},t={alive:!0,id:e.nextId,ready:new Promise(o=>{r.resolve=o,n.connection.onNotification(he,o)}),runId:void 0,worker:n};return e.nextId+=1,n.connection.onRequest(Ee,async o=>{let i=ve.safeParse(o);return i.success?e.lockfileProvider(i.data.fingerprint):{unavailable:"bad-lockfile-request"}}),n.onExit(()=>{t.alive=!1,e.workers=e.workers.filter(o=>o!==t),n.connection.dispose(),r.resolve()}),e.workers.push(t),t}function W(e,n){if(e.runId=void 0,!e.alive||n.closed)return;if(n.workers.length>n.size){O(e,n);return}let r=n.waiters.shift();r?.(e)}async function O(e,n){if(n.workers=n.workers.filter(o=>o!==e),!e.alive)return;let r=new Promise(o=>{e.worker.onExit(o)});await e.worker.connection.sendNotification(Te).catch(()=>{});let t=setTimeout(()=>{e.worker.kill()},Yr);await r,clearTimeout(t)}function en(e,n){return e instanceof nr&&e.code===ir?{outcome:{detail:"aborted",kind:"error"},serverNotified:!1}:n.alive?{outcome:{detail:e instanceof Error?e.message:String(e),kind:"error"},serverNotified:!1}:{outcome:{detail:"worker-exit",kind:"error"},serverNotified:!1}}async function rn(e,n,r){let t=await ar(r);if(t.runId="explore",!t.alive)return W(t,r),{kind:"error",rows:[],trail:[]};let o=new rr,i=()=>{o.cancel()};n.aborted&&o.cancel(),n.addEventListener("abort",i,{once:!0});try{let a=await t.worker.connection.sendRequest(Ie,e,o.token),s=xe.safeParse(a);return s.success?s.data:{kind:"error",rows:[],trail:[]}}catch(a){return nn(a)}finally{n.removeEventListener("abort",i),o.dispose(),W(t,r)}}function nn(e){return e instanceof nr&&e.code===ir?{kind:"aborted",rows:[],trail:[]}:{kind:"error",reason:`transport:${e instanceof Error?e.message.slice(0,120):"unknown"}`,rows:[],trail:[]}}function on(e,n){n.size=e,e>er.availableParallelism()&&l.warn({cores:er.availableParallelism(),size:e},"worker pool size exceeds available cores");let r=n.workers.filter(o=>o.alive&&o.runId===void 0),t=n.workers.length-e;r.slice(0,Math.max(0,t)).forEach(o=>{O(o,n)})}import L from"fs";import{graphql as sr}from"gql.tada";import{print as lr}from"graphql";var tn=sr(`
23
23
  subscription HooksPausedWatch($projectId: String!) {
24
24
  hooksPausedRequested(projectId: $projectId) {
25
25
  paused
@@ -61,8 +61,8 @@ import{A as re,C as de,F as C,H as ce,I as ue,K as pe,L as fe,N as h,O as Re,b a
61
61
  }
62
62
  }
63
63
  }
64
- `);async function wt(e){let{executor:n,explore:r,exploreConcurrency:t}=e;Hn();let o=Wn(process.cwd()),i=o.cwd,a=bn(i),s=Tn(),d=Ve({config:o,executor:n,explore:r,exploreConcurrency:t,pool:s.pool}),c={handler:()=>{}},u=In(),f=Me(),m=he({config:o,cwd:i,executor:n,explorationStatus:d.status,ready:u.promise,replayFinding:Ze({config:o,cwd:i,pool:s.pool}),onShutdownRequest:()=>{c.handler()},sseHealth:()=>f.snapshot()}),S=await Cn({cwd:i,ipc:m,releasePid:a}),R=await A(i);R.isErr()&&(process.stderr.write(`ripplo: ${H(R.error)}
65
- `),process.exit(1));let b=R.value,p=await fr(()=>_({config:o,cwd:i,lockfile:b}),{config:o,label:"initial sync"}),g=await qn(o);j({cwd:i,paused:p.hooksPaused});let k=Pe(i);k!=null&&l.info("watching branch %s in %s",k,i);let v=ln({headers:{Authorization:`Bearer ${o.token}`},retry:Oe,retryAttempts:1/0,url:`${o.ripploServerUrl}/graphql`}),P=hn({cwd:i,initialConcurrency:g});s.provide(P);let mr=await Sn({cwd:i,pool:P}),M=Pn({claim:m.claim,config:o,cwd:i,initialId:p.devSessionId,pool:P,sseClient:v,sseHealth:f}),yr=cr({cwd:i,projectId:o.projectId,sseClient:v}),gr=dr({pool:P,sseClient:v}),kr=vn({config:o,session:M}),wr=An(),U=await kn({config:o,devSessionId:p.devSessionId,executor:n});u.resolve(),Rn({devSessionId:p.devSessionId,executor:n,tunnel:U});let Sr=async()=>{kr(),wr(),d.stop(),S.close(),M.stop(),yr(),gr(),await P.close(),await wn(U),await mr.stop().catch(()=>{}),await Promise.race([E({config:o,document:gn,variables:{cwd:o.cwd,projectId:o.projectId}}).catch(()=>{}),new Promise(Rr=>setTimeout(Rr,3e3))]),a()},x=()=>{Sr().finally(()=>process.exit(0))};c.handler=x,process.on("SIGINT",x),process.on("SIGTERM",x),process.on("SIGHUP",x),process.on("SIGBREAK",x),await new Promise(()=>{})}async function kn({config:e,devSessionId:n,executor:r}){if(r==="cloud")return _e({config:e,devSessionId:n})}async function wn(e){e!=null&&await e.stop()}function Sn({cwd:e,pool:n}){return te({localDir:sn.join(e,".ripplo",".local"),writePortFile:!0,onRrwebBatch:()=>{},onSpan:r=>{let t=r.attributes["ripplo.run"];t!=null&&n.routeSpan(t,r)}})}function Rn({devSessionId:e,executor:n,tunnel:r}){process.stdout.write(`${ke({devSessionId:e,executor:n,tunnelHostname:r?.hostname,version:Q()})}
64
+ `);async function wt(e){let{executor:n,explore:r,exploreConcurrency:t}=e;Hn();let o=Wn(process.cwd()),i=o.cwd,a=bn(i),s=Tn(),d=Ve({config:o,executor:n,explore:r,exploreConcurrency:t,pool:s.pool}),c={handler:()=>{}},u=In(),f=Me(),m=He({config:o,cwd:i,executor:n,explorationStatus:d.status,ready:u.promise,replayFinding:Ze({config:o,cwd:i,pool:s.pool}),onShutdownRequest:()=>{c.handler()},sseHealth:()=>f.snapshot()}),S=await Cn({cwd:i,ipc:m,releasePid:a}),R=await A(i);R.isErr()&&(process.stderr.write(`ripplo: ${H(R.error)}
65
+ `),process.exit(1));let b=R.value,p=await fr(()=>_({config:o,cwd:i,lockfile:b}),{config:o,label:"initial sync"}),g=await qn(o);j({cwd:i,paused:p.hooksPaused});let k=De(i);k!=null&&l.info("watching branch %s in %s",k,i);let v=ln({headers:{Authorization:`Bearer ${o.token}`},retry:Oe,retryAttempts:1/0,url:`${o.ripploServerUrl}/graphql`}),P=hn({cwd:i,initialConcurrency:g});s.provide(P);let mr=await Sn({cwd:i,pool:P}),M=Pn({claim:m.claim,config:o,cwd:i,initialId:p.devSessionId,pool:P,sseClient:v,sseHealth:f}),yr=cr({cwd:i,projectId:o.projectId,sseClient:v}),gr=dr({pool:P,sseClient:v}),kr=vn({config:o,session:M}),wr=An(),U=await kn({config:o,devSessionId:p.devSessionId,executor:n});u.resolve(),Rn({devSessionId:p.devSessionId,executor:n,tunnel:U});let Sr=async()=>{kr(),wr(),d.stop(),S.close(),M.stop(),yr(),gr(),await P.close(),await wn(U),await mr.stop().catch(()=>{}),await Promise.race([E({config:o,document:gn,variables:{cwd:o.cwd,projectId:o.projectId}}).catch(()=>{}),new Promise(Rr=>setTimeout(Rr,3e3))]),a()},x=()=>{Sr().finally(()=>process.exit(0))};c.handler=x,process.on("SIGINT",x),process.on("SIGTERM",x),process.on("SIGHUP",x),process.on("SIGBREAK",x),await new Promise(()=>{})}async function kn({config:e,devSessionId:n,executor:r}){if(r==="cloud")return _e({config:e,devSessionId:n})}async function wn(e){e!=null&&await e.stop()}function Sn({cwd:e,pool:n}){return le({localDir:sn.join(e,".ripplo",".local"),writePortFile:!0,onRrwebBatch:()=>{},onSpan:r=>{let t=r.attributes["ripplo.run"];t!=null&&n.routeSpan(t,r)}})}function Rn({devSessionId:e,executor:n,tunnel:r}){process.stdout.write(`${ke({devSessionId:e,executor:n,tunnelHostname:r?.hostname,version:Q()})}
66
66
  `)}function vn({config:e,session:n}){let r=async()=>{try{let o=await E({config:e,document:mn,variables:{id:n.getId()}});o.heartbeatDevSession?.__typename==="DevSessionEndedError"&&(l.warn("dev session ended server-side (%s); re-registering",o.heartbeatDevSession.message),await n.reregister())}catch(o){l.warn("heartbeat failed: %s",o instanceof Error?o.message:String(o))}},t=setInterval(()=>{r()},pn);return()=>{clearInterval(t)}}function Pn({claim:e,config:n,cwd:r,initialId:t,pool:o,sseClient:i,sseHealth:a}){let s=t,d=null,c=0,u=0,f=p=>{b(p)},m=()=>{let p=je({claim:e,cwd:r,devSessionId:s,onSubscriptionDead:f,pool:o,sseClient:i,sseHealth:a}),g=Ne({cwd:r,devSessionId:s,onSubscriptionDead:f,sseClient:i});return()=>{p(),g()}},S=m(),R=async p=>{let g=Date.now();c=g-u>pr*2?1:c+1,u=g,c>=fn&&a.markUnhealthy(p),await xn(Math.min(1e3*2**(c-1),pr));let k=await A(r);if(k.isErr()){l.error("re-register: compile failed: %s",H(k.error));return}let v=await fr(()=>_({config:n,cwd:r,lockfile:k.value}),{config:n,label:"re-register sync"});S(),s=v.devSessionId,S=m(),l.info("re-registered dev session as %s",s)},b=p=>(d!=null||(d=R(p).finally(()=>{d=null})),d);return{getId:()=>s,reregister:()=>b("dev session ended server-side"),stop:()=>{S()}}}function xn(e){return new Promise(n=>setTimeout(n,e))}function hn({cwd:e,initialConcurrency:n}){return tr({initialSize:n,spawn:or(_n(),e),lockfileProvider:async r=>{let t=await y(e);return t.result.isErr()?{unavailable:`daemon-load-failed:${t.result.error.kind}`}:t.fingerprint!==r?{unavailable:"fingerprint-mismatch"}:{lockfileJson:J(K,t.result.value)}}})}function bn(e){try{return ee({cwd:e,onCompromised:En})}catch(n){throw n instanceof Z&&(process.stderr.write(`${we({cwd:n.cwd,pid:n.pid})}
67
67
  `),process.exit(1)),n}}function En(){l.error("daemon lock compromised; shutting down"),process.stderr.write(`${Se()}
68
68
  `),process.exit(1)}function In(){let e={handler:()=>{}};return{promise:new Promise(r=>{e.handler=r}),resolve:()=>{e.handler()}}}async function Cn({cwd:e,ipc:n,releasePid:r}){return(await Je({cwd:e,onConnection:n.handleConnection})).match(o=>o,o=>{process.stderr.write(`${me(o)}
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{aa as a,ba as b,ca as c,da as d,ea as e,fa as f,ga as g,ha as h,ia as i,ja as j,ka as k}from"./chunk-R7CQWEBF.js";import"./chunk-IHSHBPJY.js";import"./chunk-7UWDMECF.js";export{b as renderDaemonBindError,g as renderDaemonLockCompromised,f as renderDaemonLockHeld,e as renderDaemonReady,h as renderDaemonStatus,j as renderDaemonVersionNote,i as renderExplorationProgress,d as renderNotSignedIn,c as renderProjectAccessDenied,a as renderTunnelProvisionFailed,k as wireOutcome};
2
+ import{aa as a,ba as b,ca as c,da as d,ea as e,fa as f,ga as g,ha as h,ia as i,ja as j,ka as k}from"./chunk-NXJP3Q4N.js";import"./chunk-IHSHBPJY.js";import"./chunk-7UWDMECF.js";export{b as renderDaemonBindError,g as renderDaemonLockCompromised,f as renderDaemonLockHeld,e as renderDaemonReady,h as renderDaemonStatus,j as renderDaemonVersionNote,i as renderExplorationProgress,d as renderNotSignedIn,c as renderProjectAccessDenied,a as renderTunnelProvisionFailed,k as wireOutcome};
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{$ as Tn,A as oe,B as Hr,C as ie,D as b,E as un,G as mn,H as fn,I as gn,J as hn,L as yn,M as kn,N as er,O as V,P as rr,Q as Sn,R as xn,S as Rn,T as Cn,U as Pn,V as En,W as $n,X as In,Y as jn,Z as An,_ as Se,a as $r,aa as Ln,b as B,c as Ir,d as jr,e as P,f as ye,g as L,ga as ae,h as Tr,ha as Dn,i as x,ia as nr,j as c,k as Lr,ka as On,l as v,m as Dr,ma as _n,n as Or,o as _r,oa as Fn,p as Fr,q as E,qa as Nn,r as I,ra as Un,s as Nr,sa as Mn,t as w,ta as Hn,u as ke,ua as Wn,va as qn,x as _,xa as Bn,y as Ur,ya as Vn,z as Mr}from"./chunk-MOJRP3IN.js";import{$ as pn,A as Zr,B as en,C as rn,D as nn,E as tn,F as on,I as ve,N as sn,P as Ye,Q as Ze,R as an,S as ln,T as be,Y as dn,_ as cn,c as J,d as Ar,g as N,ha as wn,ia as se,ja as vn,k as O,ka as bn,l as Wr,m as qr,n as Xe,o as Br,p as K,q as Vr,r as Gr,s as we,t as C,u as zr,v as Jr,w as Kr,x as Qr,y as Xr,z as Yr}from"./chunk-R7CQWEBF.js";import{a as wr,b as vr,c as br,d as m,e as $,g as he,h as Sr,k as D,l as xr,m as Rr,n as Qe,o as Cr,p as Pr,q as Er}from"./chunk-EVYYSTR7.js";import"./chunk-IHSHBPJY.js";import"./chunk-7UWDMECF.js";import Ac from"update-notifier";import Tc from"yargs";import{hideBin as Lc}from"yargs/helpers";function xe({current:e,latest:r}){return r==null?`ripplo v${e} (latest: unknown)`:r===e?`ripplo v${e} (latest)`:`ripplo v${e} (latest: v${r} \u2014 run \`npx ripplo update\`)`}function Gn(){return"Update available {currentVersion} \u2192 {latestVersion}\nRun `npx ripplo update`"}import{graphql as Wi}from"gql.tada";import{exec as Ai}from"child_process";import{createAuthClient as Ii}from"better-auth/client";import{deviceAuthorizationClient as ji}from"better-auth/client/plugins";function zn({baseURL:e}){return Ii({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[ji()]})}import{err as Jn,ok as Ti}from"neverthrow";var Li=5e3,Kn="ripplo-cli";async function Qn({onDeviceCode:e,url:r}){let n=r??Nr().RIPPLO_SERVER_URL,t=zn({baseURL:n}),o=await t.device.code({client_id:Kn});if(o.error!=null)return Jn({description:o.error.error_description,kind:"oauth-device-code-failed"});let{device_code:i,user_code:a,verification_uri_complete:s}=o.data;return e({userCode:a,verificationUrl:s}),Ui(s),(await Di({authClient:t,deviceCode:i})).map(d=>(xr({serverUrl:n,token:d}),d))}async function Di({authClient:e,deviceCode:r}){for(;;){await Fi(Li);let n=await e.device.token({client_id:Kn,device_code:r,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(n.data?.access_token!=null)return Ti(n.data.access_token);if(n.error==null)continue;if(!_i(n.error.error))return Jn({code:n.error.error,description:n.error.error_description,kind:"oauth-authorization-failed"})}}var Oi=new Set(["authorization_pending","slow_down"]);function _i(e){return Oi.has(e)}function Fi(e){return new Promise(r=>{setTimeout(r,e)})}function Ni(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function Ui(e){let r=Ni();Ai(`${r} "${e}"`,()=>{})}function U({serverUrl:e,token:r}){return{appUrl:"",cwd:process.cwd(),engineUrl:"",projectId:"",ripploServerUrl:e,token:r,tunnelAuth:void 0,webhookSecret:""}}import Mi from"fs";import Hi from"path";function f(e){return Mi.existsSync(Hi.join(e,".ripplo"))}var qi=Wi(`
2
+ import{$ as Tn,A as oe,B as Hr,C as ie,D as b,E as un,G as mn,H as fn,I as gn,J as hn,L as yn,M as kn,N as er,O as V,P as rr,Q as Sn,R as xn,S as Rn,T as Cn,U as Pn,V as En,W as $n,X as In,Y as jn,Z as An,_ as Se,a as $r,aa as Ln,b as B,ba as Dn,c as Ir,d as jr,da as On,e as P,f as ye,fa as _n,g as L,h as Tr,ha as Fn,i as x,ia as Nn,j as c,ja as Un,k as Lr,ka as Mn,l as v,la as Hn,m as Dr,ma as Wn,n as Or,o as _r,oa as qn,p as Fr,q as E,r as I,s as Nr,t as w,u as ke,ua as ae,va as Bn,wa as nr,x as _,y as Ur,ya as Vn,z as Mr}from"./chunk-D7HOYI2S.js";import{$ as pn,D as Yr,F as Ye,G as Ze,H as Zr,I as en,J as ve,O as rn,Q as nn,R as tn,S as on,T as sn,U as an,V as ln,W as dn,X as cn,_ as be,c as J,d as Ar,g as N,ha as wn,ia as se,ja as vn,k as O,ka as bn,l as Wr,m as qr,n as Xe,o as Br,p as K,q as Vr,r as Gr,s as we,t as C,u as zr,v as Jr,w as Kr,x as Qr,y as Xr}from"./chunk-NXJP3Q4N.js";import{a as wr,b as vr,c as br,d as m,e as $,g as he,h as Sr,k as D,l as xr,m as Rr,n as Qe,o as Cr,p as Pr,q as Er}from"./chunk-EVYYSTR7.js";import"./chunk-IHSHBPJY.js";import"./chunk-7UWDMECF.js";import Ac from"update-notifier";import Tc from"yargs";import{hideBin as Lc}from"yargs/helpers";function xe({current:e,latest:r}){return r==null?`ripplo v${e} (latest: unknown)`:r===e?`ripplo v${e} (latest)`:`ripplo v${e} (latest: v${r} \u2014 run \`npx ripplo update\`)`}function Gn(){return"Update available {currentVersion} \u2192 {latestVersion}\nRun `npx ripplo update`"}import{graphql as Wi}from"gql.tada";import{exec as Ai}from"child_process";import{createAuthClient as Ii}from"better-auth/client";import{deviceAuthorizationClient as ji}from"better-auth/client/plugins";function zn({baseURL:e}){return Ii({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[ji()]})}import{err as Jn,ok as Ti}from"neverthrow";var Li=5e3,Kn="ripplo-cli";async function Qn({onDeviceCode:e,url:r}){let n=r??Nr().RIPPLO_SERVER_URL,t=zn({baseURL:n}),o=await t.device.code({client_id:Kn});if(o.error!=null)return Jn({description:o.error.error_description,kind:"oauth-device-code-failed"});let{device_code:i,user_code:a,verification_uri_complete:s}=o.data;return e({userCode:a,verificationUrl:s}),Ui(s),(await Di({authClient:t,deviceCode:i})).map(d=>(xr({serverUrl:n,token:d}),d))}async function Di({authClient:e,deviceCode:r}){for(;;){await Fi(Li);let n=await e.device.token({client_id:Kn,device_code:r,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(n.data?.access_token!=null)return Ti(n.data.access_token);if(n.error==null)continue;if(!_i(n.error.error))return Jn({code:n.error.error,description:n.error.error_description,kind:"oauth-authorization-failed"})}}var Oi=new Set(["authorization_pending","slow_down"]);function _i(e){return Oi.has(e)}function Fi(e){return new Promise(r=>{setTimeout(r,e)})}function Ni(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function Ui(e){let r=Ni();Ai(`${r} "${e}"`,()=>{})}function U({serverUrl:e,token:r}){return{appUrl:"",cwd:process.cwd(),engineUrl:"",projectId:"",ripploServerUrl:e,token:r,tunnelAuth:void 0,webhookSecret:""}}import Mi from"fs";import Hi from"path";function f(e){return Mi.existsSync(Hi.join(e,".ripplo"))}var qi=Wi(`
3
3
  query AuthViewer {
4
4
  currentUser {
5
5
  name
@@ -81,7 +81,7 @@ ${r.join(`
81
81
  ${r.join(`
82
82
  `)}`}function cs(e){let r=e.errors.map(n=>" - "+(n.path===""?"":n.path+": ")+n.message);return" "+e.name+`:
83
83
  `+r.join(`
84
- `)}function ps(e){return e.installed?"\u2713 Browser: Chromium installed":"\u2717 Browser: Chromium not installed. Run `npx playwright install chromium`."}function us(e){switch(e.status){case"enabled":return`\u2713 Adapter: enabled at ${e.url}`;case"disabled":return"\u2717 Adapter: disabled (handler returned 404). Set ENABLE_RIPPLO_TESTING=true in your app's env (e.g. apps/<app>/.env.local for Next.js) and restart the dev server.";case"bad-secret":return"\u2717 Adapter: webhook signature rejected. RIPPLO_WEBHOOK_SECRET seen by your dev server does not match the one in your env file.";case"unreachable":return`\u2717 Adapter: ${e.url} could not be reached for signed probe.`;case"no-secret":return"\u2717 Adapter: RIPPLO_WEBHOOK_SECRET is not set in your env file (declared in .ripplo/project.json)."}}function ms(e){switch(e.status){case"match":return`\u2713 Lockfile: ${L} is up to date`;case"missing":return`\u2717 Lockfile: ${L} is missing \u2014 run \`npx ripplo compile\` and commit it`;case"stale":return`\u2717 Lockfile: ${L} is out of date \u2014 run \`npx ripplo compile\` and commit it`}}function fs(e){return e.installed?"\u2713 Pre-commit hook: .git/hooks/pre-commit runs `ripplo compile --check`":"! Pre-commit hook: .git/hooks/pre-commit does not run `ripplo compile --check` \u2014 see the setup skill for the snippet"}function gs(e){return e.installed===e.cliVersion?`\u2713 Claude plugin: v${e.installed} matches CLI`:`! Claude plugin: installed v${e.installed}, CLI v${e.cliVersion} \u2014 run \`npx ripplo update\` (or /plugin in Claude Code)`}import Q from"fs";import bs from"os";import ir from"path";import{z as W}from"zod";import hs from"latest-version";import st from"semver";async function Re(e){try{return await Promise.race([hs("ripplo"),new Promise(r=>setTimeout(()=>{r(void 0)},e))])}catch{return}}function at(e){return e.filter(r=>st.valid(r)!=null).toSorted(st.rcompare)[0]}function M(e){let r=K(e),n=on(e);return[...Gr(r,n.dataRules).map(t=>({gap:t,kind:"cascade-gap"})),...Vr(n.pageRules).map(t=>({conflict:t,kind:"page-rule-conflict"}))]}function S(e,r,n){let t=e===1?r:n??`${r}s`;return`${String(e)} ${t}`}function lt(e){return`${C.good("ok")} \u2014 no static model violations (${S(e,"test")})`}function H(e){return[`${C.bad("fail")} \u2014 ${S(e.length,"static model violation")}:`,...e.map(r=>Kr(r))].join(`
84
+ `)}function ps(e){return e.installed?"\u2713 Browser: Chromium installed":"\u2717 Browser: Chromium not installed. Run `npx playwright install chromium`."}function us(e){switch(e.status){case"enabled":return`\u2713 Adapter: enabled at ${e.url}`;case"disabled":return"\u2717 Adapter: disabled (handler returned 404). Set ENABLE_RIPPLO_TESTING=true in your app's env (e.g. apps/<app>/.env.local for Next.js) and restart the dev server.";case"bad-secret":return"\u2717 Adapter: webhook signature rejected. RIPPLO_WEBHOOK_SECRET seen by your dev server does not match the one in your env file.";case"unreachable":return`\u2717 Adapter: ${e.url} could not be reached for signed probe.`;case"no-secret":return"\u2717 Adapter: RIPPLO_WEBHOOK_SECRET is not set in your env file (declared in .ripplo/project.json)."}}function ms(e){switch(e.status){case"match":return`\u2713 Lockfile: ${L} is up to date`;case"missing":return`\u2717 Lockfile: ${L} is missing \u2014 run \`npx ripplo compile\` and commit it`;case"stale":return`\u2717 Lockfile: ${L} is out of date \u2014 run \`npx ripplo compile\` and commit it`}}function fs(e){return e.installed?"\u2713 Pre-commit hook: .git/hooks/pre-commit runs `ripplo compile --check`":"! Pre-commit hook: .git/hooks/pre-commit does not run `ripplo compile --check` \u2014 see the setup skill for the snippet"}function gs(e){return e.installed===e.cliVersion?`\u2713 Claude plugin: v${e.installed} matches CLI`:`! Claude plugin: installed v${e.installed}, CLI v${e.cliVersion} \u2014 run \`npx ripplo update\` (or /plugin in Claude Code)`}import Q from"fs";import bs from"os";import ir from"path";import{z as W}from"zod";import hs from"latest-version";import st from"semver";async function Re(e){try{return await Promise.race([hs("ripplo"),new Promise(r=>setTimeout(()=>{r(void 0)},e))])}catch{return}}function at(e){return e.filter(r=>st.valid(r)!=null).toSorted(st.rcompare)[0]}function M(e){let r=K(e),n=Xr(e);return[...Gr(r,n.dataRules).map(t=>({gap:t,kind:"cascade-gap"})),...Vr(n.pageRules).map(t=>({conflict:t,kind:"page-rule-conflict"}))]}function S(e,r,n){let t=e===1?r:n??`${r}s`;return`${String(e)} ${t}`}function lt(e){return`${C.good("ok")} \u2014 no static model violations (${S(e,"test")})`}function H(e){return[`${C.bad("fail")} \u2014 ${S(e.length,"static model violation")}:`,...e.map(r=>Kr(r))].join(`
85
85
 
86
86
  `)}function Ce(e){return[`${C.warn("warn")} \u2014 ${S(e.length,"model coverage gap")} (not blocking):`,...e.map(r=>` ${ys(r)}`),`Coverage gaps mean the model can't catch regressions there. Stub the missing flows. ${c("discover")}`].join(`
87
87
  `)}function ys(e){switch(e.kind){case"entity-never-given":return`${e.entity} \u2014 declared but no implemented test seeds it, so no flow exercises this state`;case"entity-never-mutated":return`${e.entity} \u2014 seeded but never asserted created/updated/deleted, so mutations to it ship unchecked`;case"unmatchable-delete-key":return`test ${e.workflow}/${e.testSlug} asserts ${e.entity} deleted with a literal ${e.field} but no seeded ${e.entity} sets ${e.field} \u2014 the solver treats it as no-match, so counts may mispredict`}}import{graphql as ks}from"gql.tada";var ws=ks(`
@@ -111,7 +111,7 @@ ${r.join(`
111
111
  `)?"":`
112
112
  `;k.writeFileSync(r,n+o+t.join(`
113
113
  `)+`
114
- `)}var aa=`import { createRipplo } from "@ripplo/testing";
114
+ `)}var aa=`import { createRipplo, entity, field, id, v, singleton, arbitrary } from "@ripplo/testing";
115
115
  import { entities } from "./entities/index";
116
116
  import { singletons } from "./singletons/index";
117
117
  import { workflows } from "./workflows/index";
@@ -125,8 +125,7 @@ export default createRipplo({
125
125
  // app's engine funnel (createEngine). See /ripplo:create "Adding an entity".
126
126
  //
127
127
  // Example:
128
- // import { entity, field, id, v } from "@ripplo/testing";
129
- //
128
+ // //
130
129
  // export const Project = entity("project", {
131
130
  // description: "A project owned by a user",
132
131
  // fields: { name: field({ value: v.slug() }), ownerId: field({ value: v.id() }) },
@@ -140,8 +139,7 @@ export const entities = [] as const;
140
139
  `,da=`// Client/global state (e.g. localStorage flags) modeled as singletons.
141
140
  //
142
141
  // Example:
143
- // import { singleton, v } from "@ripplo/testing";
144
- //
142
+ // //
145
143
  // export const onboardingDismissed = singleton("onboardingDismissed", {
146
144
  // default: false,
147
145
  // description: "user dismissed the onboarding flow",
@@ -156,8 +154,7 @@ export const singletons = [];
156
154
  // for workflows. Compose from other worlds. See /ripplo:create "Adding a world".
157
155
  //
158
156
  // Example:
159
- // import { arbitrary } from "@ripplo/testing";
160
- // import { Project, User } from "../entities/index";
157
+ // // import { Project, User } from "../entities/index";
161
158
  //
162
159
  // export const ownedProject = () => {
163
160
  // const me = User.of({ email: arbitrary(User.field.email) });
@@ -218,7 +215,7 @@ export const workflows = [] as const;
218
215
  `);let i=n.workflows.reduce((a,s)=>a+s.tests.length,0);process.stdout.write(`${lt(i)}
219
216
  `);return}process.stderr.write(`${H(t)}
220
217
  `),process.exit(1)}function Oa(e){process.stderr.write(`${e}
221
- `),process.exit(1)}import{CancellationTokenSource as kl}from"vscode-jsonrpc/node";import Ba from"path";import{randomUUID as _a}from"crypto";import Et from"path";async function Ae({assign:e,config:r,headed:n,lockfile:t,report:o,session:i,signal:a}){let s=K(t),l=await Wa({assign:e,corpus:s,lockfile:t});if(l==null)return{kind:"error",reason:"base-state-arrange-failed",rows:[],trail:[]};if(l.stepRuns.length===0)return{kind:"error",reason:"empty-trail",rows:[],trail:[]};let d=Ha(s,l.stepRuns),y=await pn({baseState:{name:Xe(e.baseStateTest),test:e.baseStateTest},corpus:s,lensId:e.lensId,lockfile:t,lockfileHash:e.lockfileHash,options:{baseUrl:r.appUrl,engineUrl:r.engineUrl,fixturesDir:Et.join(r.cwd,ye),generate:we,headed:n,secret:r.webhookSecret,tunnelAuth:void 0},session:i,shrinkBudget:e.shrinkBudget,trail:l,now:()=>new Date().toISOString(),runIdFor:p=>`explore-${_a()}-${String(p)}`},a);return o&&await Na({baseStateTest:e.baseStateTest,config:r,lockfileHash:e.lockfileHash,outcome:y,stepSources:Fa(d)}),qa(y,d)}function Fa(e){return e.flatMap(r=>{let n=r.label.split("#")[0]??r.label;return r.actions.map(()=>n)})}async function Na({baseStateTest:e,config:r,lockfileHash:n,outcome:t,stepSources:o}){t.kind!=="finding"||t.captureEvents.length===0||await mn({baseState:t.finding.baseState,branchName:void 0,category:Ua(t.finding.verifierLayer),commitSha:void 0,config:r,debugDir:Et.join(r.cwd,".ripplo","debug"),devSessionId:void 0,events:t.captureEvents,lockfileHash:n,runId:t.runId,signature:t.finding.signature,spec:Ma(e,t.captureEvents),stepSources:o}).catch(i=>{$.warn({error:i},"explore finding report failed")})}function Ua(e){return e==="data-rule"?"dataRule":e==="page-rule"?"pageRule":e}function Ma(e,r){let n=r.flatMap(o=>o.kind==="step"?[o.source]:[]),t={absent:e.absent,exclusive:e.exclusive,intent:e.intent,maybe:[],name:e.name,params:e.params,singletons:e.singletons,steps:n,stub:!1,tests:[],world:e.world};return JSON.stringify(t)}function Ha(e,r){return r.flatMap(n=>{let t=e[n.idx];return t==null?[]:[{actions:[...t.nav,...t.steps].map(o=>Qr(o.action)),label:`${t.test}#${String(t.index)}`}]})}async function Wa({assign:e,corpus:r,lockfile:n}){return(await cn(n,{name:Xe(e.baseStateTest),test:e.baseStateTest},{generate:we,materialize:qr(we,n.valueSpaces),params:void 0})).match(o=>dn({actionHashes:r.map(i=>be(n,i)),baseStateSnapshot:o.snapshot,corpus:r,covered:new Set,lens:hn(n),lensId:e.lensId,maxLength:e.maxLength,witnessTrail:e.stepRuns}),()=>null)}function qa(e,r){return e.kind==="error"?{kind:e.kind,reason:`runtime:${e.error.kind}`,rows:[],trail:[]}:e.kind==="aborted"?{kind:e.kind,rows:[],trail:[]}:{kind:e.kind,rows:[...e.rows],trail:[...r]}}var Va={covered:0,deferred:0,findings:0,saturated:!1,total:0};async function $t(e){let r=w(e.cwd).match(d=>({config:d}),d=>({failure:d}));if("failure"in r)return{failure:r.failure,kind:"config-failed"};let n=r.config,t=await Tr(e.cwd);if(t.result.isErr())return{error:t.result.error,kind:"compile-failed"};let o={fingerprint:t.fingerprint,lockfile:t.result.value},i=gn({onChange:()=>{}});if(!i.holder())return i.stop(),{kind:"explorer-busy"};let a=ve({clientVersion:P(),debugDir:Ba.join(e.cwd,".ripplo","debug"),headed:e.headed,writeOtlpPortFile:!1}),s={executed:0,status:()=>Va},l=yn({cwd:e.cwd,maxTrailLength:e.maxLength,executeTrail:(d,y)=>Ae({assign:d,config:n,headed:e.headed,lockfile:o.lockfile,report:!0,session:a,signal:y}),loadLockfile:()=>Promise.resolve(o),notifyWork:()=>{},onTrailDone:d=>{s.executed+=1,e.onTrail(s.executed,d,s.status())},probeApp:async()=>await oe(n.appUrl)==null});s.status=()=>l.status();try{await l.ready(),e.onReady(l.status());let d=await Ga(l,e),y=l.status();return await l.stop(),await a.close(),d===0&&y.total===0?{kind:"no-work"}:{executed:d,kind:"completed",progress:y}}finally{i.stop()}}async function Ga(e,r){let n=new AbortController().signal,t=async o=>{if(o>=r.trails)return o;let i=e.next();return i==null?o:(await i.run(n),t(o+1))};return t(0)}import{okAsync as za}from"neverthrow";function It(e,r,n){let t=er(e);return Ze(t).andThen(o=>{let i=Ja(o,r);return i==null?za({id:r,kind:"not-found"}):an(t,[{at:n,kind:"resolution",signature:i}]).map(()=>({id:r,kind:"dismissed",signature:i}))})}function jt(e,r){let n=er(e);return Ze(n).andThen(t=>{let o=new Set(K(r).map(d=>be(r,d))),i=new Set(Ka(t).filter(d=>!o.has(d.actionHash)).map(d=>d.signature)),a=Qa(t),s=t.filter(d=>Xa(d,o,a)),l=[...a].filter(d=>!i.has(d)).length;return ln(n,s).map(()=>({kept:s.length,removedResolved:l,removedStale:i.size}))})}function Ja(e,r){return[...Ye(e).findings.entries()].find(([t,o])=>o.resolvedAt==null&&sn(t)===r)?.[0]}function Ka(e){return e.filter(r=>r.kind==="finding")}function Qa(e){return new Set([...Ye(e).findings.entries()].filter(([,r])=>r.resolvedAt!=null).map(([r])=>r))}function Xa(e,r,n){return e.kind==="resolution"?!1:e.kind==="finding"?r.has(e.actionHash)&&!n.has(e.signature):!0}function At(e){if(e.pending.length===0&&e.recurrentFlaky.length===0)return"explore: no pending findings";let r=e.pending.length===0?[]:[`explore: ${S(e.pending.length,"pending finding")} \u2014 triage in layer order, top first`,...e.pending.map(o=>Ya(o))],n=e.recurrentFlaky.length===0?[]:[`recurrent flaky-candidates (same failure ${String(e.recurrentFlaky.length)}x, no deterministic repro \u2014 triage after findings):`,...e.recurrentFlaky.map(o=>rl(o))],t=c("fuzz","triaging each finding (evidence -> classify -> fix -> replay)");return[...r,...n,"","replay after a fix: npx ripplo explore replay <id>",t].join(`
218
+ `),process.exit(1)}import{CancellationTokenSource as kl}from"vscode-jsonrpc/node";import Ba from"path";import{randomUUID as _a}from"crypto";import Et from"path";async function Ae({assign:e,config:r,headed:n,lockfile:t,report:o,session:i,signal:a}){let s=K(t),l=await Wa({assign:e,corpus:s,lockfile:t});if(l==null)return{kind:"error",reason:"base-state-arrange-failed",rows:[],trail:[]};if(l.stepRuns.length===0)return{kind:"error",reason:"empty-trail",rows:[],trail:[]};let d=Ha(s,l.stepRuns),y=await pn({baseState:{name:Xe(e.baseStateTest),test:e.baseStateTest},corpus:s,lensId:e.lensId,lockfile:t,lockfileHash:e.lockfileHash,options:{baseUrl:r.appUrl,engineUrl:r.engineUrl,fixturesDir:Et.join(r.cwd,ye),generate:we,headed:n,secret:r.webhookSecret,tunnelAuth:void 0},session:i,shrinkBudget:e.shrinkBudget,trail:l,now:()=>new Date().toISOString(),runIdFor:p=>`explore-${_a()}-${String(p)}`},a);return o&&await Na({baseStateTest:e.baseStateTest,config:r,lockfileHash:e.lockfileHash,outcome:y,stepSources:Fa(d)}),qa(y,d)}function Fa(e){return e.flatMap(r=>{let n=r.label.split("#")[0]??r.label;return r.actions.map(()=>n)})}async function Na({baseStateTest:e,config:r,lockfileHash:n,outcome:t,stepSources:o}){t.kind!=="finding"||t.captureEvents.length===0||await mn({baseState:t.finding.baseState,branchName:void 0,category:Ua(t.finding.verifierLayer),commitSha:void 0,config:r,debugDir:Et.join(r.cwd,".ripplo","debug"),devSessionId:void 0,events:t.captureEvents,lockfileHash:n,runId:t.runId,signature:t.finding.signature,spec:Ma(e,t.captureEvents),stepSources:o}).catch(i=>{$.warn({error:i},"explore finding report failed")})}function Ua(e){return e==="data-rule"?"dataRule":e==="page-rule"?"pageRule":e}function Ma(e,r){let n=r.flatMap(o=>o.kind==="step"?[o.source]:[]),t={absent:e.absent,exclusive:e.exclusive,intent:e.intent,maybe:[],name:e.name,params:e.params,singletons:e.singletons,steps:n,stub:!1,tests:[],world:e.world};return JSON.stringify(t)}function Ha(e,r){return r.flatMap(n=>{let t=e[n.idx];return t==null?[]:[{actions:[...t.nav,...t.steps].map(o=>Qr(o.action)),label:`${t.test}#${String(t.index)}`}]})}async function Wa({assign:e,corpus:r,lockfile:n}){return(await nn(n,{name:Xe(e.baseStateTest),test:e.baseStateTest},{generate:we,materialize:qr(we,n.valueSpaces),params:void 0})).match(o=>rn({actionHashes:r.map(i=>ve(n,i)),baseStateSnapshot:o.snapshot,corpus:r,covered:new Set,lens:hn(n),lensId:e.lensId,maxLength:e.maxLength,witnessTrail:e.stepRuns}),()=>null)}function qa(e,r){return e.kind==="error"?{kind:e.kind,reason:`runtime:${e.error.kind}`,rows:[],trail:[]}:e.kind==="aborted"?{kind:e.kind,rows:[],trail:[]}:{kind:e.kind,rows:[...e.rows],trail:[...r]}}var Va={covered:0,deferred:0,findings:0,saturated:!1,total:0};async function $t(e){let r=w(e.cwd).match(d=>({config:d}),d=>({failure:d}));if("failure"in r)return{failure:r.failure,kind:"config-failed"};let n=r.config,t=await Tr(e.cwd);if(t.result.isErr())return{error:t.result.error,kind:"compile-failed"};let o={fingerprint:t.fingerprint,lockfile:t.result.value},i=gn({onChange:()=>{}});if(!i.holder())return i.stop(),{kind:"explorer-busy"};let a=be({clientVersion:P(),debugDir:Ba.join(e.cwd,".ripplo","debug"),headed:e.headed,writeOtlpPortFile:!1}),s={executed:0,status:()=>Va},l=yn({cwd:e.cwd,maxTrailLength:e.maxLength,executeTrail:(d,y)=>Ae({assign:d,config:n,headed:e.headed,lockfile:o.lockfile,report:!0,session:a,signal:y}),loadLockfile:()=>Promise.resolve(o),notifyWork:()=>{},onTrailDone:d=>{s.executed+=1,e.onTrail(s.executed,d,s.status())},probeApp:async()=>await oe(n.appUrl)==null});s.status=()=>l.status();try{await l.ready(),e.onReady(l.status());let d=await Ga(l,e),y=l.status();return await l.stop(),await a.close(),d===0&&y.total===0?{kind:"no-work"}:{executed:d,kind:"completed",progress:y}}finally{i.stop()}}async function Ga(e,r){let n=new AbortController().signal,t=async o=>{if(o>=r.trails)return o;let i=e.next();return i==null?o:(await i.run(n),t(o+1))};return t(0)}import{okAsync as za}from"neverthrow";function It(e,r,n){let t=er(e);return Ze(t).andThen(o=>{let i=Ja(o,r);return i==null?za({id:r,kind:"not-found"}):Zr(t,[{at:n,kind:"resolution",signature:i}]).map(()=>({id:r,kind:"dismissed",signature:i}))})}function jt(e,r){let n=er(e);return Ze(n).andThen(t=>{let o=new Set(K(r).map(d=>ve(r,d))),i=new Set(Ka(t).filter(d=>!o.has(d.actionHash)).map(d=>d.signature)),a=Qa(t),s=t.filter(d=>Xa(d,o,a)),l=[...a].filter(d=>!i.has(d)).length;return en(n,s).map(()=>({kept:s.length,removedResolved:l,removedStale:i.size}))})}function Ja(e,r){return[...Ye(e).findings.entries()].find(([t,o])=>o.resolvedAt==null&&Yr(t)===r)?.[0]}function Ka(e){return e.filter(r=>r.kind==="finding")}function Qa(e){return new Set([...Ye(e).findings.entries()].filter(([,r])=>r.resolvedAt!=null).map(([r])=>r))}function Xa(e,r,n){return e.kind==="resolution"?!1:e.kind==="finding"?r.has(e.actionHash)&&!n.has(e.signature):!0}function At(e){if(e.pending.length===0&&e.recurrentFlaky.length===0)return"explore: no pending findings";let r=e.pending.length===0?[]:[`explore: ${S(e.pending.length,"pending finding")} \u2014 triage in layer order, top first`,...e.pending.map(o=>Ya(o))],n=e.recurrentFlaky.length===0?[]:[`recurrent flaky-candidates (same failure ${String(e.recurrentFlaky.length)}x, no deterministic repro \u2014 triage after findings):`,...e.recurrentFlaky.map(o=>rl(o))],t=c("fuzz","triaging each finding (evidence -> classify -> fix -> replay)");return[...r,...n,"","replay after a fix: npx ripplo explore replay <id>",t].join(`
222
219
  `).trim()}function Tt(e,r){switch(r.kind){case"resolved":return`explore: ${e} replayed clean \u2014 finding resolved, its targets covered under the current workflows`;case"unreachable":return`explore: ${e}'s trail is no longer plannable under the current workflows \u2014 finding resolved (if you narrowed a given/when, make sure a test covers the excluded state)`;case"still-failing":{let n=r.runId==null?"":` (fresh evidence: run ${r.runId})`;return`explore: ${e} still reproduces \u2014 same failure signature${n}`}case"diverged":{let n=r.runId==null?"":` (captured run ${r.runId})`;return`explore: ${e} failed with a different failure signature \u2014 new finding recorded${n}`}case"flaky":return`explore: ${e} did not reproduce deterministically \u2014 recorded as flaky-candidate, finding stays pending`;case"aborted":return`explore: replay of ${e} was aborted`;case"finding-not-found":return`explore: no pending finding ${e} \u2014 check ids with: npx ripplo explore findings`;case"unreplayable":return`explore: ${e} cannot be replayed (${r.reason})`;case"error":return`explore: replay of ${e} failed (${r.reason})`}}function Lt({executed:e,progress:r,trail:n}){let t=n.trail.flatMap(o=>[` ${o.label}`,...o.actions.map(i=>` ${i}`)]);return[`trail ${String(e)} ${n.kind} \u2014 ${se(r)}`,...t,` state: ${n.label}`].join(`
223
220
  `)}function Dt(e){switch(e.kind){case"config-failed":return E(e.failure);case"compile-failed":return v(e.error);case"no-work":return"explore: nothing to explore \u2014 no runnable actions found in your workflows";case"explorer-busy":return"explore: another explorer holds the machine lock (likely the daemon's background explorer) \u2014 watch it with `npx ripplo status`, or stop it to explore in the foreground";case"completed":return`${`explore: ${String(e.executed)} trails executed`}
224
221
  ${se(e.progress)}
@@ -291,9 +288,9 @@ Verify the dev session is live (\`npx ripplo doctor\`, ${c("start")}), or pass t
291
288
  `);let l=new Cl;process.once("SIGINT",()=>{l.cancel(),s.socket.destroy(),process.exit(130)});let d=Rl.join(t,".ripplo","debug"),p=(await Le({connection:s,request:{all:e,headed:r,tests:[...n]},token:l.token,onEvent:u=>{let z=Ne({debugDir:d,event:u});z!=null&&process.stdout.write(`${z}
292
289
  `)}})).match(u=>u,u=>ue(T(u)));await Pl({connection:s,result:p})}async function Pl({connection:e,result:r}){await new Promise(n=>{e.socket.end(n)}),r.kind==="daemon-error"&&ue(Me(r.error)),process.stdout.write(`${Jt(r)}
293
290
  `),r.failed>0&&process.exit(1),process.exit(r.notRun>0?2:0)}function ue(e){process.stderr.write(`${e}
294
- `),process.exit(1)}import oo from"path";import{err as We,ok as ur}from"neverthrow";async function io(){let e=process.cwd(),r=$.child({worker:process.pid}),n=Bn(),t=ve({clientVersion:P(),debugDir:oo.join(e,".ripplo","debug"),headed:!1,writeOtlpPortFile:!1}),o={entry:void 0},i=a=>{t.close().catch(()=>{}).then(()=>{process.exit(a)})};process.on("disconnect",()=>{i(1)}),process.on("unhandledRejection",a=>{r.error({err:a},"worker unhandled rejection")}),process.on("uncaughtException",a=>{r.error({err:a},"worker uncaught exception"),i(1)}),n.onRequest(Un,async(a,s)=>{let l=On.safeParse(a);if(!l.success)return He("bad-run-assign");let d=l.data;B(e);let y=w(e).match(j=>j,j=>j.kind);if(typeof y=="string")return He(`config:${y}`);let p=await so(d.lockfileFingerprint,o,n);if(p.isErr())return He(`lockfile-unavailable:${p.error}`);let u=p.value,z=Br(u).find(j=>j.ref===d.testRef);if(z==null)return He(`no-test:${d.testRef}`);try{let j=await un({config:y,cwd:e,fixturesDir:oo.join(e,ye),headed:d.headed,lockfile:u,runId:d.runId,session:t,signal:nr(s),test:z.test});return{outcome:bn(j),serverNotified:!0}}catch(j){if(j instanceof vr)return{outcome:{detail:j.message,kind:"infra-error"},serverNotified:!1};throw j}}),n.onRequest(Hn,(a,s)=>El({cache:o,connection:n,cwd:e,raw:a,session:t,token:s})),n.onNotification(Wn,a=>{let s=_n.safeParse(a);s.success&&t.injectSpan(s.data.runId,s.data.span)}),n.onNotification(qn,()=>{i(0)}),n.sendNotification(Nn),await new Promise(()=>{})}function He(e){return{outcome:{detail:e,kind:"error"},serverNotified:!1}}async function El({cache:e,connection:r,cwd:n,raw:t,session:o,token:i}){let a=Fn.safeParse(t);if(!a.success)return{kind:"error",reason:"bad-assign",rows:[],trail:[]};let s=a.data;B(n);let l=w(n).match(p=>p,p=>p.kind);if(typeof l=="string")return{kind:"error",reason:`config:${l}`,rows:[],trail:[]};let d=await so(s.lockfileFingerprint,e,r);if(d.isErr())return{kind:"error",reason:`lockfile:${d.error}`,rows:[],trail:[]};let y=d.value;return Ae({assign:s,config:l,headed:!1,lockfile:y,report:!0,session:o,signal:nr(i)})}async function so(e,r,n){return r.entry!=null&&r.entry.fingerprint===e?ur(r.entry.lockfile):(await $l(n,e)).andThen(o=>o.unavailable!=null?We(o.unavailable):o.lockfileJson==null?We("empty-reply"):Il(o.lockfileJson).map(i=>(r.entry={fingerprint:e,lockfile:i},i)))}async function $l(e,r){try{return ur(await e.sendRequest(Mn,{fingerprint:r}))}catch(n){return We(`transport:${n instanceof Error?n.message:"unknown"}`)}}function Il(e){try{return ur(Ar(N,e))}catch(r){return We(`worker-decode-failed:${r instanceof Error?r.message.slice(0,200):"unknown"}`)}}async function ao(){await io()}import uo from"path";import{existsSync as jl}from"fs";import{writeFile as lo}from"fs/promises";import co from"path";import{err as me,ok as Al}from"neverthrow";async function po({jsonlPath:e,moment:r,outDir:n,runId:t}){if(!jl(e))return me({kind:"run-not-found",runId:t});let o=await Xr(e),i=o[0],a=o.at(-1);if(i==null||a==null)return me({kind:"no-rrweb-events",runId:t});let s=a.timestamp-i.timestamp,l=r.kind==="offset"?r.offsetMs:r.at-i.timestamp;if(l<0)return me({durationMs:s,firstTimestamp:i.timestamp,kind:"moment-out-of-range",lastTimestamp:a.timestamp,moment:r});let d=Math.min(l,s),y=String(Math.round(d)),p=co.join(n,`snapshot-${y}ms.png`),u=co.join(n,`snapshot-${y}ms.html`);return(await Tl({events:o,htmlPath:u,offsetMs:d,pngPath:p})).map(()=>({durationMs:s,htmlPath:u,offsetMs:d,pngPath:p}))}async function Tl({events:e,htmlPath:r,offsetMs:n,pngPath:t}){let o=await nn();if(o==null)return me({detail:"chromium launch",kind:"browser-failed"});try{let{html:i,png:a}=await Ll({browser:o,events:e,offsetMs:n});return await lo(t,a),await lo(r,i,"utf8"),Al(void 0)}catch(i){return me({detail:i instanceof Error?i.message:String(i),kind:"browser-failed"})}finally{await o.close()}}async function Ll({browser:e,events:r,offsetMs:n}){let t=Yr(r),o=await e.newPage({viewport:t});await o.setContent(Zr(!1)),await o.addScriptTag({content:await tn()}),await o.evaluate(Dl({events:r,offsetMs:n})),await o.evaluate(rn());let i=o.locator(".replayer-wrapper iframe").first(),s=await(await i.count()>0?i:o).screenshot({type:"png"});return{html:await o.evaluate(Ol()),png:s}}function Dl({events:e,offsetMs:r}){return`(() => {
291
+ `),process.exit(1)}import oo from"path";import{err as We,ok as ur}from"neverthrow";async function io(){let e=process.cwd(),r=$.child({worker:process.pid}),n=qn(),t=be({clientVersion:P(),debugDir:oo.join(e,".ripplo","debug"),headed:!1,writeOtlpPortFile:!1}),o={entry:void 0},i=a=>{t.close().catch(()=>{}).then(()=>{process.exit(a)})};process.on("disconnect",()=>{i(1)}),process.on("unhandledRejection",a=>{r.error({err:a},"worker unhandled rejection")}),process.on("uncaughtException",a=>{r.error({err:a},"worker uncaught exception"),i(1)}),n.onRequest(Nn,async(a,s)=>{let l=Dn.safeParse(a);if(!l.success)return He("bad-run-assign");let d=l.data;B(e);let y=w(e).match(j=>j,j=>j.kind);if(typeof y=="string")return He(`config:${y}`);let p=await so(d.lockfileFingerprint,o,n);if(p.isErr())return He(`lockfile-unavailable:${p.error}`);let u=p.value,z=Br(u).find(j=>j.ref===d.testRef);if(z==null)return He(`no-test:${d.testRef}`);try{let j=await un({config:y,cwd:e,fixturesDir:oo.join(e,ye),headed:d.headed,lockfile:u,runId:d.runId,session:t,signal:nr(s),test:z.test});return{outcome:bn(j),serverNotified:!0}}catch(j){if(j instanceof vr)return{outcome:{detail:j.message,kind:"infra-error"},serverNotified:!1};throw j}}),n.onRequest(Mn,(a,s)=>El({cache:o,connection:n,cwd:e,raw:a,session:t,token:s})),n.onNotification(Hn,a=>{let s=On.safeParse(a);s.success&&t.injectSpan(s.data.runId,s.data.span)}),n.onNotification(Wn,()=>{i(0)}),n.sendNotification(Fn),await new Promise(()=>{})}function He(e){return{outcome:{detail:e,kind:"error"},serverNotified:!1}}async function El({cache:e,connection:r,cwd:n,raw:t,session:o,token:i}){let a=_n.safeParse(t);if(!a.success)return{kind:"error",reason:"bad-assign",rows:[],trail:[]};let s=a.data;B(n);let l=w(n).match(p=>p,p=>p.kind);if(typeof l=="string")return{kind:"error",reason:`config:${l}`,rows:[],trail:[]};let d=await so(s.lockfileFingerprint,e,r);if(d.isErr())return{kind:"error",reason:`lockfile:${d.error}`,rows:[],trail:[]};let y=d.value;return Ae({assign:s,config:l,headed:!1,lockfile:y,report:!0,session:o,signal:nr(i)})}async function so(e,r,n){return r.entry!=null&&r.entry.fingerprint===e?ur(r.entry.lockfile):(await $l(n,e)).andThen(o=>o.unavailable!=null?We(o.unavailable):o.lockfileJson==null?We("empty-reply"):Il(o.lockfileJson).map(i=>(r.entry={fingerprint:e,lockfile:i},i)))}async function $l(e,r){try{return ur(await e.sendRequest(Un,{fingerprint:r}))}catch(n){return We(`transport:${n instanceof Error?n.message:"unknown"}`)}}function Il(e){try{return ur(Ar(N,e))}catch(r){return We(`worker-decode-failed:${r instanceof Error?r.message.slice(0,200):"unknown"}`)}}async function ao(){await io()}import uo from"path";import{existsSync as jl}from"fs";import{writeFile as lo}from"fs/promises";import co from"path";import{err as me,ok as Al}from"neverthrow";async function po({jsonlPath:e,moment:r,outDir:n,runId:t}){if(!jl(e))return me({kind:"run-not-found",runId:t});let o=await tn(e),i=o[0],a=o.at(-1);if(i==null||a==null)return me({kind:"no-rrweb-events",runId:t});let s=a.timestamp-i.timestamp,l=r.kind==="offset"?r.offsetMs:r.at-i.timestamp;if(l<0)return me({durationMs:s,firstTimestamp:i.timestamp,kind:"moment-out-of-range",lastTimestamp:a.timestamp,moment:r});let d=Math.min(l,s),y=String(Math.round(d)),p=co.join(n,`snapshot-${y}ms.png`),u=co.join(n,`snapshot-${y}ms.html`);return(await Tl({events:o,htmlPath:u,offsetMs:d,pngPath:p})).map(()=>({durationMs:s,htmlPath:u,offsetMs:d,pngPath:p}))}async function Tl({events:e,htmlPath:r,offsetMs:n,pngPath:t}){let o=await dn();if(o==null)return me({detail:"chromium launch",kind:"browser-failed"});try{let{html:i,png:a}=await Ll({browser:o,events:e,offsetMs:n});return await lo(t,a),await lo(r,i,"utf8"),Al(void 0)}catch(i){return me({detail:i instanceof Error?i.message:String(i),kind:"browser-failed"})}finally{await o.close()}}async function Ll({browser:e,events:r,offsetMs:n}){let t=on(r),o=await e.newPage({viewport:t});await o.setContent(sn(!1)),await o.addScriptTag({content:await cn()}),await o.evaluate(Dl({events:r,offsetMs:n})),await o.evaluate(ln());let i=o.locator(".replayer-wrapper iframe").first(),s=await(await i.count()>0?i:o).screenshot({type:"png"});return{html:await o.evaluate(Ol()),png:s}}function Dl({events:e,offsetMs:r}){return`(() => {
295
292
  const replayer = new globalThis.__RipploReplayer(${JSON.stringify(e)}, {
296
- insertStyleRules: [${JSON.stringify(en)}],
293
+ insertStyleRules: [${JSON.stringify(an)}],
297
294
  mouseTail: false,
298
295
  root: document.body,
299
296
  showWarning: false,
@@ -342,7 +339,7 @@ ${e.findings.map(t=>zr({evidencePath:void 0,finding:t})).join(`
342
339
  `);return}Jl(e)}function Jl(e){let r=`ripplo@${e}`,n=Hl("npm",["install","-g",r],{stdio:"inherit"});if(n.status!==0){let t=`exit ${String(n.status)}`;process.stderr.write(`${$o(t)}
343
340
  `),process.exit(1)}process.stdout.write(`${fr({evicted:0,latest:e,mode:"global"})}
344
341
  `)}async function Kl(e){if((await Oe(e)).kind==="not-running")return;let n=await Gt(e);process.stdout.write(`${Io(n)}
345
- `)}async function Ao({executor:e,explore:r,exploreConcurrency:n}){let{runDaemon:t}=await import("./daemon-A5IWWSRC.js"),{TunnelProvisionFailedError:o}=await import("./daemon-tunnel-AG6FGNXC.js"),{renderTunnelProvisionFailed:i}=await import("./daemon-APJHNVIG.js");try{await t({executor:e,explore:r,exploreConcurrency:n})}catch(a){throw a instanceof o&&(process.stderr.write(`${i(a.failure)}
342
+ `)}async function Ao({executor:e,explore:r,exploreConcurrency:n}){let{runDaemon:t}=await import("./daemon-C7TOJ236.js"),{TunnelProvisionFailedError:o}=await import("./daemon-tunnel-AG6FGNXC.js"),{renderTunnelProvisionFailed:i}=await import("./daemon-RVYMMUB5.js");try{await t({executor:e,explore:r,exploreConcurrency:n})}catch(a){throw a instanceof o&&(process.stderr.write(`${i(a.failure)}
346
343
  `),process.exit(1)),a}}import{graphql as Be}from"gql.tada";function To(){return"No scope items added \u2014 the matched tests are already in scope (check `npx ripplo scope status`)."}function Lo(e){return[`No test found for: ${e.join(", ")}`,"Pass a test id (kebab-case slug of the test name) or the exact intent string.","List known tests with `npx ripplo status`. To add one, stub it first via the testing DSL.",c("create")].join(`
347
344
  `)}import{graphql as Ql}from"gql.tada";var Do=Ql(`
348
345
  query ScopeStatus($projectId: String!, $cwd: String!) {
@@ -427,7 +424,7 @@ ${e.findings.map(t=>zr({evidencePath:void 0,finding:t})).join(`
427
424
  `),process.stdout.write(`${wn(o)}
428
425
  `);return}let i={daemon:o.kind==="running"?{active:o.status.active,explorer:o.status.explorer,exploring:o.status.exploring,progress:o.status.progress,queued:o.status.queued,running:!0}:{running:o.kind==="unresponsive",state:o.kind},tests:t.map(a=>({id:a,implemented:!1}))};process.stdout.write(`${JSON.stringify(i,null,2)}
429
426
  `)}import Ve from"fs";import td from"os";import Mo from"path";import{z as nd}from"zod";function g(e,r){let n=nd.custom(t=>typeof t=="object"&&t!==null&&"hook_event_name"in t&&t.hook_event_name===e);return{event:e,run:async t=>await r(n.parse(t))??void 0}}var Ho=g("PreToolUse",e=>{if(e.tool_name!=="ExitPlanMode"||!b(e.cwd))return;let r=od();if(r==null)return{hookSpecificOutput:{additionalContext:`Ripplo plan gate: no plan file found \u2014 the "Tests to implement" requirement was not checked. Before implementing, stub a .ripplo/workflows/ workflow per affected flow. ${c("discover")}`,hookEventName:"PreToolUse"}};let n=Ve.readFileSync(r,"utf8");if(!/\.ripplo\/tests|Tests to implement|No e2e coverage needed/.test(n))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Plan must cite ripplo test stubs. Add a "Tests to implement" section listing .ripplo/workflows/<id>.ts per affected flow, OR add "No e2e coverage needed: <reason>" if this plan touches no user-facing behavior. ${c("discover")}`}}});function od(){let e=Mo.join(td.homedir(),".claude","plans");return Ve.existsSync(e)?Ve.readdirSync(e).filter(n=>n.endsWith(".md")).map(n=>Mo.join(e,n)).map(n=>({full:n,mtime:Ve.statSync(n).mtimeMs})).sort((n,t)=>t.mtime-n.mtime)[0]?.full??null:null}var Wo=g("UserPromptSubmit",async e=>{if(e.permission_mode!=="plan"||!f(e.cwd)||!b(e.cwd))return;let r=await x(e.cwd);if(r.isErr())return;let n=F(r.value),t=['Plan must include "Tests to implement" with a .ripplo/workflows/ file per affected flow (ExitPlanMode blocks otherwise). Stub each with `workflow("Intent")` (no body).'];return n.length>0&&t.push(`Existing stubs: ${n.join(", ")}`),{hookSpecificOutput:{additionalContext:t.join(`
430
- `),hookEventName:"UserPromptSubmit"}}});import yd from"path";import Xo from"picomatch";import{z as Yo}from"zod";import{mkdirSync as dd,readFileSync as cd,writeFileSync as pd}from"fs";import ud from"path";import{z as R}from"zod";import{createHash as Bh}from"crypto";import Ge from"picomatch";function id(e){return _(["diff","--name-only","HEAD"],e).split(`
427
+ `),hookEventName:"UserPromptSubmit"}}});import yd from"path";import Xo from"picomatch";import{z as Yo}from"zod";import{mkdirSync as dd,readFileSync as cd,writeFileSync as pd}from"fs";import ud from"path";import{z as R}from"zod";import{createHash as qh}from"crypto";import Ge from"picomatch";function id(e){return _(["diff","--name-only","HEAD"],e).split(`
431
428
  `).filter(r=>r.length>0)}function qo({cwd:e,ignoreGlobs:r,watchGlobs:n}){let t=Ge([...n]),o=Ge([...r]);return id(e).filter(i=>t(i)&&!o(i))}function sd(e){return _(["ls-files","--others","--exclude-standard"],e).split(`
432
429
  `).filter(r=>r.length>0)}function Bo({cwd:e,ignoreGlobs:r,watchGlobs:n}){let t=Ge([...n]),o=Ge([...r]);return sd(e).filter(i=>t(i)&&!o(i))}var ad=["**/src/**","**/app/**","**/apps/**","**/pages/**","**/routes/**","**/components/**","**/server/**","**/api/**","**/backend/**","**/features/**","**/modules/**","**/views/**","**/ui/**","**/hooks/**","**/contexts/**","**/providers/**","**/controllers/**","**/handlers/**","**/resolvers/**","**/services/**","**/middleware/**","**/lib/**"],ld=["**/*.gen.*","**/generated/**","**/*.d.ts","**/*.test.*","**/*.spec.*","**/node_modules/**","**/dist/**","**/build/**",".ripplo/**","**/*.md","**/.next/**","**/.turbo/**","**/.vercel/**","**/.svelte-kit/**","**/.nuxt/**","**/.astro/**","**/coverage/**","**/storybook-static/**","**/*.stories.*","**/*.story.*","**/cli/**","**/scripts/**","**/tools/**","**/__tests__/**","**/__mocks__/**","**/__fixtures__/**","**/*.config.*","**/*.setup.*","**/public/**","**/static/**","**/assets/**","**/migrations/**","**/prisma/migrations/**"];function re(){return{ignorePaths:ld,watchPaths:ad}}var md=R.object({label:R.string().nullable(),slug:R.string().nullable(),status:R.enum(["intent","stub","implemented"])}),fd=R.object({intent:R.string(),name:R.string(),sourcePath:R.string().nullable(),stub:R.boolean()}),gd=R.object({changedAppFiles:R.array(R.string()).readonly(),scope:R.object({available:R.boolean(),items:R.array(md).readonly()}),tests:R.array(fd).readonly().default([]),untrackedAppFiles:R.array(R.string()).readonly()});function Vo({cwd:e,scope:r}){let n=zo(e);Ko(e,{...Qo(e),scope:r,tests:n?.tests??[]})}function Go({cwd:e,tests:r}){let n=zo(e);Ko(e,{...Qo(e),scope:n?.scope??{available:!1,items:[]},tests:r??n?.tests??[]})}function zo(e){let r=gd.safeParse(hd(Jo(e)));return r.success?r.data:null}function hd(e){try{return JSON.parse(cd(e,"utf8"))}catch{return null}}function Jo(e){return Sr(e,"coverage-context.json")}function Ko(e,r){let n=Jo(e);dd(ud.dirname(n),{recursive:!0}),pd(n,JSON.stringify(r,null,2))}function Qo(e){let{ignorePaths:r,watchPaths:n}=re(),t={cwd:e,ignoreGlobs:r,watchGlobs:n};return{changedAppFiles:qo(t),untrackedAppFiles:Bo(t)}}var kd=Yo.looseObject({file_path:Yo.string()}),Zo=g("PostToolUse",async e=>{let r=kd.safeParse(e.tool_input);if(!r.success)return;let n=r.data.file_path,{cwd:t}=e;if(!f(t)||!b(t))return;let o=yd.relative(t,n);if(o.startsWith(".."))return;let{ignorePaths:i,watchPaths:a}=re(),s=Xo([...a]),l=Xo([...i]);if(!s(o)||l(o))return;let d=await x(t);if(Go({cwd:t,tests:d.isOk()?d.value.workflows.map(p=>({intent:p.intent,name:p.name,sourcePath:p.sourcePath??null,stub:p.stub})):void 0}),d.isErr())return;let y=F(d.value);if(y.length!==0)return{hookSpecificOutput:{additionalContext:`Reminder: stub workflows still unimplemented \u2014 ${y.join(", ")}. Implement with \`workflow("Intent", () => ({ given, steps }))\`.`,hookEventName:"PostToolUse"}}});import{createHash as $d}from"crypto";import{z as oi}from"zod";import{createHash as wd}from"crypto";import{mkdirSync as vd,readFileSync as ei,writeFileSync as bd}from"fs";import hr from"path";var Sd=[".ts",".tsx",".js",".jsx"];function ze(e){let r=wd("sha256");return r.update(_(["rev-parse","HEAD"],e)),r.update("\0"),r.update(_(["diff","HEAD"],e)),r.update("\0"),_(["ls-files","--others","--exclude-standard"],e).split(`
433
430
  `).filter(t=>t.length>0).filter(t=>Sd.some(o=>t.endsWith(o))).toSorted((t,o)=>t.localeCompare(o)).forEach(t=>{r.update(t),r.update("\0"),r.update(xd(hr.join(e,t))),r.update("\0")}),r.digest("hex")}function ne(e,r){try{return ei(ri(e,r),"utf8").trim()}catch{return null}}function te(e,r,n){let t=ri(e,r);vd(hr.dirname(t),{recursive:!0}),bd(t,n)}function xd(e){try{return ei(e)}catch{return Buffer.alloc(0)}}function ri(e,r){return hr.join(e,".ripplo",".local",`${r}.hash`)}import{graphql as Rd}from"gql.tada";var Cd=Rd(`
@@ -441,11 +438,11 @@ ${e.findings.map(t=>zr({evidencePath:void 0,finding:t})).join(`
441
438
  }
442
439
  }
443
440
  }
444
- `);async function ti({cwd:e,lockfile:r}){if(!b(e))return{addedSlugs:[]};let n=Ed(e);if(n.length===0)return{addedSlugs:[]};let t=new Set(n),o=r.workflows.filter(l=>l.sourcePath!=null&&t.has(l.sourcePath)).map(l=>O(l.name));if(o.length===0)return{addedSlugs:[]};let i=w(e).unwrapOr(void 0);return i==null?{addedSlugs:[]}:await Dn({config:i,cwd:e,lockfile:r}).catch(l=>($.warn("auto-sync failed: %s",l instanceof Error?l.message:String(l)),null))==null?{addedSlugs:[]}:{addedSlugs:await Pd({cfg:i,slugs:o})?o:[]}}async function Pd({cfg:e,slugs:r}){let t=(await m({config:e,document:Cd,variables:{cwd:e.cwd,projectId:e.projectId,workflowSlugs:[...r]}}).catch(o=>($.warn("auto-scope failed: %s",o instanceof Error?o.message:String(o)),null)))?.addDirtyTestsToScope;return t?.__typename!=="MutationAddDirtyTestsToScopeSuccess"?!1:t.data.length>0}var ni=".ripplo/workflows/";function Ed(e){let r;try{r=_(["status","--porcelain","--",".ripplo/workflows"],e)}catch{return[]}return r.split(`
441
+ `);async function ti({cwd:e,lockfile:r}){if(!b(e))return{addedSlugs:[]};let n=Ed(e);if(n.length===0)return{addedSlugs:[]};let t=new Set(n),o=r.workflows.filter(l=>l.sourcePath!=null&&t.has(l.sourcePath)).map(l=>O(l.name));if(o.length===0)return{addedSlugs:[]};let i=w(e).unwrapOr(void 0);return i==null?{addedSlugs:[]}:await Bn({config:i,cwd:e,lockfile:r}).catch(l=>($.warn("auto-sync failed: %s",l instanceof Error?l.message:String(l)),null))==null?{addedSlugs:[]}:{addedSlugs:await Pd({cfg:i,slugs:o})?o:[]}}async function Pd({cfg:e,slugs:r}){let t=(await m({config:e,document:Cd,variables:{cwd:e.cwd,projectId:e.projectId,workflowSlugs:[...r]}}).catch(o=>($.warn("auto-scope failed: %s",o instanceof Error?o.message:String(o)),null)))?.addDirtyTestsToScope;return t?.__typename!=="MutationAddDirtyTestsToScopeSuccess"?!1:t.data.length>0}var ni=".ripplo/workflows/";function Ed(e){let r;try{r=_(["status","--porcelain","--",".ripplo/workflows"],e)}catch{return[]}return r.split(`
445
442
  `).map(n=>n.slice(3).trim()).filter(n=>n.startsWith(ni)&&n.endsWith(".ts")).map(n=>n.slice(ni.length)).filter(n=>n!=="index.ts"&&!n.endsWith("/index.ts"))}var Id=oi.looseObject({file_path:oi.string()}),ii=g("PostToolUse",async e=>{let r=Id.safeParse(e.tool_input);if(!r.success||!/\/\.ripplo\/.*\.ts$/.test(r.data.file_path))return;let{cwd:n}=e;if(!f(n))return;if(!b(n))return{hookSpecificOutput:{additionalContext:"Ripplo hooks are paused \u2014 DSL lint and lockfile sync did not run for this edit. Resume with `npx ripplo hooks resume` when ready.",hookEventName:"PostToolUse"}};let t=await X(n);if(t.isErr())return{decision:"block",reason:`${v(t.error)}
446
443
  ${c("create","DSL authoring + lint rules")}`};let o=M(t.value);if(o.length>0)return{decision:"block",reason:`${H(o)}
447
444
  ${c("create")}`};let{addedSlugs:i}=await ti({cwd:n,lockfile:t.value});return jd([...i.length>0?[`Auto-scoped ${i.join(", ")} (dirty tests).`]:[],...Ad(n,t.value)])});function jd(e){if(e.length!==0)return{hookSpecificOutput:{additionalContext:e.join(`
448
- `),hookEventName:"PostToolUse"}}}function Ad(e,r){let n=je(r),t=$d("sha256").update(JSON.stringify(n)).digest("hex");return ne(e,"coverage-warn")===t?[]:(te(e,"coverage-warn",t),n.length===0?[]:[Ce(n)])}import{z as si}from"zod";var Td=si.looseObject({command:si.string()}),Ld=/\bripplo\s+hooks\s+pause\b/,ai=g("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=Td.safeParse(e.tool_input);if(!r.success||!Ld.test(r.data.command))return;let{cwd:n}=e;if(f(n))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"`ripplo hooks pause` is a human-only escape hatch \u2014 agents can't bypass Ripplo guardrails on their own. If the daemon genuinely can't start (auth, server down, intentional offline work), surface the blocker to the user and ask them to run `npx ripplo hooks pause` themselves from their terminal."}}});import{parse as Nd}from"shell-quote";import{z as di}from"zod";import{existsSync as Dd,mkdirSync as Od,rmSync as Vy,writeFileSync as _d}from"fs";import yr from"path";function Je(e,r,n){let t=li(e,r,n);Od(yr.dirname(t),{recursive:!0}),_d(t,"")}function Ke(e,r,n){return Dd(li(e,r,n))}function li(e,r,n){return yr.join(Fd(e,r),n)}function Fd(e,r){return yr.join(e,".ripplo",".local","skills-loaded",r)}var Ud=di.looseObject({command:di.string()}),Md="run",Hd=new Set(["tail","head","less","more","wc","sort","uniq","awk","sed","grep"]),pi=g("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=Ud.safeParse(e.tool_input);if(!r.success)return;let n=Vd(r.data.command),t=Wd(n);if(t==null)return;let{cwd:o}=e;if(!f(o)||!b(o))return;if(!Ke(o,e.session_id,t.skill))return ci(t.reason);let i=t.kind==="run"?zd(n):null;if(i!=null)return ci(`Don't pipe \`ripplo run\` through \`${i}\` \u2014 buffering filters hide live progress, and killing the pipeline mid-run orphans the run on the server. Redirect to a file instead (\`npx ripplo run <id> > /tmp/run.log 2>&1\` and Read it), or use \`run_in_background: true\`.`)});function Wd(e){return qd(e)?{kind:"replay",reason:"Running `ripplo explore replay` requires the `/ripplo:fuzz` skill loaded first. Load `/ripplo:fuzz` then retry \u2014 it carries the triage loop and the add-vs-weaken guardrail for resolving findings.",skill:"fuzz"}:Bd(e)?{kind:"run",reason:"Running `ripplo run` requires the `/ripplo:run` skill loaded first. Load `/ripplo:run` then retry \u2014 it carries the artifact-read order, the no-grep-piping guidance, the failure decision tree, and the caught-bug filing contract.",skill:Md}:null}function qd(e){return e.some((r,n)=>r==="ripplo"&&e[n+1]==="explore"&&e[n+2]==="replay")}function Bd(e){return e.some((r,n)=>r==="ripplo"&&e[n+1]==="run")}function Vd(e){try{return Nd(e)}catch{return[]}}function Gd(e){return typeof e=="object"&&"op"in e&&e.op==="|"}function zd(e){let r=e.find((n,t)=>{let o=e[t-1];return o!=null&&Gd(o)&&typeof n=="string"&&Hd.has(n)});return typeof r=="string"?r:null}function ci(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}import Jd from"path";import{z as ui}from"zod";var Kd=new Set(["Edit","Write","NotebookEdit"]),Qd=ui.looseObject({file_path:ui.string()}),Xd="create",mi=g("PreToolUse",e=>{if(!Kd.has(e.tool_name))return;let r=Qd.safeParse(e.tool_input);if(!r.success)return;let{cwd:n}=e;if(!f(n)||!b(n))return;let t=Jd.relative(n,r.data.file_path);if(!(t.startsWith("..")||t!==".ripplo"&&!t.startsWith(".ripplo/"))&&!Ke(n,e.session_id,Xd))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Editing \`.ripplo/\` files (${t}) requires the \`/ripplo:create\` skill loaded first. Load \`/ripplo:create\` then retry \u2014 it carries the DSL builder shape, lint rules, and the parallelization guidance you'll need.`}}});import Yd from"path";import fi from"picomatch";import{graphql as Zd}from"gql.tada";import{z as gi}from"zod";var ec=new Set(["Edit","Write","NotebookEdit"]),rc=gi.looseObject({file_path:gi.string()}),nc=Zd(`
445
+ `),hookEventName:"PostToolUse"}}}function Ad(e,r){let n=je(r),t=$d("sha256").update(JSON.stringify(n)).digest("hex");return ne(e,"coverage-warn")===t?[]:(te(e,"coverage-warn",t),n.length===0?[]:[Ce(n)])}import{z as si}from"zod";var Td=si.looseObject({command:si.string()}),Ld=/\bripplo\s+hooks\s+pause\b/,ai=g("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=Td.safeParse(e.tool_input);if(!r.success||!Ld.test(r.data.command))return;let{cwd:n}=e;if(f(n))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"`ripplo hooks pause` is a human-only escape hatch \u2014 agents can't bypass Ripplo guardrails on their own. If the daemon genuinely can't start (auth, server down, intentional offline work), surface the blocker to the user and ask them to run `npx ripplo hooks pause` themselves from their terminal."}}});import{parse as Nd}from"shell-quote";import{z as di}from"zod";import{existsSync as Dd,mkdirSync as Od,rmSync as By,writeFileSync as _d}from"fs";import yr from"path";function Je(e,r,n){let t=li(e,r,n);Od(yr.dirname(t),{recursive:!0}),_d(t,"")}function Ke(e,r,n){return Dd(li(e,r,n))}function li(e,r,n){return yr.join(Fd(e,r),n)}function Fd(e,r){return yr.join(e,".ripplo",".local","skills-loaded",r)}var Ud=di.looseObject({command:di.string()}),Md="run",Hd=new Set(["tail","head","less","more","wc","sort","uniq","awk","sed","grep"]),pi=g("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=Ud.safeParse(e.tool_input);if(!r.success)return;let n=Vd(r.data.command),t=Wd(n);if(t==null)return;let{cwd:o}=e;if(!f(o)||!b(o))return;if(!Ke(o,e.session_id,t.skill))return ci(t.reason);let i=t.kind==="run"?zd(n):null;if(i!=null)return ci(`Don't pipe \`ripplo run\` through \`${i}\` \u2014 buffering filters hide live progress, and killing the pipeline mid-run orphans the run on the server. Redirect to a file instead (\`npx ripplo run <id> > /tmp/run.log 2>&1\` and Read it), or use \`run_in_background: true\`.`)});function Wd(e){return qd(e)?{kind:"replay",reason:"Running `ripplo explore replay` requires the `/ripplo:fuzz` skill loaded first. Load `/ripplo:fuzz` then retry \u2014 it carries the triage loop and the add-vs-weaken guardrail for resolving findings.",skill:"fuzz"}:Bd(e)?{kind:"run",reason:"Running `ripplo run` requires the `/ripplo:run` skill loaded first. Load `/ripplo:run` then retry \u2014 it carries the artifact-read order, the no-grep-piping guidance, the failure decision tree, and the caught-bug filing contract.",skill:Md}:null}function qd(e){return e.some((r,n)=>r==="ripplo"&&e[n+1]==="explore"&&e[n+2]==="replay")}function Bd(e){return e.some((r,n)=>r==="ripplo"&&e[n+1]==="run")}function Vd(e){try{return Nd(e)}catch{return[]}}function Gd(e){return typeof e=="object"&&"op"in e&&e.op==="|"}function zd(e){let r=e.find((n,t)=>{let o=e[t-1];return o!=null&&Gd(o)&&typeof n=="string"&&Hd.has(n)});return typeof r=="string"?r:null}function ci(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}import Jd from"path";import{z as ui}from"zod";var Kd=new Set(["Edit","Write","NotebookEdit"]),Qd=ui.looseObject({file_path:ui.string()}),Xd="create",mi=g("PreToolUse",e=>{if(!Kd.has(e.tool_name))return;let r=Qd.safeParse(e.tool_input);if(!r.success)return;let{cwd:n}=e;if(!f(n)||!b(n))return;let t=Jd.relative(n,r.data.file_path);if(!(t.startsWith("..")||t!==".ripplo"&&!t.startsWith(".ripplo/"))&&!Ke(n,e.session_id,Xd))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Editing \`.ripplo/\` files (${t}) requires the \`/ripplo:create\` skill loaded first. Load \`/ripplo:create\` then retry \u2014 it carries the DSL builder shape, lint rules, and the parallelization guidance you'll need.`}}});import Yd from"path";import fi from"picomatch";import{graphql as Zd}from"gql.tada";import{z as gi}from"zod";var ec=new Set(["Edit","Write","NotebookEdit"]),rc=gi.looseObject({file_path:gi.string()}),nc=Zd(`
449
446
  query PreEditScopeGate($projectId: String!, $cwd: String!) {
450
447
  project(id: $projectId) {
451
448
  id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripplo",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "CLI for Ripplo — AI-powered end-to-end testing",
5
5
  "type": "module",
6
6
  "homepage": "https://ripplo.ai",
@@ -60,11 +60,11 @@
60
60
  "tsup": "^8.5.1",
61
61
  "vitest": "^4.1.4",
62
62
  "@ripplo/core": "^0.0.0",
63
- "@ripplo/eslint-config": "0.0.0",
64
63
  "@ripplo/run-report": "0.0.0",
64
+ "@ripplo/eslint-config": "0.0.0",
65
+ "@ripplo/runtime": "^0.0.0",
65
66
  "@ripplo/spec": "^0.0.0",
66
67
  "@ripplo/rrweb-bundle": "0.0.0",
67
- "@ripplo/runtime": "^0.0.0",
68
68
  "@ripplo/graphql": "^0.0.0"
69
69
  },
70
70
  "scripts": {