ripplo 0.7.16 → 0.7.18

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.
package/dist/index.js CHANGED
@@ -1,32 +1,32 @@
1
1
  #!/usr/bin/env node
2
- import{$ as be,A as Tr,Aa as rr,B as S,C as c,D as Dr,E as x,F as Or,G as _r,Ga as lt,H as Fr,Ha as de,I as Nr,Ia as dt,J as $,Ja as ct,K as j,Ka as K,L as Ur,La as tr,M as w,Ma as pt,N as ve,Na as ut,Oa as mt,Pa as ft,Q as N,Qa as gt,R as Mr,Ra as ht,S as Hr,Sa as yt,T as ae,Ta as kt,U as Wr,Ua as wt,V as qr,Va as vt,W as Xe,Wa as Re,X as Br,Xa as bt,Y as J,Ya as St,Z as Vr,_ as Gr,a as vr,aa as P,b as br,ba as zr,c as Sr,ca as Jr,cb as ce,d as u,da as Kr,db as xt,e as _,eb as nr,fa as Qr,g as ke,ga as Se,gb as Rt,h as xr,ha as Yr,i as F,ia as Ze,ib as Ct,j as Rr,ja as er,k as Cr,ka as Xr,kb as Pt,l as Ye,la as Zr,m as Pr,ma as xe,mb as Et,n as Er,o as $r,oa as et,ob as $t,p as Ir,pb as It,q as V,qa as rt,qb as jt,r as jr,ra as tt,rb as At,s as Ar,sa as le,sb as Lt,t as C,ta as v,tb as Tt,u as z,ua as nt,v as Lr,va as ot,vb as Dt,w as M,wa as it,x as A,y as we,ya as st,z as D,za as at}from"./chunk-KSPGEX7G.js";import Sc from"update-notifier";import xc from"yargs";import{hideBin as Rc}from"yargs/helpers";function Ce({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 Ot(){return"Update available {currentVersion} \u2192 {latestVersion}\nRun `npx ripplo update`"}import{graphql as Ei}from"gql.tada";import{exec as hi}from"child_process";import{createAuthClient as fi}from"better-auth/client";import{deviceAuthorizationClient as gi}from"better-auth/client/plugins";function _t({baseURL:e}){return fi({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[gi()]})}import{err as Ft,ok as yi}from"neverthrow";var ki=5e3,Nt="ripplo-cli";async function Ut({onDeviceCode:e,url:r}){let t=r??Ur().RIPPLO_SERVER_URL,n=_t({baseURL:t}),o=await n.device.code({client_id:Nt});if(o.error!=null)return Ft({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}),Ri(s),(await wi({authClient:n,deviceCode:i})).map(d=>(Rr({serverUrl:t,token:d}),d))}async function wi({authClient:e,deviceCode:r}){for(;;){await Si(ki);let t=await e.device.token({client_id:Nt,device_code:r,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(t.data?.access_token!=null)return yi(t.data.access_token);if(t.error==null)continue;if(!bi(t.error.error))return Ft({code:t.error.error,description:t.error.error_description,kind:"oauth-authorization-failed"})}}var vi=new Set(["authorization_pending","slow_down"]);function bi(e){return vi.has(e)}function Si(e){return new Promise(r=>{setTimeout(r,e)})}function xi(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function Ri(e){let r=xi();hi(`${r} "${e}"`,()=>{})}function H({serverUrl:e,token:r}){return{appUrl:"",cwd:process.cwd(),engineUrl:"",projectId:"",ripploServerUrl:e,token:r,webhookSecret:""}}import Ci from"fs";import Pi from"path";function m(e){return Ci.existsSync(Pi.join(e,".ripplo"))}var $i=Ei(`
2
+ import{A as ae,B as Wr,C as le,D as v,E as nt,F as ot,G as it,I as st,J as at,K as rr,L as K,M as tr,N as pt,O as ut,P as mt,Q as ft,R as gt,S as ht,T as yt,U as kt,V as wt,W as vt,X as Re,Y as bt,Z as St,a as Ir,b as V,c as Ar,d as jr,da as ce,e as C,ea as xt,f as we,fa as nr,g as D,h as Tr,ha as Rt,i as S,j as c,ja as Ct,k as Dr,l as x,la as Pt,m as Or,n as _r,na as Et,o as Fr,p as Nr,pa as $t,q as I,qa as It,r as A,ra as At,s as Ur,sa as jt,t as w,ta as Lt,u as ve,ua as Tt,wa as Dt,x as N,y as Mr,z as Hr}from"./chunk-LYAUCRJZ.js";import{$ as dt,D as Yr,F as Ze,G as er,H as Xr,I as Zr,J as xe,P as et,R as rt,S as tt,Z as lt,_ as de,aa as ct,c as z,d as Lr,g as M,j,l as qr,m as Xe,n as Br,o as J,p as Vr,q as Gr,r as be,s as P,t as zr,u as Jr,v as Kr,x as Qr,y as Se}from"./chunk-RZKJVOMP.js";import{a as vr,b as br,c as Sr,d as u,e as _,g as ke,h as xr,k as F,l as Rr,m as Cr,n as Ye,o as Pr,p as Er,q as $r}from"./chunk-Y5IXMRSB.js";import"./chunk-SB7H6IAS.js";import Sc from"update-notifier";import xc from"yargs";import{hideBin as Rc}from"yargs/helpers";function Ce({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 Ot(){return"Update available {currentVersion} \u2192 {latestVersion}\nRun `npx ripplo update`"}import{graphql as Ei}from"gql.tada";import{exec as hi}from"child_process";import{createAuthClient as fi}from"better-auth/client";import{deviceAuthorizationClient as gi}from"better-auth/client/plugins";function _t({baseURL:e}){return fi({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[gi()]})}import{err as Ft,ok as yi}from"neverthrow";var ki=5e3,Nt="ripplo-cli";async function Ut({onDeviceCode:e,url:r}){let t=r??Ur().RIPPLO_SERVER_URL,n=_t({baseURL:t}),o=await n.device.code({client_id:Nt});if(o.error!=null)return Ft({description:o.error.error_description,kind:"oauth-device-code-failed"});let{device_code:i,user_code:s,verification_uri_complete:a}=o.data;return e({userCode:s,verificationUrl:a}),Ri(a),(await wi({authClient:n,deviceCode:i})).map(d=>(Rr({serverUrl:t,token:d}),d))}async function wi({authClient:e,deviceCode:r}){for(;;){await Si(ki);let t=await e.device.token({client_id:Nt,device_code:r,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(t.data?.access_token!=null)return yi(t.data.access_token);if(t.error==null)continue;if(!bi(t.error.error))return Ft({code:t.error.error,description:t.error.error_description,kind:"oauth-authorization-failed"})}}var vi=new Set(["authorization_pending","slow_down"]);function bi(e){return vi.has(e)}function Si(e){return new Promise(r=>{setTimeout(r,e)})}function xi(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function Ri(e){let r=xi();hi(`${r} "${e}"`,()=>{})}function H({serverUrl:e,token:r}){return{appUrl:"",cwd:process.cwd(),engineUrl:"",projectId:"",ripploServerUrl:e,token:r,tunnelAuth:void 0,webhookSecret:""}}import Ci from"fs";import Pi from"path";function m(e){return Ci.existsSync(Pi.join(e,".ripplo"))}var $i=Ei(`
3
3
  query AuthViewer {
4
4
  currentUser {
5
5
  name
6
6
  email
7
7
  }
8
8
  }
9
- `);async function Mt(){let e=j(),r=F(e);if(r!=null&&await Ii(e,r))return;let n=(await Ut({url:e,onDeviceCode:s=>{process.stdout.write(`Opening your browser to finish sign-in.
10
- `),process.stdout.write(`If it didn't open, visit: ${s.verificationUrl}
11
- `),process.stdout.write(`Verification code: ${s.userCode}
9
+ `);async function Mt(){let e=A(),r=F(e);if(r!=null&&await Ii(e,r))return;let n=(await Ut({url:e,onDeviceCode:a=>{process.stdout.write(`Opening your browser to finish sign-in.
10
+ `),process.stdout.write(`If it didn't open, visit: ${a.verificationUrl}
11
+ `),process.stdout.write(`Verification code: ${a.userCode}
12
12
 
13
13
  `),process.stdout.write(`Waiting for approval...
14
- `)}})).match(s=>s,s=>{process.stderr.write(`${$(s)}
15
- `),process.exit(1)}),o=await or({serverUrl:e,token:n}),i=m(process.cwd()),a=i?"Welcome back":"Welcome to Ripplo";process.stdout.write(`
16
- `),process.stdout.write(o.kind==="ok"?`${a}, ${ji(o.viewer)}.
17
- `:`${a}.
14
+ `)}})).match(a=>a,a=>{process.stderr.write(`${I(a)}
15
+ `),process.exit(1)}),o=await or({serverUrl:e,token:n}),i=m(process.cwd()),s=i?"Welcome back":"Welcome to Ripplo";process.stdout.write(`
16
+ `),process.stdout.write(o.kind==="ok"?`${s}, ${Ai(o.viewer)}.
17
+ `:`${s}.
18
18
  `),process.stdout.write(`Session saved to ${Ye(e)}.
19
19
  `),i||(process.stdout.write("\nNext: run `/ripplo:setup` in Claude Code to wire Ripplo into this project,\n"),process.stdout.write("or `npx ripplo init` to set it up by hand.\n"))}async function Ii(e,r){let t=await or({serverUrl:e,token:r});return t.kind==="ok"?(process.stdout.write(`Already signed in as ${t.viewer.email}. Run \`npx ripplo auth logout\` to switch accounts.
20
20
  `),!0):(t.kind==="unreachable"&&(process.stdout.write(`Could not reach the Ripplo server at ${e} (${t.message}). Your saved session may still be valid \u2014 fix connectivity and retry.
21
21
  `),process.exit(1)),process.stdout.write(`Your session expired \u2014 signing you back in.
22
22
 
23
- `),!1)}async function Ht(){let e=j(),r=F(e);r==null&&(process.stdout.write("Not signed in. Run `npx ripplo auth login`.\n"),process.exit(1));let t=await or({serverUrl:e,token:r});t.kind==="unreachable"&&(process.stdout.write(`Could not reach the Ripplo server at ${e} (${t.message}). The token may still be valid \u2014 check the server / network and retry.
23
+ `),!1)}async function Ht(){let e=A(),r=F(e);r==null&&(process.stdout.write("Not signed in. Run `npx ripplo auth login`.\n"),process.exit(1));let t=await or({serverUrl:e,token:r});t.kind==="unreachable"&&(process.stdout.write(`Could not reach the Ripplo server at ${e} (${t.message}). The token may still be valid \u2014 check the server / network and retry.
24
24
  `),process.exit(1)),t.kind==="rejected"&&(process.stdout.write("Token rejected by the server. Run `npx ripplo auth login` to sign in again.\n"),process.stdout.write(`(Claude Code: run it yourself in the background \u2014 the user just approves in the browser.)
25
25
  `),process.exit(1)),process.stdout.write(`Signed in as ${t.viewer.name} <${t.viewer.email}> (${e})
26
- `)}function Wt(){let e=j();if(!Cr(e)){process.stdout.write(`Already signed out.
26
+ `)}function Wt(){let e=A();if(!Cr(e)){process.stdout.write(`Already signed out.
27
27
  `);return}process.stdout.write(`Signed out. Removed ${Ye(e)}.
28
- `)}async function or({serverUrl:e,token:r}){try{let n=(await u({config:H({serverUrl:e,token:r}),document:$i,variables:void 0})).currentUser;return n==null?{kind:"rejected"}:{kind:"ok",viewer:{email:n.email,name:n.name}}}catch(t){return Sr(t)==="UNAUTHENTICATED"?{kind:"rejected"}:{kind:"unreachable",message:t instanceof Error?t.message:String(t)}}}function ji(e){let r=e.name.trim().split(/\s+/)[0];return r!=null&&r.length>0?r:e.email}import{readFile as Ai,writeFile as Li}from"fs/promises";import Ti from"path";async function qt(e){let r=process.cwd(),t=await S(r);t.isErr()&&(process.stderr.write(`${x(t.error)}
29
- `),process.exit(1));let n=z(M,t.value),o=Ti.join(r,D);if(e.check){let i=await Ai(o,"utf8").catch(()=>null);if(i===n){process.stdout.write(`${Or()}
28
+ `)}async function or({serverUrl:e,token:r}){try{let n=(await u({config:H({serverUrl:e,token:r}),document:$i,variables:void 0})).currentUser;return n==null?{kind:"rejected"}:{kind:"ok",viewer:{email:n.email,name:n.name}}}catch(t){return Sr(t)==="UNAUTHENTICATED"?{kind:"rejected"}:{kind:"unreachable",message:t instanceof Error?t.message:String(t)}}}function Ai(e){let r=e.name.trim().split(/\s+/)[0];return r!=null&&r.length>0?r:e.email}import{readFile as ji,writeFile as Li}from"fs/promises";import Ti from"path";async function qt(e){let r=process.cwd(),t=await S(r);t.isErr()&&(process.stderr.write(`${x(t.error)}
29
+ `),process.exit(1));let n=z(M,t.value),o=Ti.join(r,D);if(e.check){let i=await ji(o,"utf8").catch(()=>null);if(i===n){process.stdout.write(`${Or()}
30
30
  `);return}process.stderr.write(`${_r(i==null?"missing":"stale")}
31
31
  `),process.exit(1)}await Li(o,n),process.stdout.write(`${Fr()}
32
32
  `)}import{graphql as Bt}from"gql.tada";function ir(e){return e==null?"Max local concurrent runs: unknown (not signed in?)":`Max local concurrent runs: ${String(e)} (the daemon applies changes live)`}import{graphql as Di}from"gql.tada";var Oi=Di(`
@@ -38,7 +38,7 @@ import{$ as be,A as Tr,Aa as rr,B as S,C as c,D as Dr,E as x,F as Or,G as _r,Ga
38
38
  }
39
39
  }
40
40
  }
41
- `);function L(){return w(process.cwd()).match(e=>e,e=>{process.stderr.write(`${$(e)}
41
+ `);function L(){return w(process.cwd()).match(e=>e,e=>{process.stderr.write(`${I(e)}
42
42
  `),process.stderr.write(`${c("setup")}
43
43
  `),process.exit(1)})}async function pe(e){(await u({config:e,document:Oi,variables:{cwd:e.cwd,projectId:e.projectId}})).project?.devSession==null&&(process.stderr.write("No active dev session. Start `npx ripplo daemon` as a background process (your app's dev server must also be running), then retry. Or run `/ripplo:start` in Claude Code.\n"),process.stderr.write(`${c("setup")}
44
44
  `),process.exit(1))}var _i=Bt(`
@@ -69,7 +69,7 @@ import{$ as be,A as Tr,Aa as rr,B as S,C as c,D as Dr,E as x,F as Or,G as _r,Ga
69
69
  `);function Gt(e){switch(e.status){case"active":return e.gitMidOperation?"\u2713 Dev session: ripplo daemon is live (hooks auto-paused while git is mid-merge/rebase/cherry-pick \u2014 they resume when the operation ends)":"\u2713 Dev session: ripplo daemon is live for this directory";case"starting":return"! Dev session: the daemon is running locally but hasn't registered with the server yet. Wait a few seconds and re-run `npx ripplo doctor`. If it persists, check the daemon output for auth or network errors.";case"missing":return"\u2717 Dev session: no daemon running. Start `npx ripplo daemon` as a background process (separate from your app's dev server). Without it, `npx ripplo run` refuses to dispatch and scope/coverage hooks won't arm."}}async function zt(e){let r=ve(e),t=Mr(e),n=w(e).unwrapOr(void 0);return n==null?{gitMidOperation:t,status:r?"starting":"missing",type:"dev-session"}:(await u({config:n,document:Ui,variables:{cwd:n.cwd,projectId:n.projectId}}).catch(()=>null))?.project?.devSession!=null?{gitMidOperation:t,status:"active",type:"dev-session"}:{gitMidOperation:t,status:r?"starting":"missing",type:"dev-session"}}function Jt(e){switch(e.type){case"settings":return Mi(e);case"env-files":return Hi(e);case"token":return Wi(e);case"dev-server":return qi(e);case"dev-session":return Gt(e);case"preconditions":return Bi(e);case"webhook-verification":return Vi(e);case"preconditions-validation":return Gi(e);case"workflows":return zi(e);case"browser":return Ki(e);case"engine-endpoint":return e.reachable?`\u2713 Engine endpoint: ${e.url} is reachable`:`\u2717 Engine endpoint: ${e.url} is not reachable`;case"adapter-enabled":return Qi(e);case"lockfile":return Yi(e);case"pre-commit-hook":return Xi(e);case"plugin-version":return Zi(e)}}function Mi(e){return e.valid?"\u2713 Settings: project configured":`\u2717 Settings: missing fields: ${e.missingFields.join(", ")}`}function Hi(e){return e.missing.length===0?"\u2713 Env files: declared files present":`\u2717 Env files: declared in .ripplo/project.json but missing:
70
70
  ${e.missing.map(t=>` ${t}`).join(`
71
71
  `)}
72
- In a git worktree? Copy the env file from the main checkout, or symlink to a shared file outside the working tree.`}function Wi(e){switch(e.status){case"valid":return`\u2713 Auth: signed in as ${e.email??"unknown"}`;case"missing":return"\u2717 Auth: not signed in. Run `npx ripplo auth login`.\n (Claude Code: run it yourself in the background \u2014 the user just approves in the browser.)";case"invalid":return"\u2717 Auth: saved token rejected by the server. Run `npx ripplo auth login` to sign in again.\n (Claude Code: run it yourself in the background \u2014 the user just approves in the browser.)";case"unreachable":return`! Auth: could not reach ${j()} to validate the token.`}}function qi(e){return e.reachable?`\u2713 Dev server: ${e.appUrl} is reachable`:`\u2717 Dev server: ${e.appUrl} is not responding. Start your dev server.`}function Bi(e){return e.count===0?"! Entities: none defined":e.configured?e.endpointReachable===void 0?`! Entities: ${String(e.count)} defined (could not verify engine endpoint)`:`\u2713 Entities: ${String(e.count)} defined, engine configured`:`\u2717 Entities: ${String(e.count)} defined but RIPPLO_ENGINE_URL is not set in your env file`}function Vi(e){return e.rejectsUnsigned?"\u2713 Webhook verification: unsigned requests rejected":"\u2717 Webhook verification: endpoint accepted an unsigned request \u2014 signature verification may be missing or misconfigured."}function Gi(e){if(!e.found)return"\u2717 Model: DSL failed to compile";if(e.valid)return"\u2713 Model: valid";let r=e.errors.map(t=>` - ${t.path===""?"":t.path+": "}${t.message}`);return`\u2717 Model: ${String(e.errorCount)} validation error${e.errorCount===1?"":"s"}
72
+ In a git worktree? Copy the env file from the main checkout, or symlink to a shared file outside the working tree.`}function Wi(e){switch(e.status){case"valid":return`\u2713 Auth: signed in as ${e.email??"unknown"}`;case"missing":return"\u2717 Auth: not signed in. Run `npx ripplo auth login`.\n (Claude Code: run it yourself in the background \u2014 the user just approves in the browser.)";case"invalid":return"\u2717 Auth: saved token rejected by the server. Run `npx ripplo auth login` to sign in again.\n (Claude Code: run it yourself in the background \u2014 the user just approves in the browser.)";case"unreachable":return`! Auth: could not reach ${A()} to validate the token.`}}function qi(e){return e.reachable?`\u2713 Dev server: ${e.appUrl} is reachable`:`\u2717 Dev server: ${e.appUrl} is not responding. Start your dev server.`}function Bi(e){return e.count===0?"! Entities: none defined":e.configured?e.endpointReachable===void 0?`! Entities: ${String(e.count)} defined (could not verify engine endpoint)`:`\u2713 Entities: ${String(e.count)} defined, engine configured`:`\u2717 Entities: ${String(e.count)} defined but RIPPLO_ENGINE_URL is not set in your env file`}function Vi(e){return e.rejectsUnsigned?"\u2713 Webhook verification: unsigned requests rejected":"\u2717 Webhook verification: endpoint accepted an unsigned request \u2014 signature verification may be missing or misconfigured."}function Gi(e){if(!e.found)return"\u2717 Model: DSL failed to compile";if(e.valid)return"\u2713 Model: valid";let r=e.errors.map(t=>` - ${t.path===""?"":t.path+": "}${t.message}`);return`\u2717 Model: ${String(e.errorCount)} validation error${e.errorCount===1?"":"s"}
73
73
  ${r.join(`
74
74
  `)}`}function zi(e){if(e.total===0)return"! Tests: none defined";if(e.invalidNames.length===0)return`\u2713 Tests: ${String(e.total)} valid`;let r=e.invalidWorkflows.map(t=>Ji(t));return`\u2717 Tests: ${String(e.invalidNames.length)} invalid
75
75
  ${r.join(`
@@ -85,7 +85,7 @@ ${r.join(`
85
85
  email
86
86
  }
87
87
  }
88
- `),os="Failed to connect to Ripplo server";async function Xt({serverUrl:e,token:r}){try{let t=await u({config:H({serverUrl:e,token:r}),document:ns,variables:void 0});return t.currentUser==null?{kind:"invalid"}:{email:t.currentUser.email,kind:"valid"}}catch(t){return(t instanceof Error?t.message:String(t)).startsWith(os)?{kind:"unreachable"}:{kind:"invalid"}}}function Zt(e){switch(e.type){case"settings":return!e.valid;case"env-files":return e.missing.length>0;case"token":return e.status==="missing"||e.status==="invalid";case"dev-server":return!e.reachable;case"dev-session":return e.status!=="active";case"preconditions":return e.count>0&&!e.configured;case"engine-endpoint":return!e.reachable;case"adapter-enabled":return e.status!=="enabled";case"webhook-verification":return!e.rejectsUnsigned;case"preconditions-validation":return!e.valid;case"workflows":return e.invalidNames.length>0;case"browser":return!e.installed;case"lockfile":return e.status!=="match";case"pre-commit-hook":return!e.installed;case"plugin-version":return!1}}async function en(e){let r=await ls(e),t={missing:Ar(e),type:"env-files"},n=await ds(),o=await cs(),i=await S(e),a=ps(i),s=us(i),l=await ms(e,i),d=gs(e),h=as(e),p=await hs(r,i);return[r,t,n,...p,a,s,l,d,o,...h==null?[]:[h]]}var ss=B.object({plugins:B.record(B.string(),B.array(B.object({projectPath:B.string().optional(),scope:B.string().optional(),version:B.string()})))});function as(e){let r=sr.join(is.homedir(),".claude","plugins","installed_plugins.json"),t=Q.existsSync(r)?Q.readFileSync(r,"utf8"):null;if(t==null)return null;let n=ss.safeParse(JSON.parse(t));if(!n.success)return null;let o=Object.entries(n.data.plugins).filter(([a])=>a.startsWith("ripplo@")).flatMap(([,a])=>a).filter(a=>a.scope==="user"||a.projectPath===e).map(a=>a.version),i=Qt(o);return i==null?null:{cliVersion:C(),installed:i,type:"plugin-version"}}async function ls(e){let r=await Hr(e);return r.valid?{missingFields:[],type:"settings",valid:!0}:{missingFields:r.errors.map(n=>n.path).filter(n=>n.length>0),type:"settings",valid:!1}}async function ds(){let e=j(),r=F(e);if(r==null||r.length===0)return{email:void 0,status:"missing",type:"token"};let t=await Xt({serverUrl:e,token:r});return t.kind==="valid"?{email:t.email,status:"valid",type:"token"}:{email:void 0,status:t.kind,type:"token"}}async function cs(){let{chromium:e}=await import("playwright"),r=e.executablePath();return{installed:Q.existsSync(r),type:"browser"}}function ps(e){if(e.isErr())return{errorCount:1,errors:[{message:x(e.error),path:".ripplo/"}],found:!1,type:"preconditions-validation",valid:!1};let r=W(e.value);return{errorCount:r.length,errors:r.length===0?[]:[{message:q(r),path:""}],found:!0,type:"preconditions-validation",valid:r.length===0}}function us(e){if(e.isErr())return{invalidNames:[],invalidWorkflows:[],total:0,type:"workflows",valid:0};let r=e.value.workflows.length;return{invalidNames:[],invalidWorkflows:[],total:r,type:"workflows",valid:r}}async function ms(e,r){if(r.isErr())return{status:"missing",type:"lockfile"};let t=z(M,r.value),n=await Q.promises.readFile(sr.join(e,D),"utf8").catch(()=>null);return{status:fs(n,t),type:"lockfile"}}function fs(e,r){return e==null?"missing":e===r?"match":"stale"}function gs(e){let r=sr.join(e,".git","hooks","pre-commit");return Q.existsSync(r)?{installed:Q.readFileSync(r,"utf8").includes("ripplo compile --check"),type:"pre-commit-hook"}:{installed:!1,type:"pre-commit-hook"}}async function hs(e,r){if(!e.valid||r.isErr())return[];let t=jr().map(s=>({appUrl:s.appUrl,engineUrl:s.engineUrl,webhookSecret:s.webhookSecret})).unwrapOr(void 0);if(t==null)return[];let n=[],o=await ae(t.appUrl)==null;n.push({appUrl:t.appUrl,reachable:o,type:"dev-server"});let i=await zt(process.cwd());n.push(i);let a=await ys(t,r);return n.push(...a),n}async function ys(e,r){let t=r.isOk()?r.value.entities.length:0,n=e.engineUrl.length>0,o={configured:n,count:t,endpointReachable:void 0,type:"preconditions"};if(t===0||!n)return[o];let i=ws(e.appUrl,e.engineUrl);if(i==null)return[o];let a=await ae(i)==null,s=[];if(s.push({configured:!0,count:t,endpointReachable:a,type:"preconditions"}),rn(e.engineUrl)&&s.push({reachable:a,type:"engine-endpoint",url:i}),!a)return s;let l=await Wr({appUrl:e.appUrl,engineUrl:e.engineUrl});s.push({rejectsUnsigned:l==null,type:"webhook-verification"});let d=await ks({engineUrl:i,webhookSecret:e.webhookSecret});return s.push({status:d,type:"adapter-enabled",url:i}),s}async function ks({engineUrl:e,webhookSecret:r}){if(r.length===0)return"no-secret";let t=JSON.stringify({});try{let n=await fetch(`${e}/setup`,{body:t,headers:{"Content-Type":"application/json",...Er({body:t,secret:r})},method:"PUT",signal:AbortSignal.timeout(5e3)});return n.status===404?"disabled":n.status===401||n.status===403?"bad-secret":"enabled"}catch{return"unreachable"}}function rn(e){return e.startsWith("http://")||e.startsWith("https://")}function ws(e,r){return rn(r)?r:`${e}${r}`}var vs=3e3;async function tn(){let e=process.cwd(),[r,t]=await Promise.all([Pe(vs),en(e)]);process.stdout.write(`${Ce({current:C(),latest:r})}
88
+ `),os="Failed to connect to Ripplo server";async function Xt({serverUrl:e,token:r}){try{let t=await u({config:H({serverUrl:e,token:r}),document:ns,variables:void 0});return t.currentUser==null?{kind:"invalid"}:{email:t.currentUser.email,kind:"valid"}}catch(t){return(t instanceof Error?t.message:String(t)).startsWith(os)?{kind:"unreachable"}:{kind:"invalid"}}}function Zt(e){switch(e.type){case"settings":return!e.valid;case"env-files":return e.missing.length>0;case"token":return e.status==="missing"||e.status==="invalid";case"dev-server":return!e.reachable;case"dev-session":return e.status!=="active";case"preconditions":return e.count>0&&!e.configured;case"engine-endpoint":return!e.reachable;case"adapter-enabled":return e.status!=="enabled";case"webhook-verification":return!e.rejectsUnsigned;case"preconditions-validation":return!e.valid;case"workflows":return e.invalidNames.length>0;case"browser":return!e.installed;case"lockfile":return e.status!=="match";case"pre-commit-hook":return!e.installed;case"plugin-version":return!1}}async function en(e){let r=await ls(e),t={missing:jr(e),type:"env-files"},n=await ds(),o=await cs(),i=await S(e),s=ps(i),a=us(i),l=await ms(e,i),d=gs(e),h=as(e),p=await hs(r,i);return[r,t,n,...p,s,a,l,d,o,...h==null?[]:[h]]}var ss=B.object({plugins:B.record(B.string(),B.array(B.object({projectPath:B.string().optional(),scope:B.string().optional(),version:B.string()})))});function as(e){let r=sr.join(is.homedir(),".claude","plugins","installed_plugins.json"),t=Q.existsSync(r)?Q.readFileSync(r,"utf8"):null;if(t==null)return null;let n=ss.safeParse(JSON.parse(t));if(!n.success)return null;let o=Object.entries(n.data.plugins).filter(([s])=>s.startsWith("ripplo@")).flatMap(([,s])=>s).filter(s=>s.scope==="user"||s.projectPath===e).map(s=>s.version),i=Qt(o);return i==null?null:{cliVersion:C(),installed:i,type:"plugin-version"}}async function ls(e){let r=await Hr(e);return r.valid?{missingFields:[],type:"settings",valid:!0}:{missingFields:r.errors.map(n=>n.path).filter(n=>n.length>0),type:"settings",valid:!1}}async function ds(){let e=A(),r=F(e);if(r==null||r.length===0)return{email:void 0,status:"missing",type:"token"};let t=await Xt({serverUrl:e,token:r});return t.kind==="valid"?{email:t.email,status:"valid",type:"token"}:{email:void 0,status:t.kind,type:"token"}}async function cs(){let{chromium:e}=await import("playwright"),r=e.executablePath();return{installed:Q.existsSync(r),type:"browser"}}function ps(e){if(e.isErr())return{errorCount:1,errors:[{message:x(e.error),path:".ripplo/"}],found:!1,type:"preconditions-validation",valid:!1};let r=W(e.value);return{errorCount:r.length,errors:r.length===0?[]:[{message:q(r),path:""}],found:!0,type:"preconditions-validation",valid:r.length===0}}function us(e){if(e.isErr())return{invalidNames:[],invalidWorkflows:[],total:0,type:"workflows",valid:0};let r=e.value.workflows.length;return{invalidNames:[],invalidWorkflows:[],total:r,type:"workflows",valid:r}}async function ms(e,r){if(r.isErr())return{status:"missing",type:"lockfile"};let t=z(M,r.value),n=await Q.promises.readFile(sr.join(e,D),"utf8").catch(()=>null);return{status:fs(n,t),type:"lockfile"}}function fs(e,r){return e==null?"missing":e===r?"match":"stale"}function gs(e){let r=sr.join(e,".git","hooks","pre-commit");return Q.existsSync(r)?{installed:Q.readFileSync(r,"utf8").includes("ripplo compile --check"),type:"pre-commit-hook"}:{installed:!1,type:"pre-commit-hook"}}async function hs(e,r){if(!e.valid||r.isErr())return[];let t=Ar().map(a=>({appUrl:a.appUrl,engineUrl:a.engineUrl,webhookSecret:a.webhookSecret})).unwrapOr(void 0);if(t==null)return[];let n=[],o=await ae(t.appUrl)==null;n.push({appUrl:t.appUrl,reachable:o,type:"dev-server"});let i=await zt(process.cwd());n.push(i);let s=await ys(t,r);return n.push(...s),n}async function ys(e,r){let t=r.isOk()?r.value.entities.length:0,n=e.engineUrl.length>0,o={configured:n,count:t,endpointReachable:void 0,type:"preconditions"};if(t===0||!n)return[o];let i=ws(e.appUrl,e.engineUrl);if(i==null)return[o];let s=await ae(i)==null,a=[];if(a.push({configured:!0,count:t,endpointReachable:s,type:"preconditions"}),rn(e.engineUrl)&&a.push({reachable:s,type:"engine-endpoint",url:i}),!s)return a;let l=await Wr({appUrl:e.appUrl,engineUrl:e.engineUrl});a.push({rejectsUnsigned:l==null,type:"webhook-verification"});let d=await ks({engineUrl:i,webhookSecret:e.webhookSecret});return a.push({status:d,type:"adapter-enabled",url:i}),a}async function ks({engineUrl:e,webhookSecret:r}){if(r.length===0)return"no-secret";let t=JSON.stringify({});try{let n=await fetch(`${e}/setup`,{body:t,headers:{"Content-Type":"application/json",...Er({body:t,secret:r})},method:"PUT",signal:AbortSignal.timeout(5e3)});return n.status===404?"disabled":n.status===401||n.status===403?"bad-secret":"enabled"}catch{return"unreachable"}}function rn(e){return e.startsWith("http://")||e.startsWith("https://")}function ws(e,r){return rn(r)?r:`${e}${r}`}var vs=3e3;async function tn(){let e=process.cwd(),[r,t]=await Promise.all([Pe(vs),en(e)]);process.stdout.write(`${Ce({current:C(),latest:r})}
89
89
  `);let n=t.map(i=>Jt(i));process.stdout.write(n.join(`
90
90
  `)+`
91
91
  `);let o=t.some(i=>Zt(i));process.exit(o?1:0)}import $e from"fs";import{graphql as bs}from"gql.tada";var Ss=bs(`
@@ -97,13 +97,13 @@ ${r.join(`
97
97
  `);async function nn(){process.stdin.isTTY||(process.stderr.write("`ripplo hooks pause` only runs interactively from a terminal. Bypassing the gate is a human decision, not an agent decision.\n"),process.exit(1));let e=process.cwd();ke(e);let r=le(e);if($e.existsSync(r)){process.stdout.write("Hooks already paused. Run `npx ripplo hooks resume` to re-enable.\n");return}$e.writeFileSync(r,""),await sn(e,!0),process.stdout.write("Hooks paused. Pre-edit gates and stop enforcement are off until you run `npx ripplo hooks resume`.\n")}async function on(){let e=process.cwd(),r=le(e);if(!$e.existsSync(r)){process.stdout.write(`Hooks already active.
98
98
  `);return}$e.unlinkSync(r),await sn(e,!1),process.stdout.write(`Hooks resumed.
99
99
  `)}async function sn(e,r){let t=w(e).unwrapOr(void 0);t!=null&&await u({config:t,document:Ss,variables:{paused:r,projectId:t.projectId}}).catch(n=>{process.stderr.write(`Warning: could not push hook-pause state to the server (${n instanceof Error?n.message:String(n)}) \u2014 the dashboard may show stale hook status.
100
- `)})}import Z from"fs";import ee from"path";import{input as mn,select as fn}from"@inquirer/prompts";import{graphql as Zs}from"gql.tada";import{exec as Cs,execFile as Ps}from"child_process";import{err as Es,ok as an}from"neverthrow";import y from"fs";import{createRequire as $s}from"module";import g from"path";import{promisify as dn}from"util";import{writeFile as xs}from"fs/promises";import Rs from"path";async function Y(e){let r=await S(e);return r.isOk()&&await xs(Rs.join(e,D),z(M,r.value)),r}function U(e){return e.workflows.filter(r=>r.stub).map(r=>r.name)}var Is=["@ripplo/testing","@ripplo/instrument"],ln=".ripplo/ripplo.lock linguist-generated=true",js=[".ripplo/debug/",".ripplo/.local/"],As=dn(Cs),Ls=dn(Ps);async function cn({cwd:e,onStep:r}){r("Scaffolding project files..."),qs({cwd:e}),r("Updating .gitignore..."),Bs(e),r("Marking ripplo.lock as generated..."),_s(e),r("Installing dependencies...");let t=await Ds(e),n=[];if(t.ok||n.push({manualCommand:t.cmd,message:`Couldn't auto-install dev dependencies (${t.reason}). Run the command below, then run \`npx ripplo lint\` to compile the lockfile.`}),t.ok){r("Compiling initial lockfile...");let i=await Os(e);i!=null&&n.push({manualCommand:void 0,message:i})}return r("Setting up browser..."),(await Ts()).map(()=>n)}async function Ts(){let{chromium:e}=await import("playwright"),r=e.executablePath();if(y.existsSync(r))return an(void 0);_.info("Chromium not found. Installing via Playwright...");let t=$s(import.meta.url),n=g.dirname(t.resolve("playwright/package.json")),o=g.join(n,"cli.js");return await Ls(process.execPath,[o,"install","chromium"]),y.existsSync(r)?an(void 0):Es({kind:"playwright-install-failed"})}async function Ds(e){let r=Fs({cwd:e,pm:Ws(e)});_.info("Installing dependencies: %s",r);try{return await As(r,{cwd:e}),{ok:!0}}catch(t){let n=t instanceof Error?t.message.split(`
101
- `)[0]??t.message:String(t);return _.warn("Install failed (%s): %s",r,n),{cmd:r,ok:!1,reason:n}}}async function Os(e){try{await Y(e);return}catch(r){return`Couldn't compile initial lockfile: ${r instanceof Error?r.message:String(r)}.`}}function _s(e){let r=g.join(e,".gitattributes"),t=y.existsSync(r)?y.readFileSync(r,"utf8"):"";if(t.includes(ln))return;let n=t.length===0||t.endsWith(`
100
+ `)})}import Z from"fs";import ee from"path";import{input as mn,select as fn}from"@inquirer/prompts";import{graphql as Zs}from"gql.tada";import{exec as Cs,execFile as Ps}from"child_process";import{err as Es,ok as an}from"neverthrow";import k from"fs";import{createRequire as $s}from"module";import g from"path";import{promisify as dn}from"util";import{writeFile as xs}from"fs/promises";import Rs from"path";async function Y(e){let r=await S(e);return r.isOk()&&await xs(Rs.join(e,D),z(M,r.value)),r}function U(e){return e.workflows.filter(r=>r.stub).map(r=>r.name)}var Is=["@ripplo/testing","@ripplo/instrument"],ln=".ripplo/ripplo.lock linguist-generated=true",As=[".ripplo/debug/",".ripplo/.local/"],js=dn(Cs),Ls=dn(Ps);async function cn({cwd:e,onStep:r}){r("Scaffolding project files..."),qs({cwd:e}),r("Updating .gitignore..."),Bs(e),r("Marking ripplo.lock as generated..."),_s(e),r("Installing dependencies...");let t=await Ds(e),n=[];if(t.ok||n.push({manualCommand:t.cmd,message:`Couldn't auto-install dev dependencies (${t.reason}). Run the command below, then run \`npx ripplo lint\` to compile the lockfile.`}),t.ok){r("Compiling initial lockfile...");let i=await Os(e);i!=null&&n.push({manualCommand:void 0,message:i})}return r("Setting up browser..."),(await Ts()).map(()=>n)}async function Ts(){let{chromium:e}=await import("playwright"),r=e.executablePath();if(k.existsSync(r))return an(void 0);_.info("Chromium not found. Installing via Playwright...");let t=$s(import.meta.url),n=g.dirname(t.resolve("playwright/package.json")),o=g.join(n,"cli.js");return await Ls(process.execPath,[o,"install","chromium"]),k.existsSync(r)?an(void 0):Es({kind:"playwright-install-failed"})}async function Ds(e){let r=Fs({cwd:e,pm:Ws(e)});_.info("Installing dependencies: %s",r);try{return await js(r,{cwd:e}),{ok:!0}}catch(t){let n=t instanceof Error?t.message.split(`
101
+ `)[0]??t.message:String(t);return _.warn("Install failed (%s): %s",r,n),{cmd:r,ok:!1,reason:n}}}async function Os(e){try{await Y(e);return}catch(r){return`Couldn't compile initial lockfile: ${r instanceof Error?r.message:String(r)}.`}}function _s(e){let r=g.join(e,".gitattributes"),t=k.existsSync(r)?k.readFileSync(r,"utf8"):"";if(t.includes(ln))return;let n=t.length===0||t.endsWith(`
102
102
  `)?"":`
103
- `;y.writeFileSync(r,`${t}${n}${ln}
104
- `)}function Fs({cwd:e,pm:r}){let t=Is.join(" ");return r==="pnpm"?Ns(e)?`pnpm add -wD ${t}`:`pnpm add -D ${t}`:r==="yarn"?Us({cwd:e,deps:t}):r==="bun"?`bun add -d ${t}`:`npm install -D ${t}`}function Ns(e){return y.existsSync(g.join(e,"pnpm-workspace.yaml"))||y.existsSync(g.join(e,"pnpm-workspace.yml"))}function Us({cwd:e,deps:r}){return Ms(e)?`yarn add -D ${r}`:Hs(e)?`yarn add -WD ${r}`:`yarn add -D ${r}`}function Ms(e){if(y.existsSync(g.join(e,".yarnrc.yml"))||y.existsSync(g.join(e,".pnp.cjs"))||y.existsSync(g.join(e,".pnp.js")))return!0;let r=g.join(e,"package.json");if(!y.existsSync(r))return!1;try{let t=JSON.parse(y.readFileSync(r,"utf8"));if(t==null||typeof t!="object"||!("packageManager"in t))return!1;let n=t.packageManager;if(typeof n!="string")return!1;let o=/^yarn@(\d+)/.exec(n);return o!=null&&Number(o[1])>=2}catch{return!1}}function Hs(e){let r=g.join(e,"package.json");if(!y.existsSync(r))return!1;try{let t=JSON.parse(y.readFileSync(r,"utf8"));if(t==null||typeof t!="object"||!("workspaces"in t))return!1;let n=t.workspaces;return Array.isArray(n)||n!=null&&typeof n=="object"}catch{return!1}}function Ws(e){return y.existsSync(g.join(e,"pnpm-lock.yaml"))?"pnpm":y.existsSync(g.join(e,"yarn.lock"))?"yarn":y.existsSync(g.join(e,"bun.lockb"))||y.existsSync(g.join(e,"bun.lock"))?"bun":"npm"}function qs({cwd:e}){let r=g.join(e,".ripplo"),t=g.join(r,"entities"),n=g.join(r,"singletons"),o=g.join(r,"worlds"),i=g.join(r,"workflows");[t,n,o,i].forEach(a=>{y.mkdirSync(a,{recursive:!0})}),X(g.join(r,"index.ts"),Vs),X(g.join(t,"index.ts"),Gs),X(g.join(n,"index.ts"),zs),X(g.join(o,"index.ts"),Js),X(g.join(i,"index.ts"),Ks),X(g.join(r,"tsconfig.json"),Qs)}function X(e,r){y.existsSync(e)||y.writeFileSync(e,r)}function Bs(e){let r=g.join(e,".gitignore");if(!y.existsSync(r))return;let t=y.readFileSync(r,"utf8"),n=js.filter(i=>!t.includes(i));if(n.length===0)return;let o=t.endsWith(`
103
+ `;k.writeFileSync(r,`${t}${n}${ln}
104
+ `)}function Fs({cwd:e,pm:r}){let t=Is.join(" ");return r==="pnpm"?Ns(e)?`pnpm add -wD ${t}`:`pnpm add -D ${t}`:r==="yarn"?Us({cwd:e,deps:t}):r==="bun"?`bun add -d ${t}`:`npm install -D ${t}`}function Ns(e){return k.existsSync(g.join(e,"pnpm-workspace.yaml"))||k.existsSync(g.join(e,"pnpm-workspace.yml"))}function Us({cwd:e,deps:r}){return Ms(e)?`yarn add -D ${r}`:Hs(e)?`yarn add -WD ${r}`:`yarn add -D ${r}`}function Ms(e){if(k.existsSync(g.join(e,".yarnrc.yml"))||k.existsSync(g.join(e,".pnp.cjs"))||k.existsSync(g.join(e,".pnp.js")))return!0;let r=g.join(e,"package.json");if(!k.existsSync(r))return!1;try{let t=JSON.parse(k.readFileSync(r,"utf8"));if(t==null||typeof t!="object"||!("packageManager"in t))return!1;let n=t.packageManager;if(typeof n!="string")return!1;let o=/^yarn@(\d+)/.exec(n);return o!=null&&Number(o[1])>=2}catch{return!1}}function Hs(e){let r=g.join(e,"package.json");if(!k.existsSync(r))return!1;try{let t=JSON.parse(k.readFileSync(r,"utf8"));if(t==null||typeof t!="object"||!("workspaces"in t))return!1;let n=t.workspaces;return Array.isArray(n)||n!=null&&typeof n=="object"}catch{return!1}}function Ws(e){return k.existsSync(g.join(e,"pnpm-lock.yaml"))?"pnpm":k.existsSync(g.join(e,"yarn.lock"))?"yarn":k.existsSync(g.join(e,"bun.lockb"))||k.existsSync(g.join(e,"bun.lock"))?"bun":"npm"}function qs({cwd:e}){let r=g.join(e,".ripplo"),t=g.join(r,"entities"),n=g.join(r,"singletons"),o=g.join(r,"worlds"),i=g.join(r,"workflows");[t,n,o,i].forEach(s=>{k.mkdirSync(s,{recursive:!0})}),X(g.join(r,"index.ts"),Vs),X(g.join(t,"index.ts"),Gs),X(g.join(n,"index.ts"),zs),X(g.join(o,"index.ts"),Js),X(g.join(i,"index.ts"),Ks),X(g.join(r,"tsconfig.json"),Qs)}function X(e,r){k.existsSync(e)||k.writeFileSync(e,r)}function Bs(e){let r=g.join(e,".gitignore");if(!k.existsSync(r))return;let t=k.readFileSync(r,"utf8"),n=As.filter(i=>!t.includes(i));if(n.length===0)return;let o=t.endsWith(`
105
105
  `)?"":`
106
- `;y.writeFileSync(r,t+o+n.join(`
106
+ `;k.writeFileSync(r,t+o+n.join(`
107
107
  `)+`
108
108
  `)}var Vs=`import { createRipplo } from "@ripplo/testing";
109
109
  import { entities } from "./entities/index";
@@ -183,21 +183,21 @@ export const workflows = [] as const;
183
183
  "include": ["*.ts", "entities/**/*.ts", "singletons/**/*.ts", "worlds/**/*.ts", "workflows/**/*.ts"],
184
184
  "exclude": ["node_modules"]
185
185
  }
186
- `;import Ie from"fs";import pn from"path";import{z as ar}from"zod";var Ys={autoUpdate:!0,source:{repo:"ripplo/claude-plugin",source:"github"}},un=ar.record(ar.string(),ar.unknown());function je(e){let r=pn.join(e,".claude","settings.json"),t=Xs(r);if(t==null)return"unparseable";let n=un.safeParse(t.extraKnownMarketplaces??{});if(!n.success)return"unparseable";if(n.data.ripplo!=null)return"already-present";let o={...t,extraKnownMarketplaces:{...n.data,ripplo:Ys}};return Ie.mkdirSync(pn.dirname(r),{recursive:!0}),Ie.writeFileSync(r,`${JSON.stringify(o,null,2)}
187
- `),"written"}function Xs(e){if(!Ie.existsSync(e))return{};try{let r=un.safeParse(JSON.parse(Ie.readFileSync(e,"utf8")));return r.success?r.data:void 0}catch{return}}function Ae(e){switch(e){case"written":return" registered ripplo plugin marketplace in .claude/settings.json (autoUpdate: true \u2014 Claude Code keeps the plugin current)";case"already-present":return" ripplo plugin marketplace already registered in .claude/settings.json";case"unparseable":return" ! .claude/settings.json could not be parsed \u2014 add the ripplo marketplace to extraKnownMarketplaces manually"}}var ea=Zs(`
186
+ `;import Ie from"fs";import pn from"path";import{z as ar}from"zod";var Ys={autoUpdate:!0,source:{repo:"ripplo/claude-plugin",source:"github"}},un=ar.record(ar.string(),ar.unknown());function Ae(e){let r=pn.join(e,".claude","settings.json"),t=Xs(r);if(t==null)return"unparseable";let n=un.safeParse(t.extraKnownMarketplaces??{});if(!n.success)return"unparseable";if(n.data.ripplo!=null)return"already-present";let o={...t,extraKnownMarketplaces:{...n.data,ripplo:Ys}};return Ie.mkdirSync(pn.dirname(r),{recursive:!0}),Ie.writeFileSync(r,`${JSON.stringify(o,null,2)}
187
+ `),"written"}function Xs(e){if(!Ie.existsSync(e))return{};try{let r=un.safeParse(JSON.parse(Ie.readFileSync(e,"utf8")));return r.success?r.data:void 0}catch{return}}function je(e){switch(e){case"written":return" registered ripplo plugin marketplace in .claude/settings.json (autoUpdate: true \u2014 Claude Code keeps the plugin current)";case"already-present":return" ripplo plugin marketplace already registered in .claude/settings.json";case"unparseable":return" ! .claude/settings.json could not be parsed \u2014 add the ripplo marketplace to extraKnownMarketplaces manually"}}var ea=Zs(`
188
188
  query InitProjects {
189
189
  projects {
190
190
  id
191
191
  name
192
192
  }
193
193
  }
194
- `),lr=["../.env.local","../.env"];async function gn(e=na()){let r=process.cwd(),t=j(),n=F(t);n==null&&(process.stdout.write("Not signed in. Run `npx ripplo auth login` first.\n"),process.exit(1)),ra(r)&&(process.stdout.write(`\`.ripplo/index.ts\` already exists at ${r}. To re-init, delete it first.
195
- `),process.exit(1));let o=await ta({serverUrl:t,token:n});o.length===0&&(process.stdout.write("No projects found. Create one in the dashboard first, then re-run `npx ripplo init`.\n"),process.exit(1));let i=await ia(o,e.projectId),a=await sa(r,e.envFile),s=await la(e.appUrl),l=oa(s,e.engineUrl),d=ee.resolve(ee.join(r,".ripplo"),a);Ir({cwd:r,envFiles:[a],projectId:i}),ca({appUrl:s,engineUrl:l,filePath:d}),process.stdout.write(`${Ae(je(r))}
196
- `);let p=(await cn({cwd:r,onStep:k=>{process.stdout.write(` ${k}
197
- `)}})).match(k=>k,k=>{process.stderr.write(`${$(k)}
194
+ `),lr=["../.env.local","../.env"];async function gn(e=na()){let r=process.cwd(),t=A(),n=F(t);n==null&&(process.stdout.write("Not signed in. Run `npx ripplo auth login` first.\n"),process.exit(1)),ra(r)&&(process.stdout.write(`\`.ripplo/index.ts\` already exists at ${r}. To re-init, delete it first.
195
+ `),process.exit(1));let o=await ta({serverUrl:t,token:n});o.length===0&&(process.stdout.write("No projects found. Create one in the dashboard first, then re-run `npx ripplo init`.\n"),process.exit(1));let i=await ia(o,e.projectId),s=await sa(r,e.envFile),a=await la(e.appUrl),l=oa(a,e.engineUrl),d=ee.resolve(ee.join(r,".ripplo"),s);Ir({cwd:r,envFiles:[s],projectId:i}),ca({appUrl:a,engineUrl:l,filePath:d}),process.stdout.write(`${je(Ae(r))}
196
+ `);let p=(await cn({cwd:r,onStep:y=>{process.stdout.write(` ${y}
197
+ `)}})).match(y=>y,y=>{process.stderr.write(`${I(y)}
198
198
  `),process.exit(1)});if(p.length>0){process.stdout.write(`Done with warnings:
199
- `),p.forEach(k=>{process.stdout.write(` - ${k.message}
200
- `),k.manualCommand!=null&&process.stdout.write(` run: ${k.manualCommand}
199
+ `),p.forEach(y=>{process.stdout.write(` - ${y.message}
200
+ `),y.manualCommand!=null&&process.stdout.write(` run: ${y.manualCommand}
201
201
  `)});return}process.stdout.write("Ready. Start `npx ripplo daemon` as a background process (or run `/ripplo:start` in Claude Code), then write workflows in `.ripplo/workflows/`.\n")}function ra(e){return Z.existsSync(ee.join(e,".ripplo","index.ts"))}async function ta({serverUrl:e,token:r}){return((await u({config:H({serverUrl:e,token:r}),document:ea,variables:void 0})).projects??[]).map(n=>({id:n.id,name:n.name}))}function na(){return{appUrl:void 0,engineUrl:void 0,envFile:void 0,projectId:void 0}}function oa(e,r){if(r!=null){try{new URL(r)}catch{process.stdout.write(`--engine-url is not a valid URL: ${r}
202
202
  `),process.exit(1)}return r}return`${e.replace(/\/$/,"")}/ripplo`}async function ia(e,r){if(r!=null){let t=e.find(n=>n.id===r);return t==null&&(process.stdout.write(`Unknown project id: ${r}
203
203
  `),process.exit(1)),process.stdout.write(`Using project: ${t.name} (${t.id})
@@ -209,32 +209,32 @@ export const workflows = [] as const;
209
209
  `;Z.writeFileSync(t,`${n}${i}${o.join(`
210
210
  `)}
211
211
  `)}function Le(e){let r=e.workflows.filter(o=>!o.stub);if(r.length===0)return[];let t=new Set(r.flatMap(o=>pa(o))),n=new Set(r.flatMap(o=>ua(o)));return[...e.entities.filter(o=>!t.has(o.name)).map(o=>({entity:o.name,kind:"entity-never-given"})),...e.entities.filter(o=>t.has(o.name)&&!n.has(o.name)).map(o=>({entity:o.name,kind:"entity-never-mutated"})),...r.flatMap(o=>o.tests.flatMap(i=>ma(i)))]}function pa(e){return[...e.world,...e.maybe].map(r=>r.entity)}function ua(e){return e.steps.flatMap(r=>r.expect.flatMap(t=>ue(t))).map(r=>r.entity)}function ma(e){let r=e.steps.flatMap(t=>t.expect.flatMap(n=>ue(n)));return fa({states:r,test:e})}function fa({states:e,test:r}){let t=e.filter(n=>n.assertion.kind==="deleted").flatMap(n=>ga({predicate:n,test:r})).map(({entity:n,field:o})=>({entity:n,field:o,kind:"unmatchable-delete-key",testSlug:r.slug,workflow:r.workflow}));return ka(t)}function ga({predicate:e,test:r}){let t=r.world.filter(n=>n.entity===e.entity);return t.length===0?[]:Object.entries(e.key).filter(([n,o])=>ya(o)&&!ha({field:n,setups:t})).map(([n])=>({entity:e.entity,field:n}))}function ha({field:e,setups:r}){return r.some(t=>Object.keys(t.set).includes(e))}function ya(e){return e===null||typeof e!="object"}function ka(e){return e.filter((r,t)=>e.findIndex(n=>JSON.stringify(n)===JSON.stringify(r))===t)}function ue(e){return e.kind==="state"?[e]:e.kind==="not"?ue(e.predicate):e.kind==="and"?e.predicates.flatMap(r=>ue(r)):e.kind==="when"?e.branches.flatMap(r=>r.consequence.flatMap(t=>ue(t))):[]}async function hn(){let e=process.cwd(),t=(await S(e)).match(o=>o,o=>wa(x(o))),n=W(t);if(n.length===0){let o=Le(t);o.length>0&&process.stdout.write(`${Ee(o)}
212
- `);let i=t.workflows.reduce((a,s)=>a+s.tests.length,0);process.stdout.write(`${Yt(i)}
212
+ `);let i=t.workflows.reduce((s,a)=>s+a.tests.length,0);process.stdout.write(`${Yt(i)}
213
213
  `);return}process.stderr.write(`${q(n)}
214
214
  `),process.exit(1)}function wa(e){process.stderr.write(`${e}
215
- `),process.exit(1)}import{CancellationTokenSource as Xa}from"vscode-jsonrpc/node";import Ca from"path";import{randomUUID as va}from"crypto";import ba from"path";async function Te({assign:e,config:r,headed:t,lockfile:n,session:o,signal:i}){let a=J(n),s=await xa({assign:e,corpus:a,lockfile:n});if(s==null)return{kind:"error",reason:"base-state-arrange-failed",rows:[],trail:[]};if(s.stepRuns.length===0)return{kind:"error",reason:"empty-trail",rows:[],trail:[]};let l=Sa(a,s.stepRuns),d=await tt({attemptTimeoutMs:it,baseState:{name:Xe(e.baseStateTest),test:e.baseStateTest},corpus:a,lensId:e.lensId,lockfile:n,lockfileHash:e.lockfileHash,options:{baseUrl:r.appUrl,engineUrl:r.engineUrl,fixturesDir:ba.join(r.cwd,we),generate:be,headed:t,secret:r.webhookSecret},session:o,shrinkBudget:e.shrinkBudget,trail:s,now:()=>new Date().toISOString(),runIdFor:h=>`explore-${va()}-${String(h)}`},i);return Ra(d,l)}function Sa(e,r){return r.flatMap(t=>{let n=e[t.idx];return n==null?[]:[{actions:[...n.nav,...n.steps].map(o=>Kr(o.action)),label:`${n.test}#${String(n.index)}`}]})}async function xa({assign:e,corpus:r,lockfile:t}){return(await rt(t,{name:Xe(e.baseStateTest),test:e.baseStateTest},{generate:be,materialize:qr(be,t.valueSpaces),params:void 0})).match(o=>et({actionHashes:r.map(i=>xe(t,i)),baseStateSnapshot:o.snapshot,corpus:r,covered:new Set,lens:ot(t),lensId:e.lensId,maxLength:e.maxLength,witnessTrail:e.stepRuns}),()=>null)}function Ra(e,r){return e.kind==="error"?{kind:e.kind,reason:`runtime:${e.error.kind}`,rows:[],trail:[]}:e.kind==="timeout"?{kind:"error",reason:"trail-timeout",rows:[],trail:[]}:e.kind==="aborted"?{kind:e.kind,rows:[],trail:[]}:{kind:e.kind,rows:[...e.rows],trail:[...r]}}var Pa={covered:0,deferred:0,findings:0,saturated:!1,total:0};async function yn(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 t=r.config,n=await Tr(e.cwd);if(n.result.isErr())return{error:n.result.error,kind:"compile-failed"};let o={fingerprint:n.fingerprint,lockfile:n.result.value},i=nt({onChange:()=>{}});if(!i.holder())return i.stop(),{kind:"explorer-busy"};let a=Se({clientVersion:C(),debugDir:Ca.join(e.cwd,".ripplo","debug"),headed:e.headed,writeOtlpPortFile:!1}),s={executed:0,status:()=>Pa},l=st({cwd:e.cwd,maxTrailLength:e.maxLength,executeTrail:(d,h)=>Te({assign:d,config:t,headed:e.headed,lockfile:o.lockfile,session:a,signal:h}),loadLockfile:()=>Promise.resolve(o),notifyWork:()=>{},onTrailDone:d=>{s.executed+=1,e.onTrail(s.executed,d,s.status())},probeApp:async()=>await ae(t.appUrl)==null});s.status=()=>l.status();try{await l.ready(),e.onReady(l.status());let d=await Ea(l,e),h=l.status();return await l.stop(),await a.close(),d===0&&h.total===0?{kind:"no-work"}:{executed:d,kind:"completed",progress:h}}finally{i.stop()}}async function Ea(e,r){let t=new AbortController().signal,n=async o=>{if(o>=r.trails)return o;let i=e.next();return i==null?o:(await i.run(t),n(o+1))};return n(0)}import{okAsync as $a}from"neverthrow";function kn(e,r,t){let n=rr(e);return er(n).andThen(o=>{let i=Ia(o,r);return i==null?$a({id:r,kind:"not-found"}):Xr(n,[{at:t,kind:"resolution",signature:i}]).map(()=>({id:r,kind:"dismissed"}))})}function wn(e,r){let t=rr(e);return er(t).andThen(n=>{let o=new Set(J(r).map(d=>xe(r,d))),i=new Set(ja(n).filter(d=>!o.has(d.actionHash)).map(d=>d.signature)),a=Aa(n),s=n.filter(d=>La(d,o,a)),l=[...a].filter(d=>!i.has(d)).length;return Zr(t,s).map(()=>({kept:s.length,removedResolved:l,removedStale:i.size}))})}function Ia(e,r){return[...Ze(e).findings.entries()].find(([n,o])=>o.resolvedAt==null&&Yr(n)===r)?.[0]}function ja(e){return e.filter(r=>r.kind==="finding")}function Aa(e){return new Set([...Ze(e).findings.entries()].filter(([,r])=>r.resolvedAt!=null).map(([r])=>r))}function La(e,r,t){return e.kind==="resolution"?!1:e.kind==="finding"?r.has(e.actionHash)&&!t.has(e.signature):!0}function vn(e){if(e.pending.length===0&&e.recurrentFlaky.length===0)return"explore: no pending findings";let r=e.pending.length===0?[]:[`explore: ${b(e.pending.length,"pending finding")} \u2014 triage in layer order, top first`,...e.pending.map(o=>Ta(o))],t=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=>_a(o))],n=c("fuzz","triaging each finding (evidence -> classify -> fix -> replay)");return[...r,...t,"","replay after a fix: npx ripplo explore replay <id>",n].join(`
215
+ `),process.exit(1)}import{CancellationTokenSource as Xa}from"vscode-jsonrpc/node";import Ca from"path";import{randomUUID as va}from"crypto";import ba from"path";async function Te({assign:e,config:r,headed:t,lockfile:n,session:o,signal:i}){let s=J(n),a=await xa({assign:e,corpus:s,lockfile:n});if(a==null)return{kind:"error",reason:"base-state-arrange-failed",rows:[],trail:[]};if(a.stepRuns.length===0)return{kind:"error",reason:"empty-trail",rows:[],trail:[]};let l=Sa(s,a.stepRuns),d=await tt({attemptTimeoutMs:it,baseState:{name:Xe(e.baseStateTest),test:e.baseStateTest},corpus:s,lensId:e.lensId,lockfile:n,lockfileHash:e.lockfileHash,options:{baseUrl:r.appUrl,engineUrl:r.engineUrl,fixturesDir:ba.join(r.cwd,we),generate:be,headed:t,secret:r.webhookSecret,tunnelAuth:void 0},session:o,shrinkBudget:e.shrinkBudget,trail:a,now:()=>new Date().toISOString(),runIdFor:h=>`explore-${va()}-${String(h)}`},i);return Ra(d,l)}function Sa(e,r){return r.flatMap(t=>{let n=e[t.idx];return n==null?[]:[{actions:[...n.nav,...n.steps].map(o=>Kr(o.action)),label:`${n.test}#${String(n.index)}`}]})}async function xa({assign:e,corpus:r,lockfile:t}){return(await rt(t,{name:Xe(e.baseStateTest),test:e.baseStateTest},{generate:be,materialize:qr(be,t.valueSpaces),params:void 0})).match(o=>et({actionHashes:r.map(i=>xe(t,i)),baseStateSnapshot:o.snapshot,corpus:r,covered:new Set,lens:ot(t),lensId:e.lensId,maxLength:e.maxLength,witnessTrail:e.stepRuns}),()=>null)}function Ra(e,r){return e.kind==="error"?{kind:e.kind,reason:`runtime:${e.error.kind}`,rows:[],trail:[]}:e.kind==="timeout"?{kind:"error",reason:"trail-timeout",rows:[],trail:[]}:e.kind==="aborted"?{kind:e.kind,rows:[],trail:[]}:{kind:e.kind,rows:[...e.rows],trail:[...r]}}var Pa={covered:0,deferred:0,findings:0,saturated:!1,total:0};async function yn(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 t=r.config,n=await Tr(e.cwd);if(n.result.isErr())return{error:n.result.error,kind:"compile-failed"};let o={fingerprint:n.fingerprint,lockfile:n.result.value},i=nt({onChange:()=>{}});if(!i.holder())return i.stop(),{kind:"explorer-busy"};let s=Se({clientVersion:C(),debugDir:Ca.join(e.cwd,".ripplo","debug"),headed:e.headed,writeOtlpPortFile:!1}),a={executed:0,status:()=>Pa},l=st({cwd:e.cwd,maxTrailLength:e.maxLength,executeTrail:(d,h)=>Te({assign:d,config:t,headed:e.headed,lockfile:o.lockfile,session:s,signal:h}),loadLockfile:()=>Promise.resolve(o),notifyWork:()=>{},onTrailDone:d=>{a.executed+=1,e.onTrail(a.executed,d,a.status())},probeApp:async()=>await ae(t.appUrl)==null});a.status=()=>l.status();try{await l.ready(),e.onReady(l.status());let d=await Ea(l,e),h=l.status();return await l.stop(),await s.close(),d===0&&h.total===0?{kind:"no-work"}:{executed:d,kind:"completed",progress:h}}finally{i.stop()}}async function Ea(e,r){let t=new AbortController().signal,n=async o=>{if(o>=r.trails)return o;let i=e.next();return i==null?o:(await i.run(t),n(o+1))};return n(0)}import{okAsync as $a}from"neverthrow";function kn(e,r,t){let n=rr(e);return er(n).andThen(o=>{let i=Ia(o,r);return i==null?$a({id:r,kind:"not-found"}):Xr(n,[{at:t,kind:"resolution",signature:i}]).map(()=>({id:r,kind:"dismissed"}))})}function wn(e,r){let t=rr(e);return er(t).andThen(n=>{let o=new Set(J(r).map(d=>xe(r,d))),i=new Set(Aa(n).filter(d=>!o.has(d.actionHash)).map(d=>d.signature)),s=ja(n),a=n.filter(d=>La(d,o,s)),l=[...s].filter(d=>!i.has(d)).length;return Zr(t,a).map(()=>({kept:a.length,removedResolved:l,removedStale:i.size}))})}function Ia(e,r){return[...Ze(e).findings.entries()].find(([n,o])=>o.resolvedAt==null&&Yr(n)===r)?.[0]}function Aa(e){return e.filter(r=>r.kind==="finding")}function ja(e){return new Set([...Ze(e).findings.entries()].filter(([,r])=>r.resolvedAt!=null).map(([r])=>r))}function La(e,r,t){return e.kind==="resolution"?!1:e.kind==="finding"?r.has(e.actionHash)&&!t.has(e.signature):!0}function vn(e){if(e.pending.length===0&&e.recurrentFlaky.length===0)return"explore: no pending findings";let r=e.pending.length===0?[]:[`explore: ${b(e.pending.length,"pending finding")} \u2014 triage in layer order, top first`,...e.pending.map(o=>Ta(o))],t=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=>_a(o))],n=c("fuzz","triaging each finding (evidence -> classify -> fix -> replay)");return[...r,...t,"","replay after a fix: npx ripplo explore replay <id>",n].join(`
216
216
  `).trim()}function bn(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 t=r.runId==null?"":` (fresh evidence: run ${r.runId})`;return`explore: ${e} still reproduces \u2014 same failure signature${t}`}case"diverged":{let t=r.runId==null?"":` (captured run ${r.runId})`;return`explore: ${e} failed with a different failure signature \u2014 new finding recorded${t}`}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 Sn({executed:e,progress:r,trail:t}){let n=t.trail.flatMap(o=>[` ${o.label}`,...o.actions.map(i=>` ${i}`)]);return[`trail ${String(e)} ${t.kind} \u2014 ${de(r)}`,...n,` state: ${t.label}`].join(`
217
- `)}function xn(e){switch(e.kind){case"config-failed":return $(e.failure);case"compile-failed":return x(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`}
217
+ `)}function xn(e){switch(e.kind){case"config-failed":return I(e.failure);case"compile-failed":return x(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`}
218
218
  ${de(e.progress)}
219
219
  findings land in .ripplo/.local/explore-ledger.jsonl`}}function Rn(e){let r=e.evidence.map(n=>` ${n}`),t=e.runId==null?[" no captured run \u2014 replay to capture one"]:[` run: ${e.runId}`,` behavior: .ripplo/debug/${e.runId}/behavior.jsonl`];return[`${e.id} layer=${e.verifierLayer} start=${e.baseState}`,` seen ${String(e.occurrences)}x between ${dr(e.firstSeen)} and ${dr(e.lastSeen)}`,` trail: ${e.trail.join(" -> ")}`," evidence:",...r,...t,` replay after a fix: npx ripplo explore replay ${e.id}`].join(`
220
220
  `)}function cr(e){return`explore: no pending finding ${e} \u2014 check ids with: npx ripplo explore findings`}function Cn(e){return e.kind==="not-found"?cr(e.id):`explore: dismissed finding ${e.id}. Prune the log to drop it: npx ripplo explore prune`}function Pn(e){if(e.removedStale+e.removedResolved===0)return"explore: nothing to prune \u2014 the findings log has no stale or dismissed entries.";let t=b(e.removedStale,"stale finding"),n=b(e.removedResolved,"dismissed finding");return`explore: pruned ${t} and ${n}, ${b(e.kept,"row")} kept.`}function Ta(e){let r=e.runId==null?"no captured run":`run ${e.runId}`;return[` ${e.id} layer=${e.verifierLayer} seen ${String(e.occurrences)}x (last ${dr(e.lastSeen)}) start=${e.baseState} ${r}`,` mismatch: ${Da(e.parts)}`,` trail: ${e.trail.join(" -> ")}`].join(`
221
221
  `)}function dr(e){return e.slice(0,16).replace("T"," ")}function Da(e){let r=e.at(0);if(r==null)return"no parts recorded";let t=e.length>1?` (+${String(e.length-1)} more)`:"";return`${Oa(r)}${t}`}function Oa(e){switch(e.kind){case"consistency":{let r="entity"in e.mismatch?e.mismatch.entity:e.mismatch.singleton,t=e.step==null?"":` at "${e.step.intent}"`;return`${e.mismatch.kind} on ${r}${t}`}case"pending-check":return`${e.source} check failed at "${e.step.intent}"`;case"unrunnable":return`step "${e.intent}" could not run (${e.reason})`;case"driver-error":return`${e.error} at "${e.step}"`;case"impossible-action":return`impossible action at "${e.step}"`}}function _a(e){let r=e.trail.map(t=>t.split("|").at(0)??t);return` ${e.id} seen ${String(e.occurrences)}x start=${e.baseState}
222
- trail: ${r.join(" -> ")}`}import{spawn as Fa}from"child_process";import En from"fs";import Na from"net";import{setTimeout as pr}from"timers/promises";import{err as re,ok as me}from"neverthrow";import{ResponseError as Ua}from"vscode-jsonrpc/node";var In=12e4,jn=300,$n=5e3;async function te({cliEntry:e,cwd:r}){let t=K(r),n=await G(t);return n!=null?Ma({cliEntry:e,connection:_e(n,!1),cwd:r}):An({cliEntry:e,cwd:r,versionNote:void 0})}async function An({cliEntry:e,cwd:r,versionNote:t}){let n=Ga({cliEntry:e,cwd:r});if(n!=null)return re(n);let o=await za(K(r));return o==null?re({deadlineMs:In,kind:"connect-timeout",logPath:tr(r)}):me({..._e(o,!0),versionNote:t})}async function Ma({cliEntry:e,connection:r,cwd:t}){let n=await Dn(r);if(n==null||n.version===C())return me(r);let o={daemonVersion:n.version};return await Ha(r)?(r.socket.destroy(),await qa(K(t))?An({cliEntry:e,cwd:t,versionNote:{...o,kind:"restarted"}}):re({kind:"connection-lost"})):me({...r,versionNote:{...o,kind:"stale-busy"}})}async function Ha(e){try{return await e.rpc.sendRequest(Re)}catch{return!1}}var Wa=1e4;async function qa(e){let r=Date.now()+Wa,t=await G(e);for(;t!=null&&Date.now()<r;)t.destroy(),await pr(jn),t=await G(e);return t?.destroy(),t==null}async function De({connection:e,onEvent:r,request:t,token:n}){let o=await Ba({connection:e,onEvent:r,request:t,token:n});return o.kind==="transport"?re(o.error):me(o)}async function Ln({connection:e,findingId:r,token:t}){try{let n=await e.rpc.sendRequest(kt,{findingId:r},t),o=yt.safeParse(n);return o.success?me(o.data):re({kind:"bad-frame"})}catch{return re({kind:"connection-lost"})}}async function Tn(e){let r=await G(K(e));if(r==null)return!1;let t=_e(r,!1);try{return await t.rpc.sendRequest(Re)}catch{return!1}finally{r.destroy()}}async function ne(e){try{await e.rpc.sendRequest(Re)}catch{}}async function Oe(e){let r=await G(K(e));if(r==null)return{kind:"not-running"};let t=_e(r,!1),n=await Promise.race([Dn(t),pr($n).then(()=>null)]);return r.destroy(),n==null?{kind:"unresponsive",timeoutMs:$n}:{kind:"running",status:n}}async function Dn(e){try{let r=await e.rpc.sendRequest(vt),t=mt.safeParse(r);return t.success?t.data:null}catch{return null}}function Ba({connection:e,onEvent:r,request:t,token:n}){let{rpc:o}=e;return new Promise(i=>{let a=s=>{i(s)};o.onNotification(bt,s=>{let l=ft.safeParse(s);if(!l.success){a({error:{kind:"bad-frame"},kind:"transport"});return}r(l.data.event)}),o.onNotification(St,s=>{let l=gt.safeParse(s);if(!l.success){a({error:{kind:"bad-frame"},kind:"transport"});return}a({failed:l.data.failed,kind:"done",notRun:l.data.notRun,passed:l.data.passed})}),o.onClose(()=>{a({error:{kind:"connection-lost"},kind:"transport"})}),o.sendRequest(wt,t,n).then(s=>{ut.safeParse(s).success||a({error:{kind:"bad-frame"},kind:"transport"})}).catch(s=>{a(Va(s))})})}function Va(e){if(e instanceof Ua){let r=ht.safeParse(e.data);return r.success?{error:r.data,kind:"daemon-error"}:{error:{kind:"bad-frame"},kind:"transport"}}return{error:{kind:"connection-lost"},kind:"transport"}}function _e(e,r){let t=pt(e);return t.listen(),{rpc:t,socket:e,spawned:r,versionNote:void 0}}function G(e){return new Promise(r=>{let t=Na.connect(e);t.once("connect",()=>{r(t)}),t.once("error",()=>{r(null)})})}function Ga({cliEntry:e,cwd:r}){try{ke(r);let t=En.openSync(tr(r),"a");return Fa(process.execPath,[e,"daemon"],{cwd:r,detached:!0,stdio:["ignore",t,t]}).unref(),En.closeSync(t),null}catch(t){return{kind:"spawn-failed",message:t instanceof Error?t.message:String(t)}}}async function za(e){let r=Date.now()+In,t=await G(e);for(;t==null&&Date.now()<r;)await pr(jn),t=await G(e);return t}import Ka from"fuse.js";var Ja=3e3;async function Fe(e){V(e);let r=w(e).unwrapOr(void 0);if(r==null)return null;try{return await fetch(`${r.ripploServerUrl}/health`,{signal:AbortSignal.timeout(Ja)}),null}catch(t){return{detail:t instanceof Error?t.message:String(t),serverUrl:r.ripploServerUrl}}}function Ne(e){return e.includes("localhost")||e.includes("127.0.0.1")}function ur(e){switch(e.kind){case"conflicting-flags":return"Pass either --all or test ids, not both.";case"nothing-selected":return`No tests selected \u2014 scope is empty and no .ripplo/workflows files are dirty. Pass test ids, add workflows to scope (${c("run")}), or use --all.`;case"unknown-ids":{let r=e.known,t=e.unknown.flatMap(o=>Qa(o,r)),n=t.length>0?[`Did you mean: ${[...new Set(t)].join(", ")}`]:[];return[`Unknown ${e.unknown.length===1?"id":"ids"}: ${e.unknown.join(", ")}`,...n,"Pass a workflow slug to run all its tests, or workflow/test for one. Known workflows:",...r.map(o=>` ${o}`)].join(`
222
+ trail: ${r.join(" -> ")}`}import{spawn as Fa}from"child_process";import En from"fs";import Na from"net";import{setTimeout as pr}from"timers/promises";import{err as re,ok as me}from"neverthrow";import{ResponseError as Ua}from"vscode-jsonrpc/node";var In=12e4,An=300,$n=5e3;async function te({cliEntry:e,cwd:r}){let t=K(r),n=await G(t);return n!=null?Ma({cliEntry:e,connection:_e(n,!1),cwd:r}):jn({cliEntry:e,cwd:r,versionNote:void 0})}async function jn({cliEntry:e,cwd:r,versionNote:t}){let n=Ga({cliEntry:e,cwd:r});if(n!=null)return re(n);let o=await za(K(r));return o==null?re({deadlineMs:In,kind:"connect-timeout",logPath:tr(r)}):me({..._e(o,!0),versionNote:t})}async function Ma({cliEntry:e,connection:r,cwd:t}){let n=await Dn(r);if(n==null||n.version===C())return me(r);let o={daemonVersion:n.version};return await Ha(r)?(r.socket.destroy(),await qa(K(t))?jn({cliEntry:e,cwd:t,versionNote:{...o,kind:"restarted"}}):re({kind:"connection-lost"})):me({...r,versionNote:{...o,kind:"stale-busy"}})}async function Ha(e){try{return await e.rpc.sendRequest(Re)}catch{return!1}}var Wa=1e4;async function qa(e){let r=Date.now()+Wa,t=await G(e);for(;t!=null&&Date.now()<r;)t.destroy(),await pr(An),t=await G(e);return t?.destroy(),t==null}async function De({connection:e,onEvent:r,request:t,token:n}){let o=await Ba({connection:e,onEvent:r,request:t,token:n});return o.kind==="transport"?re(o.error):me(o)}async function Ln({connection:e,findingId:r,token:t}){try{let n=await e.rpc.sendRequest(kt,{findingId:r},t),o=yt.safeParse(n);return o.success?me(o.data):re({kind:"bad-frame"})}catch{return re({kind:"connection-lost"})}}async function Tn(e){let r=await G(K(e));if(r==null)return!1;let t=_e(r,!1);try{return await t.rpc.sendRequest(Re)}catch{return!1}finally{r.destroy()}}async function ne(e){try{await e.rpc.sendRequest(Re)}catch{}}async function Oe(e){let r=await G(K(e));if(r==null)return{kind:"not-running"};let t=_e(r,!1),n=await Promise.race([Dn(t),pr($n).then(()=>null)]);return r.destroy(),n==null?{kind:"unresponsive",timeoutMs:$n}:{kind:"running",status:n}}async function Dn(e){try{let r=await e.rpc.sendRequest(vt),t=mt.safeParse(r);return t.success?t.data:null}catch{return null}}function Ba({connection:e,onEvent:r,request:t,token:n}){let{rpc:o}=e;return new Promise(i=>{let s=a=>{i(a)};o.onNotification(bt,a=>{let l=ft.safeParse(a);if(!l.success){s({error:{kind:"bad-frame"},kind:"transport"});return}r(l.data.event)}),o.onNotification(St,a=>{let l=gt.safeParse(a);if(!l.success){s({error:{kind:"bad-frame"},kind:"transport"});return}s({failed:l.data.failed,kind:"done",notRun:l.data.notRun,passed:l.data.passed})}),o.onClose(()=>{s({error:{kind:"connection-lost"},kind:"transport"})}),o.sendRequest(wt,t,n).then(a=>{ut.safeParse(a).success||s({error:{kind:"bad-frame"},kind:"transport"})}).catch(a=>{s(Va(a))})})}function Va(e){if(e instanceof Ua){let r=ht.safeParse(e.data);return r.success?{error:r.data,kind:"daemon-error"}:{error:{kind:"bad-frame"},kind:"transport"}}return{error:{kind:"connection-lost"},kind:"transport"}}function _e(e,r){let t=pt(e);return t.listen(),{rpc:t,socket:e,spawned:r,versionNote:void 0}}function G(e){return new Promise(r=>{let t=Na.connect(e);t.once("connect",()=>{r(t)}),t.once("error",()=>{r(null)})})}function Ga({cliEntry:e,cwd:r}){try{ke(r);let t=En.openSync(tr(r),"a");return Fa(process.execPath,[e,"daemon"],{cwd:r,detached:!0,stdio:["ignore",t,t]}).unref(),En.closeSync(t),null}catch(t){return{kind:"spawn-failed",message:t instanceof Error?t.message:String(t)}}}async function za(e){let r=Date.now()+In,t=await G(e);for(;t==null&&Date.now()<r;)await pr(An),t=await G(e);return t}import Ka from"fuse.js";var Ja=3e3;async function Fe(e){V(e);let r=w(e).unwrapOr(void 0);if(r==null)return null;try{return await fetch(`${r.ripploServerUrl}/health`,{signal:AbortSignal.timeout(Ja)}),null}catch(t){return{detail:t instanceof Error?t.message:String(t),serverUrl:r.ripploServerUrl}}}function Ne(e){return e.includes("localhost")||e.includes("127.0.0.1")}function ur(e){switch(e.kind){case"conflicting-flags":return"Pass either --all or test ids, not both.";case"nothing-selected":return`No tests selected \u2014 scope is empty and no .ripplo/workflows files are dirty. Pass test ids, add workflows to scope (${c("run")}), or use --all.`;case"unknown-ids":{let r=e.known,t=e.unknown.flatMap(o=>Qa(o,r)),n=t.length>0?[`Did you mean: ${[...new Set(t)].join(", ")}`]:[];return[`Unknown ${e.unknown.length===1?"id":"ids"}: ${e.unknown.join(", ")}`,...n,"Pass a workflow slug to run all its tests, or workflow/test for one. Known workflows:",...r.map(o=>` ${o}`)].join(`
223
223
  `)}}}function On({failed:e,notRun:r,passed:t}){let n=r>0?`, ${String(r)} not run`:"",o=e>0?`
224
224
  ${c("run")}`:"";return`${String(t)} passed, ${String(e)} failed${n} (${String(t+e+r)} total)${o}`}function Ue({debugDir:e,event:r}){let t=`${r.workflowName} \u2192 ${r.testName}`;if(r.kind==="test-started")return`${P.dim("run ")} ${t}`;switch(r.outcome.kind){case"pass":return`${P.good("pass")} ${t}`;case"findings":return[`${P.bad("fail")} ${t} \u2014 ${b(r.outcome.findingLines.length,"finding")}`,...r.outcome.findingLines,zr({debugDir:e,runId:r.runId})].join(`
225
225
  `);case"error":return`${P.bad("error")} ${t} \u2014 ${r.outcome.detail}`;case"dispatch-error":return`${P.bad("error")} ${t} \u2014 failed to dispatch (${r.outcome.detail})`;case"infra-error":return`${P.bad("error")} ${t} \u2014 not run: Ripplo server unreachable (server-side, not local): ${r.outcome.detail}`}}function Me({detail:e,serverUrl:r}){return Ne(r)?`Ripplo server at ${r} is not running (${e}). Tests were not started. Start the dev server (\`pnpm dev\`) and re-run.`:`Ripplo server at ${r} is unreachable (${e}). Tests were not started. This is a server-side issue, not your local environment \u2014 wait a moment and re-run.`}function He(e){switch(e.code){case"compile-failed":{let r=e.diagnostics.length===0?[]:["",...e.diagnostics];return[`Compilation failed in the daemon (${e.detail}). Run \`npx ripplo compile\` for the full output.`,...r].join(`
226
226
  `)}case"selection-conflicting-flags":return ur({kind:"conflicting-flags"});case"selection-nothing-selected":return ur({kind:"nothing-selected"});case"selection-unknown-ids":return ur({kind:"unknown-ids",known:e.known,unknown:e.unknown});case"app-unreachable":return`Your dev server is not responding at ${e.url} (${e.detail}). Tests were not started \u2014 start your app and re-run. This is your local environment, not the Ripplo server.`;case"scope-failed":return`Could not resolve the dev-session scope: ${e.detail}
227
227
  Verify the dev session is live (\`npx ripplo doctor\`, ${c("start")}), or pass test ids / --all explicitly.`;case"sync-failed":{let r=/401|unauthor/i.test(e.detail)?"\nLooks like an auth failure \u2014 run `npx ripplo auth login` and retry.":"";return`Sync to the Ripplo server failed: ${e.detail}${r}`}case"bad-message":return"Daemon rejected the request (protocol mismatch \u2014 rebuild/update the CLI and restart the daemon)."}}function T(e){switch(e.kind){case"spawn-failed":return`Failed to start \`npx ripplo daemon\`: ${Ya(e.message)}`;case"connect-timeout":return[`Daemon did not come up within ${String(Math.round(e.deadlineMs/1e3))}s.`,`Check ${e.logPath} for startup errors.`,"Common causes: a stale socket (`rm .ripplo/.local/daemon.sock`), another daemon holding the dev lock, or the Ripplo server unreachable."].join(`
228
228
  `);case"connection-lost":return["Lost the daemon connection mid-run \u2014 it likely crashed or was killed.","Check .ripplo/.local/daemon.log, then rerun (`npx ripplo run` auto-starts the daemon)."].join(`
229
- `);case"bad-frame":return"Received a malformed frame from the daemon (version mismatch \u2014 rebuild/update the CLI and restart the daemon)."}}function Qa(e,r){return new Ka(r,{includeScore:!0,threshold:.5}).search(A(e)).slice(0,3).map(n=>n.item)}function Ya(e){return e.includes("ENOENT")?`${e} \u2014 the node executable or CLI entry was not found on PATH.`:e.includes("EACCES")?`${e} \u2014 permission denied executing the CLI entry.`:e}async function _n({findingId:e,json:r}){await at(process.cwd()).match(t=>{if(e==null){let i=r?JSON.stringify(t,null,2):vn(t);process.stdout.write(`${i}
229
+ `);case"bad-frame":return"Received a malformed frame from the daemon (version mismatch \u2014 rebuild/update the CLI and restart the daemon)."}}function Qa(e,r){return new Ka(r,{includeScore:!0,threshold:.5}).search(j(e)).slice(0,3).map(n=>n.item)}function Ya(e){return e.includes("ENOENT")?`${e} \u2014 the node executable or CLI entry was not found on PATH.`:e.includes("EACCES")?`${e} \u2014 permission denied executing the CLI entry.`:e}async function _n({findingId:e,json:r}){await at(process.cwd()).match(t=>{if(e==null){let i=r?JSON.stringify(t,null,2):vn(t);process.stdout.write(`${i}
230
230
  `);return}let n=t.pending.find(i=>i.id===e);n==null&&(process.stderr.write(`${cr(e)}
231
231
  `),process.exit(1));let o=r?JSON.stringify(n,null,2):Rn(n);process.stdout.write(`${o}
232
232
  `)},t=>{process.stderr.write(`explore: findings log unreadable (${t.kind})
233
233
  `),process.exit(1)})}async function Fn({findingId:e}){let r=process.argv[1];r==null&&(process.stderr.write(`${T({kind:"spawn-failed",message:"process.argv[1] missing"})}
234
234
  `),process.exit(1));let t=await te({cliEntry:r,cwd:process.cwd()});t.isErr()&&(process.stderr.write(`${T(t.error)}
235
- `),process.exit(1));let n=t.value,o=new Xa;process.once("SIGINT",()=>{o.cancel(),n.socket.destroy(),process.exit(130)});let a=await(await Ln({connection:n,findingId:e,token:o.token})).match(async s=>(process.stdout.write(`${bn(e,s)}
236
- `),n.spawned&&await ne(n),s.kind==="resolved"||s.kind==="unreachable"?0:1),s=>(process.stderr.write(`${T(s)}
237
- `),Promise.resolve(1)));n.socket.destroy(),process.exit(a)}async function Nn({findingId:e}){let r=new Date().toISOString();await kn(process.cwd(),e,r).match(t=>{process.stdout.write(`${Cn(t)}
235
+ `),process.exit(1));let n=t.value,o=new Xa;process.once("SIGINT",()=>{o.cancel(),n.socket.destroy(),process.exit(130)});let s=await(await Ln({connection:n,findingId:e,token:o.token})).match(async a=>(process.stdout.write(`${bn(e,a)}
236
+ `),n.spawned&&await ne(n),a.kind==="resolved"||a.kind==="unreachable"?0:1),a=>(process.stderr.write(`${T(a)}
237
+ `),Promise.resolve(1)));n.socket.destroy(),process.exit(s)}async function Nn({findingId:e}){let r=new Date().toISOString();await kn(process.cwd(),e,r).match(t=>{process.stdout.write(`${Cn(t)}
238
238
  `),process.exit(t.kind==="dismissed"?0:1)},t=>{process.stderr.write(`explore: findings log unreadable (${t.kind})
239
239
  `),process.exit(1)})}async function Un(){let e=await S(process.cwd());e.isErr()&&(process.stderr.write(`${x(e.error)}
240
240
  `),process.exit(1)),await wn(process.cwd(),e.value).match(r=>{process.stdout.write(`${Pn(r)}
@@ -249,7 +249,7 @@ Verify the dev session is live (\`npx ripplo doctor\`, ${c("start")}), or pass t
249
249
  name
250
250
  }
251
251
  }
252
- `);async function Hn(){let e=j(),r=F(e);r==null&&(process.stderr.write("Not signed in. Run `npx ripplo auth login` first.\n"),process.exit(1));let n=((await u({config:H({serverUrl:e,token:r}),document:el,variables:void 0})).projects??[]).map(o=>({id:o.id,name:o.name}));process.stdout.write(`${JSON.stringify({projects:n},null,2)}
252
+ `);async function Hn(){let e=A(),r=F(e);r==null&&(process.stderr.write("Not signed in. Run `npx ripplo auth login` first.\n"),process.exit(1));let n=((await u({config:H({serverUrl:e,token:r}),document:el,variables:void 0})).projects??[]).map(o=>({id:o.id,name:o.name}));process.stdout.write(`${JSON.stringify({projects:n},null,2)}
253
253
  `)}import{graphql as rl}from"gql.tada";function Wn({id:e,kind:r,title:t}){return`Caught bug reported (${r}): "${t}" [${e}]`}var tl=rl(`
254
254
  mutation ReportCaughtBug(
255
255
  $projectId: String!
@@ -280,26 +280,26 @@ Verify the dev session is live (\`npx ripplo doctor\`, ${c("start")}), or pass t
280
280
  }
281
281
  }
282
282
  }
283
- `);async function qn(e){let r=L(),n=(await u({config:r,document:tl,variables:{kind:e.kind,projectId:r.projectId,rootCause:e.rootCause,runId:e.runId,surfacedBy:e.surfacedBy,title:e.title,workflowSlug:e.testId==null?null:A(e.testId)}})).reportCaughtBug;if(n?.__typename!=="CaughtBug"){let o=n?.__typename==="WorkflowNotFoundError"?n.message:null;process.stderr.write(`${o??"reportCaughtBug failed"}
283
+ `);async function qn(e){let r=L(),n=(await u({config:r,document:tl,variables:{kind:e.kind,projectId:r.projectId,rootCause:e.rootCause,runId:e.runId,surfacedBy:e.surfacedBy,title:e.title,workflowSlug:e.testId==null?null:j(e.testId)}})).reportCaughtBug;if(n?.__typename!=="CaughtBug"){let o=n?.__typename==="WorkflowNotFoundError"?n.message:null;process.stderr.write(`${o??"reportCaughtBug failed"}
284
284
  `),process.exit(1)}process.stdout.write(`${Wn({id:n.id,kind:e.kind,title:e.title})}
285
- `)}import nl from"path";import{CancellationTokenSource as ol}from"vscode-jsonrpc/node";async function Bn({all:e,headed:r,ids:t,keepAlive:n}){let o=process.cwd(),i=process.argv[1];i==null&&fe(T({kind:"spawn-failed",message:"process.argv[1] missing"}));let a=await Fe(o);a!=null&&fe(Me(a));let l=(await te({cliEntry:i,cwd:o})).match(I=>I,I=>fe(T(I)));l.versionNote!=null&&process.stderr.write(`${dt(l.versionNote)}
286
- `);let d=new ol;process.once("SIGINT",()=>{d.cancel(),l.socket.destroy(),process.exit(130)});let h=nl.join(o,".ripplo","debug"),k=(await De({connection:l,request:{all:e,headed:r,tests:[...t]},token:d.token,onEvent:I=>{let E=Ue({debugDir:h,event:I});E!=null&&process.stdout.write(`${E}
287
- `)}})).match(I=>I,I=>fe(T(I)));await il({connection:l,keepAlive:n,result:k})}async function il({connection:e,keepAlive:r,result:t}){e.spawned&&!r&&await ne(e),await new Promise(n=>{e.socket.end(n)}),t.kind==="daemon-error"&&fe(He(t.error)),process.stdout.write(`${On(t)}
285
+ `)}import nl from"path";import{CancellationTokenSource as ol}from"vscode-jsonrpc/node";async function Bn({all:e,headed:r,ids:t,keepAlive:n}){let o=process.cwd(),i=process.argv[1];i==null&&fe(T({kind:"spawn-failed",message:"process.argv[1] missing"}));let s=await Fe(o);s!=null&&fe(Me(s));let l=(await te({cliEntry:i,cwd:o})).match($=>$,$=>fe(T($)));l.versionNote!=null&&process.stderr.write(`${dt(l.versionNote)}
286
+ `);let d=new ol;process.once("SIGINT",()=>{d.cancel(),l.socket.destroy(),process.exit(130)});let h=nl.join(o,".ripplo","debug"),y=(await De({connection:l,request:{all:e,headed:r,tests:[...t]},token:d.token,onEvent:$=>{let E=Ue({debugDir:h,event:$});E!=null&&process.stdout.write(`${E}
287
+ `)}})).match($=>$,$=>fe(T($)));await il({connection:l,keepAlive:n,result:y})}async function il({connection:e,keepAlive:r,result:t}){e.spawned&&!r&&await ne(e),await new Promise(n=>{e.socket.end(n)}),t.kind==="daemon-error"&&fe(He(t.error)),process.stdout.write(`${On(t)}
288
288
  `),t.failed>0&&process.exit(1),process.exit(t.notRun>0?2:0)}function fe(e){process.stderr.write(`${e}
289
- `),process.exit(1)}import Vn from"path";import{err as qe,ok as mr}from"neverthrow";async function Gn(){let e=process.cwd(),r=_.child({worker:process.pid}),t=Dt(),n=Se({clientVersion:C(),debugDir:Vn.join(e,".ripplo","debug"),headed:!1,writeOtlpPortFile:!1}),o={entry:void 0},i=a=>{n.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)}),t.onRequest(It,async(a,s)=>{let l=Ct.safeParse(a);if(!l.success)return We("bad-run-assign");let d=l.data;V(e);let h=w(e).match(E=>E,E=>E.kind);if(typeof h=="string")return We(`config:${h}`);let p=await zn(d.lockfileFingerprint,o,t);if(p.isErr())return We(`lockfile-unavailable:${p.error}`);let k=p.value,I=Br(k).find(E=>E.ref===d.testRef);if(I==null)return We(`no-test:${d.testRef}`);try{let E=await Rt({config:h,cwd:e,fixturesDir:Vn.join(e,we),headed:d.headed,lockfile:k,runId:d.runId,session:n,signal:nr(s),test:I.test});return{outcome:ct(E),serverNotified:!0}}catch(E){if(E instanceof br)return{outcome:{detail:E.message,kind:"infra-error"},serverNotified:!1};throw E}}),t.onRequest(At,(a,s)=>sl({cache:o,connection:t,cwd:e,raw:a,session:n,token:s})),t.onNotification(Lt,a=>{let s=Pt.safeParse(a);s.success&&n.injectSpan(s.data.runId,s.data.span)}),t.onNotification(Tt,()=>{i(0)}),t.sendNotification($t),await new Promise(()=>{})}function We(e){return{outcome:{detail:e,kind:"error"},serverNotified:!1}}async function sl({cache:e,connection:r,cwd:t,raw:n,session:o,token:i}){let a=Et.safeParse(n);if(!a.success)return{kind:"error",reason:"bad-assign",rows:[],trail:[]};let s=a.data;V(t);let l=w(t).match(p=>p,p=>p.kind);if(typeof l=="string")return{kind:"error",reason:`config:${l}`,rows:[],trail:[]};let d=await zn(s.lockfileFingerprint,e,r);if(d.isErr())return{kind:"error",reason:`lockfile:${d.error}`,rows:[],trail:[]};let h=d.value;return Te({assign:s,config:l,headed:!1,lockfile:h,session:o,signal:nr(i)})}async function zn(e,r,t){return r.entry!=null&&r.entry.fingerprint===e?mr(r.entry.lockfile):(await al(t,e)).andThen(o=>o.unavailable!=null?qe(o.unavailable):o.lockfileJson==null?qe("empty-reply"):ll(o.lockfileJson).map(i=>(r.entry={fingerprint:e,lockfile:i},i)))}async function al(e,r){try{return mr(await e.sendRequest(jt,{fingerprint:r}))}catch(t){return qe(`transport:${t instanceof Error?t.message:"unknown"}`)}}function ll(e){try{return mr(Lr(M,e))}catch(r){return qe(`worker-decode-failed:${r instanceof Error?r.message.slice(0,200):"unknown"}`)}}async function Jn(){await Gn()}import eo from"path";import{createRequire as dl}from"module";import{existsSync as cl}from"fs";import{readFile as Xn,writeFile as Kn}from"fs/promises";import{fileURLToPath as pl}from"url";import Qn from"path";import{err as ge,ok as ul}from"neverthrow";import{z as O}from"zod";var ml={height:800,width:1280};async function Zn({jsonlPath:e,moment:r,outDir:t,runId:n}){if(!cl(e))return ge({kind:"run-not-found",runId:n});let o=await gl(e),i=o[0],a=o.at(-1);if(i==null||a==null)return ge({kind:"no-rrweb-events",runId:n});let s=a.timestamp-i.timestamp,l=r.kind==="offset"?r.offsetMs:r.at-i.timestamp;if(l<0||l>s)return ge({durationMs:s,firstTimestamp:i.timestamp,kind:"moment-out-of-range",lastTimestamp:a.timestamp,moment:r});let d=String(Math.round(l)),h=Qn.join(t,`snapshot-${d}ms.png`),p=Qn.join(t,`snapshot-${d}ms.html`);return(await bl({events:o,htmlPath:p,offsetMs:l,pngPath:h})).map(()=>({durationMs:s,htmlPath:p,offsetMs:l,pngPath:h}))}var fl=O.object({event:O.looseObject({timestamp:O.number(),type:O.number()}),kind:O.literal("rrweb")});async function gl(e){return(await Xn(e,"utf8")).split(`
289
+ `),process.exit(1)}import Vn from"path";import{err as qe,ok as mr}from"neverthrow";async function Gn(){let e=process.cwd(),r=_.child({worker:process.pid}),t=Dt(),n=Se({clientVersion:C(),debugDir:Vn.join(e,".ripplo","debug"),headed:!1,writeOtlpPortFile:!1}),o={entry:void 0},i=s=>{n.close().catch(()=>{}).then(()=>{process.exit(s)})};process.on("disconnect",()=>{i(1)}),process.on("unhandledRejection",s=>{r.error({err:s},"worker unhandled rejection")}),process.on("uncaughtException",s=>{r.error({err:s},"worker uncaught exception"),i(1)}),t.onRequest(It,async(s,a)=>{let l=Ct.safeParse(s);if(!l.success)return We("bad-run-assign");let d=l.data;V(e);let h=w(e).match(E=>E,E=>E.kind);if(typeof h=="string")return We(`config:${h}`);let p=await zn(d.lockfileFingerprint,o,t);if(p.isErr())return We(`lockfile-unavailable:${p.error}`);let y=p.value,$=Br(y).find(E=>E.ref===d.testRef);if($==null)return We(`no-test:${d.testRef}`);try{let E=await Rt({config:h,cwd:e,fixturesDir:Vn.join(e,we),headed:d.headed,lockfile:y,runId:d.runId,session:n,signal:nr(a),test:$.test});return{outcome:ct(E),serverNotified:!0}}catch(E){if(E instanceof br)return{outcome:{detail:E.message,kind:"infra-error"},serverNotified:!1};throw E}}),t.onRequest(jt,(s,a)=>sl({cache:o,connection:t,cwd:e,raw:s,session:n,token:a})),t.onNotification(Lt,s=>{let a=Pt.safeParse(s);a.success&&n.injectSpan(a.data.runId,a.data.span)}),t.onNotification(Tt,()=>{i(0)}),t.sendNotification($t),await new Promise(()=>{})}function We(e){return{outcome:{detail:e,kind:"error"},serverNotified:!1}}async function sl({cache:e,connection:r,cwd:t,raw:n,session:o,token:i}){let s=Et.safeParse(n);if(!s.success)return{kind:"error",reason:"bad-assign",rows:[],trail:[]};let a=s.data;V(t);let l=w(t).match(p=>p,p=>p.kind);if(typeof l=="string")return{kind:"error",reason:`config:${l}`,rows:[],trail:[]};let d=await zn(a.lockfileFingerprint,e,r);if(d.isErr())return{kind:"error",reason:`lockfile:${d.error}`,rows:[],trail:[]};let h=d.value;return Te({assign:a,config:l,headed:!1,lockfile:h,session:o,signal:nr(i)})}async function zn(e,r,t){return r.entry!=null&&r.entry.fingerprint===e?mr(r.entry.lockfile):(await al(t,e)).andThen(o=>o.unavailable!=null?qe(o.unavailable):o.lockfileJson==null?qe("empty-reply"):ll(o.lockfileJson).map(i=>(r.entry={fingerprint:e,lockfile:i},i)))}async function al(e,r){try{return mr(await e.sendRequest(At,{fingerprint:r}))}catch(t){return qe(`transport:${t instanceof Error?t.message:"unknown"}`)}}function ll(e){try{return mr(Lr(M,e))}catch(r){return qe(`worker-decode-failed:${r instanceof Error?r.message.slice(0,200):"unknown"}`)}}async function Jn(){await Gn()}import eo from"path";import{createRequire as dl}from"module";import{existsSync as cl}from"fs";import{readFile as Xn,writeFile as Kn}from"fs/promises";import{fileURLToPath as pl}from"url";import Qn from"path";import{err as ge,ok as ul}from"neverthrow";import{z as O}from"zod";var ml={height:800,width:1280};async function Zn({jsonlPath:e,moment:r,outDir:t,runId:n}){if(!cl(e))return ge({kind:"run-not-found",runId:n});let o=await gl(e),i=o[0],s=o.at(-1);if(i==null||s==null)return ge({kind:"no-rrweb-events",runId:n});let a=s.timestamp-i.timestamp,l=r.kind==="offset"?r.offsetMs:r.at-i.timestamp;if(l<0)return ge({durationMs:a,firstTimestamp:i.timestamp,kind:"moment-out-of-range",lastTimestamp:s.timestamp,moment:r});let d=Math.min(l,a),h=String(Math.round(d)),p=Qn.join(t,`snapshot-${h}ms.png`),y=Qn.join(t,`snapshot-${h}ms.html`);return(await bl({events:o,htmlPath:y,offsetMs:d,pngPath:p})).map(()=>({durationMs:a,htmlPath:y,offsetMs:d,pngPath:p}))}var fl=O.object({event:O.looseObject({timestamp:O.number(),type:O.number()}),kind:O.literal("rrweb")});async function gl(e){return(await Xn(e,"utf8")).split(`
290
290
  `).filter(t=>t.length>0).map(t=>hl(t)).map(t=>fl.safeParse(t)).flatMap(t=>t.success?[t.data.event]:[])}function hl(e){try{return JSON.parse(e)}catch{return null}}var yl=O.object({data:O.looseObject({height:O.number(),width:O.number()}),type:O.literal(4)});function kl(e){let r=e.flatMap(t=>{let n=yl.safeParse(t);return n.success?[n.data]:[]})[0];return r==null?ml:{height:r.data.height,width:r.data.width}}var wl=`<!doctype html><html><head><style>
291
291
  html, body { margin: 0; padding: 0; }
292
292
  .replayer-wrapper { position: relative; }
293
293
  .replayer-mouse, .replayer-mouse-tail { display: none; }
294
294
  iframe { border: none; }
295
- </style></head><body></body></html>`,vl="*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; transition-duration: 0s !important; transition-delay: 0s !important; }";async function bl({events:e,htmlPath:r,offsetMs:t,pngPath:n}){let o=await import("playwright").then(({chromium:i})=>i.launch({args:["--no-sandbox"],headless:!0})).catch(()=>null);if(o==null)return ge({detail:"chromium launch",kind:"browser-failed"});try{let{html:i,png:a}=await Sl({browser:o,events:e,offsetMs:t});return await Kn(n,a),await Kn(r,i,"utf8"),ul(void 0)}catch(i){return ge({detail:i instanceof Error?i.message:String(i),kind:"browser-failed"})}finally{await o.close()}}async function Sl({browser:e,events:r,offsetMs:t}){let n=kl(r),o=await e.newPage({viewport:n});await o.setContent(wl),await o.addScriptTag({content:await Pl()}),await o.evaluate(xl({events:r,offsetMs:t})),await o.evaluate(Rl());let i=o.locator(".replayer-wrapper iframe").first(),s=await(await i.count()>0?i:o).screenshot({type:"png"});return{html:await o.evaluate(Cl()),png:s}}function xl({events:e,offsetMs:r}){return`(() => {
295
+ </style></head><body></body></html>`,vl="*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; transition-duration: 0s !important; transition-delay: 0s !important; }";async function bl({events:e,htmlPath:r,offsetMs:t,pngPath:n}){let o=await import("playwright").then(({chromium:i})=>i.launch({args:["--no-sandbox"],headless:!0})).catch(()=>null);if(o==null)return ge({detail:"chromium launch",kind:"browser-failed"});try{let{html:i,png:s}=await Sl({browser:o,events:e,offsetMs:t});return await Kn(n,s),await Kn(r,i,"utf8"),ul(void 0)}catch(i){return ge({detail:i instanceof Error?i.message:String(i),kind:"browser-failed"})}finally{await o.close()}}async function Sl({browser:e,events:r,offsetMs:t}){let n=kl(r),o=await e.newPage({viewport:n});await o.setContent(wl),await o.addScriptTag({content:await Pl()}),await o.evaluate(xl({events:r,offsetMs:t})),await o.evaluate(Rl());let i=o.locator(".replayer-wrapper iframe").first(),a=await(await i.count()>0?i:o).screenshot({type:"png"});return{html:await o.evaluate(Cl()),png:a}}function xl({events:e,offsetMs:r}){return`(() => {
296
296
  const replayer = new globalThis.__RipploReplayer(${JSON.stringify(e)}, {
297
297
  insertStyleRules: [${JSON.stringify(vl)}],
298
298
  mouseTail: false,
299
299
  root: document.body,
300
300
  showWarning: false,
301
301
  });
302
- replayer.pause(${String(r)});
302
+ replayer.pause(${String(r+.5)});
303
303
  globalThis.__ripploSnapshotReplayer = replayer;
304
304
  })()`}function Rl(){return`(async () => {
305
305
  const doc = document.querySelector(".replayer-wrapper iframe")?.contentDocument;
@@ -329,22 +329,23 @@ iframe { border: none; }
329
329
  `)}function no(e){return`${P.bad("fail")} \u2014 ${$l(e)}`}function oo(){return`${P.bad("fail")} \u2014 pass exactly one of --at <epoch-ms from behavior.jsonl> or --offset <ms from the start of the recording>.`}function $l(e){switch(e.kind){case"run-not-found":return`no debug artifacts for run ${e.runId} (.ripplo/debug/${e.runId}/behavior.jsonl missing). ${c("run")}`;case"no-rrweb-events":return`run ${e.runId} has a behavior.jsonl but no rrweb events \u2014 nothing to replay.`;case"moment-out-of-range":return`${e.moment.kind==="offset"?"--offset":"--at"} is outside the recording, which spans ${String(e.firstTimestamp)}\u2013${String(e.lastTimestamp)} (duration ${String(e.durationMs)}ms). Pass --at <epoch-ms from behavior.jsonl> or --offset <0\u2013${String(e.durationMs)}>.`;case"browser-failed":return`replay browser failed (${e.detail}). Is chromium installed? Try \`npx playwright install chromium\`.`}}async function io({at:e,offset:r,runId:t}){let n=Il({at:e,offset:r});n==null&&(process.stderr.write(`${oo()}
330
330
  `),process.exit(1)),(await ro({cwd:process.cwd(),moment:n,runId:t})).match(i=>{process.stdout.write(`${to(i)}
331
331
  `)},i=>{process.stderr.write(`${no(i)}
332
- `),process.exit(1)})}function Il({at:e,offset:r}){if(e!=null&&r==null)return{at:e,kind:"absolute"};if(r!=null&&e==null)return{kind:"offset",offsetMs:r}}async function so(){let e=L();try{let t=(await ce(e.cwd,e)).match(i=>i,i=>{process.stderr.write(`${$(i)}
332
+ `),process.exit(1)})}function Il({at:e,offset:r}){if(e!=null&&r==null)return{at:e,kind:"absolute"};if(r!=null&&e==null)return{kind:"offset",offsetMs:r}}async function so(){let e=L();try{let t=(await ce(e.cwd,e)).match(i=>i,i=>{process.stderr.write(`${I(i)}
333
333
  `),process.exit(1)}),n=t.lockfile.workflows.length,o=t.lockfile.entities.length;process.stdout.write(`Synced ${b(n,"workflow")} and ${b(o,"entity","entities")} to dev session ${t.devSessionId}
334
334
  `)}catch(r){let t=r instanceof Error?r.message:String(r);process.stderr.write(`ripplo sync failed: ${t}
335
335
  `),process.stderr.write(`${c("setup","verify auth + server reachability")}
336
- `),process.exit(1)}}import{spawnSync as Ll}from"child_process";import Tl from"fs";import Dl from"path";import Ol from"semver";import Be from"fs";import jl from"os";import he from"path";function ao(e){return e.split(he.sep).includes("_npx")?"npx":e.includes(he.join("packages","cli"))?"workspace":"global"}function lo(){let e=he.join(jl.homedir(),".npm","_npx");return Be.existsSync(e)?Be.readdirSync(e).map(r=>he.join(e,r)).filter(r=>Be.existsSync(he.join(r,"node_modules","ripplo"))).map(r=>(Be.rmSync(r,{force:!0,recursive:!0}),r)):[]}import{spawnSync as co}from"child_process";var Al=["user","project","local"];function fr(){return co("claude",["--version"],{stdio:"ignore"}).error!=null?"claude-missing":Al.find(t=>co("claude",["plugin","update","ripplo","--scope",t],{stdio:"ignore"}).status===0)==null?"not-installed":"updated"}function po(e){return`ripplo v${e} is already the latest version.`}function uo(e){return`ripplo v${e}: could not reach the npm registry to check for updates.`}function gr({evicted:e,latest:r,mode:t}){return t==="npx"?`ripplo: ${e===0?"npx cache had no stale copy":`cleared ${b(e,"stale npx cache entry","stale npx cache entries")}`} \u2014 the next \`npx ripplo\` command runs v${r}.`:`ripplo: updated global install to v${r}.`}function mo(){return"ripplo: this is a workspace build (packages/cli/dist) \u2014 update via git pull + `pnpm --filter ripplo build`."}function fo(e){return`ripplo: \`npm install -g ripplo@latest\` failed: ${e}`}function hr(e){switch(e){case"updated":return"ripplo: Claude plugin updated \u2014 restart Claude Code (or /reload-plugins) to apply.";case"not-installed":return"ripplo: Claude plugin not installed in this project \u2014 skipped.";case"claude-missing":return"ripplo: claude binary not found \u2014 skipped plugin update."}}function go(e){return e?"ripplo: idle daemon stopped \u2014 the next `npx ripplo run` restarts it on the new version.":"ripplo: daemon is busy \u2014 it picks up the new version once idle (the next `npx ripplo run` handles it)."}var _l=1e4;async function ho(){let e=C(),r=process.argv[1];Fl(process.cwd());let t=await Pe(_l);process.stdout.write(`${Ce({current:e,latest:t})}
336
+ `),process.exit(1)}}import{spawnSync as Ll}from"child_process";import Tl from"fs";import Dl from"path";import Ol from"semver";import Be from"fs";import Al from"os";import he from"path";function ao(e){return e.split(he.sep).includes("_npx")?"npx":e.includes(he.join("packages","cli"))?"workspace":"global"}function lo(){let e=he.join(Al.homedir(),".npm","_npx");return Be.existsSync(e)?Be.readdirSync(e).map(r=>he.join(e,r)).filter(r=>Be.existsSync(he.join(r,"node_modules","ripplo"))).map(r=>(Be.rmSync(r,{force:!0,recursive:!0}),r)):[]}import{spawnSync as co}from"child_process";var jl=["user","project","local"];function fr(){return co("claude",["--version"],{stdio:"ignore"}).error!=null?"claude-missing":jl.find(t=>co("claude",["plugin","update","ripplo","--scope",t],{stdio:"ignore"}).status===0)==null?"not-installed":"updated"}function po(e){return`ripplo v${e} is already the latest version.`}function uo(e){return`ripplo v${e}: could not reach the npm registry to check for updates.`}function gr({evicted:e,latest:r,mode:t}){return t==="npx"?`ripplo: ${e===0?"npx cache had no stale copy":`cleared ${b(e,"stale npx cache entry","stale npx cache entries")}`} \u2014 the next \`npx ripplo\` command runs v${r}.`:`ripplo: updated global install to v${r}.`}function mo(){return"ripplo: this is a workspace build (packages/cli/dist) \u2014 update via git pull + `pnpm --filter ripplo build`."}function fo(e){return`ripplo: \`npm install -g ripplo@latest\` failed: ${e}`}function hr(e){switch(e){case"updated":return"ripplo: Claude plugin updated \u2014 restart Claude Code (or /reload-plugins) to apply.";case"not-installed":return"ripplo: Claude plugin not installed in this project \u2014 skipped.";case"claude-missing":return"ripplo: claude binary not found \u2014 skipped plugin update."}}function go(e){return e?"ripplo: idle daemon stopped \u2014 the next `npx ripplo run` restarts it on the new version.":"ripplo: daemon is busy \u2014 it picks up the new version once idle (the next `npx ripplo run` handles it)."}var _l=1e4;async function ho(){let e=C(),r=process.argv[1];Fl(process.cwd());let t=await Pe(_l);process.stdout.write(`${Ce({current:e,latest:t})}
337
337
  `),t==null&&(process.stderr.write(`${uo(e)}
338
338
  `),process.exit(1)),Ol.gt(t,e)||(process.stdout.write(`${po(e)}
339
339
  `),process.stdout.write(`${hr(fr())}
340
340
  `),process.exit(0));let n=r==null?"global":ao(r);Nl({latest:t,mode:n}),process.stdout.write(`${hr(fr())}
341
- `),await Ml(process.cwd()),process.exit(0)}function Fl(e){if(!Tl.existsSync(Dl.join(e,".ripplo")))return;let r=je(e);r==="written"&&process.stdout.write(`${Ae(r).trim()}
341
+ `),await Ml(process.cwd()),process.exit(0)}function Fl(e){if(!Tl.existsSync(Dl.join(e,".ripplo")))return;let r=Ae(e);r==="written"&&process.stdout.write(`${je(r).trim()}
342
342
  `)}function Nl({latest:e,mode:r}){if(r==="workspace"&&(process.stdout.write(`${mo()}
343
343
  `),process.exit(0)),r==="npx"){let t=lo();process.stdout.write(`${gr({evicted:t.length,latest:e,mode:r})}
344
344
  `);return}Ul(e)}function Ul(e){let r=`ripplo@${e}`,t=Ll("npm",["install","-g",r],{stdio:"inherit"});if(t.status!==0){let n=`exit ${String(t.status)}`;process.stderr.write(`${fo(n)}
345
345
  `),process.exit(1)}process.stdout.write(`${gr({evicted:0,latest:e,mode:"global"})}
346
346
  `)}async function Ml(e){if((await Oe(e)).kind==="not-running")return;let t=await Tn(e);process.stdout.write(`${go(t)}
347
- `)}async function yo({explore:e,exploreConcurrency:r}){let{runDaemon:t}=await import("./daemon-MBSJLAN3.js");await t({explore:e,exploreConcurrency:r})}import{graphql as Ve}from"gql.tada";function ko(){return"No scope items added \u2014 the matched tests are already in scope (check `npx ripplo scope status`)."}function wo(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
+ `)}async function yo({executor:e,explore:r,exploreConcurrency:t}){let{runDaemon:n}=await import("./daemon-T77UK4AQ.js"),{TunnelProvisionFailedError:o}=await import("./daemon-tunnel-MOYCRSSC.js"),{renderTunnelProvisionFailed:i}=await import("./daemon-HZS7WYEG.js");try{await n({executor:e,explore:r,exploreConcurrency:t})}catch(s){throw s instanceof o&&(process.stderr.write(`${i(s.failure)}
348
+ `),process.exit(1)),s}}import{graphql as Ve}from"gql.tada";function ko(){return"No scope items added \u2014 the matched tests are already in scope (check `npx ripplo scope status`)."}function wo(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(`
348
349
  `)}import{graphql as Hl}from"gql.tada";var vo=Hl(`
349
350
  query ScopeStatus($projectId: String!, $cwd: String!) {
350
351
  project(id: $projectId) {
@@ -412,13 +413,13 @@ iframe { border: none; }
412
413
  }
413
414
  `);async function bo(e){let r=L();await pe(r);let n=(await u({config:r,document:vo,variables:{cwd:r.cwd,projectId:r.projectId}})).project?.devSession?.scopeItems??[];if(e.format==="json"){process.stdout.write(`${JSON.stringify(n,null,2)}
414
415
  `);return}if(n.length===0){process.stdout.write("No scope items. Add some with `npx ripplo scope add <test-ids..>` or from the dashboard.\n");return}n.forEach(o=>{let i=o.workflow;if(i==null){process.stdout.write(` [intent] (${o.id}) ${o.label??""}
415
- `);return}let a=i.spec==null?"stub":"implemented";process.stdout.write(` [${a}] (${o.id}) ${i.slug} \u2014 ${i.name}
416
- `)})}async function So({testIds:e}){let r=L();await pe(r),(await ce(r.cwd,r)).match(()=>{},s=>{process.stderr.write(`${$(s)}
417
- `),process.exit(1)});let o=(await u({config:r,document:ql,variables:{cwd:r.cwd,projectId:r.projectId,workflowSlugs:e.map(s=>A(s))}})).addDirtyTestsToScope;o?.__typename==="NoActiveDevSessionError"&&(process.stderr.write(`${o.message}
416
+ `);return}let s=i.spec==null?"stub":"implemented";process.stdout.write(` [${s}] (${o.id}) ${i.slug} \u2014 ${i.name}
417
+ `)})}async function So({testIds:e}){let r=L();await pe(r),(await ce(r.cwd,r)).match(()=>{},a=>{process.stderr.write(`${I(a)}
418
+ `),process.exit(1)});let o=(await u({config:r,document:ql,variables:{cwd:r.cwd,projectId:r.projectId,workflowSlugs:e.map(a=>j(a))}})).addDirtyTestsToScope;o?.__typename==="NoActiveDevSessionError"&&(process.stderr.write(`${o.message}
418
419
  `),process.exit(1)),o?.__typename==="UnknownWorkflowSlugsError"&&(process.stderr.write(`${wo(o.slugs)}
419
420
  `),process.exit(1));let i=o?.__typename==="MutationAddDirtyTestsToScopeSuccess"?o.data:[];if(i.length===0){process.stdout.write(`${ko()}
420
- `);return}let a=i.map(s=>s.workflow?.slug??"?").join(", ");process.stdout.write(`Added ${b(i.length,"scope item")}: ${a}
421
- `)}async function xo({id:e,testId:r}){let t=L();await pe(t),(await ce(t.cwd,t)).match(()=>{},i=>{process.stderr.write(`${$(i)}
421
+ `);return}let s=i.map(a=>a.workflow?.slug??"?").join(", ");process.stdout.write(`Added ${b(i.length,"scope item")}: ${s}
422
+ `)}async function xo({id:e,testId:r}){let t=L();await pe(t),(await ce(t.cwd,t)).match(()=>{},i=>{process.stderr.write(`${I(i)}
422
423
  `),process.exit(1)});let o=await Gl({cfg:t,slug:r});await u({config:t,document:Bl,variables:{id:e,workflowId:o}}),process.stdout.write(`Linked scope item ${e} to ${r}
423
424
  `)}async function Ro({ids:e}){let r=L();await pe(r);let n=(await u({config:r,document:Vl,variables:{ids:[...e]}})).removeScopeItems??0;process.stdout.write(`Removed ${b(n,"scope item")}
424
425
  `)}async function Gl({cfg:e,slug:r}){let n=(await u({config:e,document:Wl,variables:{cwd:e.cwd,projectId:e.projectId,slug:r}})).project?.devSession?.workflows?.[0];return n==null&&(process.stderr.write(`No workflow found with id "${r}". Create a stub first via the testing DSL.
@@ -426,11 +427,11 @@ iframe { border: none; }
426
427
  `),process.exit(1)),n.id}async function Co(e){let r=process.cwd(),t=await S(r);t.isErr()&&(process.stderr.write(`${x(t.error)}
427
428
  `),process.exit(1));let n=U(t.value),o=await Oe(r);if(e.format==="summary"){n.length>0&&process.stdout.write(`stub workflows: ${n.join(", ")}
428
429
  `),process.stdout.write(`${lt(o)}
429
- `);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:n.map(a=>({id:a,implemented:!1}))};process.stdout.write(`${JSON.stringify(i,null,2)}
430
+ `);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:n.map(s=>({id:s,implemented:!1}))};process.stdout.write(`${JSON.stringify(i,null,2)}
430
431
  `)}import Ge from"fs";import Jl from"os";import Po from"path";import{z as zl}from"zod";function f(e,r){let t=zl.custom(n=>typeof n=="object"&&n!==null&&"hook_event_name"in n&&n.hook_event_name===e);return{event:e,run:async n=>await r(t.parse(n))??void 0}}var Eo=f("PreToolUse",e=>{if(e.tool_name!=="ExitPlanMode"||!v(e.cwd))return;let r=Kl();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 t=Ge.readFileSync(r,"utf8");if(!/\.ripplo\/tests|Tests to implement|No e2e coverage needed/.test(t))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 Kl(){let e=Po.join(Jl.homedir(),".claude","plans");return Ge.existsSync(e)?Ge.readdirSync(e).filter(t=>t.endsWith(".md")).map(t=>Po.join(e,t)).map(t=>({full:t,mtime:Ge.statSync(t).mtimeMs})).sort((t,n)=>n.mtime-t.mtime)[0]?.full??null:null}var $o=f("UserPromptSubmit",async e=>{if(e.permission_mode!=="plan"||!m(e.cwd)||!v(e.cwd))return;let r=await S(e.cwd);if(r.isErr())return;let t=U(r.value),n=['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 t.length>0&&n.push(`Existing stubs: ${t.join(", ")}`),{hookSpecificOutput:{additionalContext:n.join(`
431
432
  `),hookEventName:"UserPromptSubmit"}}});import ld from"path";import Fo from"picomatch";import{z as No}from"zod";import{mkdirSync as ed,readFileSync as rd,writeFileSync as td}from"fs";import nd from"path";import{z as R}from"zod";import{createHash as Ch}from"crypto";import ze from"picomatch";function Ql(e){return N(["diff","--name-only","HEAD"],e).split(`
432
433
  `).filter(r=>r.length>0)}function Io({cwd:e,ignoreGlobs:r,watchGlobs:t}){let n=ze([...t]),o=ze([...r]);return Ql(e).filter(i=>n(i)&&!o(i))}function Yl(e){return N(["ls-files","--others","--exclude-standard"],e).split(`
433
- `).filter(r=>r.length>0)}function jo({cwd:e,ignoreGlobs:r,watchGlobs:t}){let n=ze([...t]),o=ze([...r]);return Yl(e).filter(i=>n(i)&&!o(i))}var Xl=["**/src/**","**/app/**","**/apps/**","**/pages/**","**/routes/**","**/components/**","**/server/**","**/api/**","**/backend/**","**/features/**","**/modules/**","**/views/**","**/ui/**","**/hooks/**","**/contexts/**","**/providers/**","**/controllers/**","**/handlers/**","**/resolvers/**","**/services/**","**/middleware/**","**/lib/**"],Zl=["**/*.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 oe(){return{ignorePaths:Zl,watchPaths:Xl}}var od=R.object({label:R.string().nullable(),slug:R.string().nullable(),status:R.enum(["intent","stub","implemented"])}),id=R.object({intent:R.string(),name:R.string(),sourcePath:R.string().nullable(),stub:R.boolean()}),sd=R.object({changedAppFiles:R.array(R.string()).readonly(),scope:R.object({available:R.boolean(),items:R.array(od).readonly()}),tests:R.array(id).readonly().default([]),untrackedAppFiles:R.array(R.string()).readonly()});function Ao({cwd:e,scope:r}){let t=To(e);Oo(e,{..._o(e),scope:r,tests:t?.tests??[]})}function Lo({cwd:e,tests:r}){let t=To(e);Oo(e,{..._o(e),scope:t?.scope??{available:!1,items:[]},tests:r??t?.tests??[]})}function To(e){let r=sd.safeParse(ad(Do(e)));return r.success?r.data:null}function ad(e){try{return JSON.parse(rd(e,"utf8"))}catch{return null}}function Do(e){return xr(e,"coverage-context.json")}function Oo(e,r){let t=Do(e);ed(nd.dirname(t),{recursive:!0}),td(t,JSON.stringify(r,null,2))}function _o(e){let{ignorePaths:r,watchPaths:t}=oe(),n={cwd:e,ignoreGlobs:r,watchGlobs:t};return{changedAppFiles:Io(n),untrackedAppFiles:jo(n)}}var dd=No.looseObject({file_path:No.string()}),Uo=f("PostToolUse",async e=>{let r=dd.safeParse(e.tool_input);if(!r.success)return;let t=r.data.file_path,{cwd:n}=e;if(!m(n)||!v(n))return;let o=ld.relative(n,t);if(o.startsWith(".."))return;let{ignorePaths:i,watchPaths:a}=oe(),s=Fo([...a]),l=Fo([...i]);if(!s(o)||l(o))return;let d=await S(n);if(Lo({cwd:n,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 h=U(d.value);if(h.length!==0)return{hookSpecificOutput:{additionalContext:`Reminder: stub workflows still unimplemented \u2014 ${h.join(", ")}. Implement with \`workflow("Intent", () => ({ given, steps }))\`.`,hookEventName:"PostToolUse"}}});import{createHash as wd}from"crypto";import{z as Bo}from"zod";import{createHash as cd}from"crypto";import{mkdirSync as pd,readFileSync as Mo,writeFileSync as ud}from"fs";import yr from"path";var md=[".ts",".tsx",".js",".jsx"];function Je(e){let r=cd("sha256");return r.update(N(["rev-parse","HEAD"],e)),r.update("\0"),r.update(N(["diff","HEAD"],e)),r.update("\0"),N(["ls-files","--others","--exclude-standard"],e).split(`
434
+ `).filter(r=>r.length>0)}function Ao({cwd:e,ignoreGlobs:r,watchGlobs:t}){let n=ze([...t]),o=ze([...r]);return Yl(e).filter(i=>n(i)&&!o(i))}var Xl=["**/src/**","**/app/**","**/apps/**","**/pages/**","**/routes/**","**/components/**","**/server/**","**/api/**","**/backend/**","**/features/**","**/modules/**","**/views/**","**/ui/**","**/hooks/**","**/contexts/**","**/providers/**","**/controllers/**","**/handlers/**","**/resolvers/**","**/services/**","**/middleware/**","**/lib/**"],Zl=["**/*.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 oe(){return{ignorePaths:Zl,watchPaths:Xl}}var od=R.object({label:R.string().nullable(),slug:R.string().nullable(),status:R.enum(["intent","stub","implemented"])}),id=R.object({intent:R.string(),name:R.string(),sourcePath:R.string().nullable(),stub:R.boolean()}),sd=R.object({changedAppFiles:R.array(R.string()).readonly(),scope:R.object({available:R.boolean(),items:R.array(od).readonly()}),tests:R.array(id).readonly().default([]),untrackedAppFiles:R.array(R.string()).readonly()});function jo({cwd:e,scope:r}){let t=To(e);Oo(e,{..._o(e),scope:r,tests:t?.tests??[]})}function Lo({cwd:e,tests:r}){let t=To(e);Oo(e,{..._o(e),scope:t?.scope??{available:!1,items:[]},tests:r??t?.tests??[]})}function To(e){let r=sd.safeParse(ad(Do(e)));return r.success?r.data:null}function ad(e){try{return JSON.parse(rd(e,"utf8"))}catch{return null}}function Do(e){return xr(e,"coverage-context.json")}function Oo(e,r){let t=Do(e);ed(nd.dirname(t),{recursive:!0}),td(t,JSON.stringify(r,null,2))}function _o(e){let{ignorePaths:r,watchPaths:t}=oe(),n={cwd:e,ignoreGlobs:r,watchGlobs:t};return{changedAppFiles:Io(n),untrackedAppFiles:Ao(n)}}var dd=No.looseObject({file_path:No.string()}),Uo=f("PostToolUse",async e=>{let r=dd.safeParse(e.tool_input);if(!r.success)return;let t=r.data.file_path,{cwd:n}=e;if(!m(n)||!v(n))return;let o=ld.relative(n,t);if(o.startsWith(".."))return;let{ignorePaths:i,watchPaths:s}=oe(),a=Fo([...s]),l=Fo([...i]);if(!a(o)||l(o))return;let d=await S(n);if(Lo({cwd:n,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 h=U(d.value);if(h.length!==0)return{hookSpecificOutput:{additionalContext:`Reminder: stub workflows still unimplemented \u2014 ${h.join(", ")}. Implement with \`workflow("Intent", () => ({ given, steps }))\`.`,hookEventName:"PostToolUse"}}});import{createHash as wd}from"crypto";import{z as Bo}from"zod";import{createHash as cd}from"crypto";import{mkdirSync as pd,readFileSync as Mo,writeFileSync as ud}from"fs";import yr from"path";var md=[".ts",".tsx",".js",".jsx"];function Je(e){let r=cd("sha256");return r.update(N(["rev-parse","HEAD"],e)),r.update("\0"),r.update(N(["diff","HEAD"],e)),r.update("\0"),N(["ls-files","--others","--exclude-standard"],e).split(`
434
435
  `).filter(n=>n.length>0).filter(n=>md.some(o=>n.endsWith(o))).toSorted((n,o)=>n.localeCompare(o)).forEach(n=>{r.update(n),r.update("\0"),r.update(fd(yr.join(e,n))),r.update("\0")}),r.digest("hex")}function ie(e,r){try{return Mo(Ho(e,r),"utf8").trim()}catch{return null}}function se(e,r,t){let n=Ho(e,r);pd(yr.dirname(n),{recursive:!0}),ud(n,t)}function fd(e){try{return Mo(e)}catch{return Buffer.alloc(0)}}function Ho(e,r){return yr.join(e,".ripplo",".local",`${r}.hash`)}import{graphql as gd}from"gql.tada";var hd=gd(`
435
436
  mutation AutoScopeAddDirty($projectId: String!, $cwd: String!, $workflowSlugs: [String!]!) {
436
437
  addDirtyTestsToScope(projectId: $projectId, cwd: $cwd, workflowSlugs: $workflowSlugs) {
@@ -442,11 +443,11 @@ iframe { border: none; }
442
443
  }
443
444
  }
444
445
  }
445
- `);async function qo({cwd:e,lockfile:r}){if(!v(e))return{addedSlugs:[]};let t=kd(e);if(t.length===0)return{addedSlugs:[]};let n=new Set(t),o=r.workflows.filter(l=>l.sourcePath!=null&&n.has(l.sourcePath)).map(l=>A(l.name));if(o.length===0)return{addedSlugs:[]};let i=w(e).unwrapOr(void 0);return i==null?{addedSlugs:[]}:await xt({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 yd({cfg:i,slugs:o})?o:[]}}async function yd({cfg:e,slugs:r}){let n=(await u({config:e,document:hd,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 n?.__typename!=="MutationAddDirtyTestsToScopeSuccess"?!1:n.data.length>0}var Wo=".ripplo/workflows/";function kd(e){let r;try{r=N(["status","--porcelain","--",".ripplo/workflows"],e)}catch{return[]}return r.split(`
446
+ `);async function qo({cwd:e,lockfile:r}){if(!v(e))return{addedSlugs:[]};let t=kd(e);if(t.length===0)return{addedSlugs:[]};let n=new Set(t),o=r.workflows.filter(l=>l.sourcePath!=null&&n.has(l.sourcePath)).map(l=>j(l.name));if(o.length===0)return{addedSlugs:[]};let i=w(e).unwrapOr(void 0);return i==null?{addedSlugs:[]}:await xt({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 yd({cfg:i,slugs:o})?o:[]}}async function yd({cfg:e,slugs:r}){let n=(await u({config:e,document:hd,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 n?.__typename!=="MutationAddDirtyTestsToScopeSuccess"?!1:n.data.length>0}var Wo=".ripplo/workflows/";function kd(e){let r;try{r=N(["status","--porcelain","--",".ripplo/workflows"],e)}catch{return[]}return r.split(`
446
447
  `).map(t=>t.slice(3).trim()).filter(t=>t.startsWith(Wo)&&t.endsWith(".ts")).map(t=>t.slice(Wo.length)).filter(t=>t!=="index.ts"&&!t.endsWith("/index.ts"))}var vd=Bo.looseObject({file_path:Bo.string()}),Vo=f("PostToolUse",async e=>{let r=vd.safeParse(e.tool_input);if(!r.success||!/\/\.ripplo\/.*\.ts$/.test(r.data.file_path))return;let{cwd:t}=e;if(!m(t))return;if(!v(t))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 n=await Y(t);if(n.isErr())return{decision:"block",reason:`${x(n.error)}
447
448
  ${c("create","DSL authoring + lint rules")}`};let o=W(n.value);if(o.length>0)return{decision:"block",reason:`${q(o)}
448
449
  ${c("create")}`};let{addedSlugs:i}=await qo({cwd:t,lockfile:n.value});return bd([...i.length>0?[`Auto-scoped ${i.join(", ")} (dirty tests).`]:[],...Sd(t,n.value)])});function bd(e){if(e.length!==0)return{hookSpecificOutput:{additionalContext:e.join(`
449
- `),hookEventName:"PostToolUse"}}}function Sd(e,r){let t=Le(r),n=wd("sha256").update(JSON.stringify(t)).digest("hex");return ie(e,"coverage-warn")===n?[]:(se(e,"coverage-warn",n),t.length===0?[]:[Ee(t)])}import{z as Go}from"zod";var xd=Go.looseObject({command:Go.string()}),Rd=/\bripplo\s+hooks\s+pause\b/,zo=f("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=xd.safeParse(e.tool_input);if(!r.success||!Rd.test(r.data.command))return;let{cwd:t}=e;if(m(t))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 Id}from"shell-quote";import{z as Ko}from"zod";import{existsSync as Cd,mkdirSync as Pd,rmSync as Py,writeFileSync as Ed}from"fs";import kr from"path";function Ke(e,r,t){let n=Jo(e,r,t);Pd(kr.dirname(n),{recursive:!0}),Ed(n,"")}function Qe(e,r,t){return Cd(Jo(e,r,t))}function Jo(e,r,t){return kr.join($d(e,r),t)}function $d(e,r){return kr.join(e,".ripplo",".local","skills-loaded",r)}var jd=Ko.looseObject({command:Ko.string()}),Ad="run",Ld=new Set(["tail","head","less","more","wc","sort","uniq","awk","sed","grep"]),Yo=f("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=jd.safeParse(e.tool_input);if(!r.success)return;let t=_d(r.data.command),n=Td(t);if(n==null)return;let{cwd:o}=e;if(!m(o)||!v(o))return;if(!Qe(o,e.session_id,n.skill))return Qo(n.reason);let i=n.kind==="run"?Nd(t):null;if(i!=null)return Qo(`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 Td(e){return Dd(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"}:Od(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:Ad}:null}function Dd(e){return e.some((r,t)=>r==="ripplo"&&e[t+1]==="explore"&&e[t+2]==="replay")}function Od(e){return e.some((r,t)=>r==="ripplo"&&e[t+1]==="run")}function _d(e){try{return Id(e)}catch{return[]}}function Fd(e){return typeof e=="object"&&"op"in e&&e.op==="|"}function Nd(e){let r=e.find((t,n)=>{let o=e[n-1];return o!=null&&Fd(o)&&typeof t=="string"&&Ld.has(t)});return typeof r=="string"?r:null}function Qo(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}import Ud from"path";import{z as Xo}from"zod";var Md=new Set(["Edit","Write","NotebookEdit"]),Hd=Xo.looseObject({file_path:Xo.string()}),Wd="create",Zo=f("PreToolUse",e=>{if(!Md.has(e.tool_name))return;let r=Hd.safeParse(e.tool_input);if(!r.success)return;let{cwd:t}=e;if(!m(t)||!v(t))return;let n=Ud.relative(t,r.data.file_path);if(!(n.startsWith("..")||n!==".ripplo"&&!n.startsWith(".ripplo/"))&&!Qe(t,e.session_id,Wd))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Editing \`.ripplo/\` files (${n}) 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 qd from"path";import ei from"picomatch";import{graphql as Bd}from"gql.tada";import{z as ri}from"zod";var Vd=new Set(["Edit","Write","NotebookEdit"]),Gd=ri.looseObject({file_path:ri.string()}),zd=Bd(`
450
+ `),hookEventName:"PostToolUse"}}}function Sd(e,r){let t=Le(r),n=wd("sha256").update(JSON.stringify(t)).digest("hex");return ie(e,"coverage-warn")===n?[]:(se(e,"coverage-warn",n),t.length===0?[]:[Ee(t)])}import{z as Go}from"zod";var xd=Go.looseObject({command:Go.string()}),Rd=/\bripplo\s+hooks\s+pause\b/,zo=f("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=xd.safeParse(e.tool_input);if(!r.success||!Rd.test(r.data.command))return;let{cwd:t}=e;if(m(t))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 Id}from"shell-quote";import{z as Ko}from"zod";import{existsSync as Cd,mkdirSync as Pd,rmSync as Py,writeFileSync as Ed}from"fs";import kr from"path";function Ke(e,r,t){let n=Jo(e,r,t);Pd(kr.dirname(n),{recursive:!0}),Ed(n,"")}function Qe(e,r,t){return Cd(Jo(e,r,t))}function Jo(e,r,t){return kr.join($d(e,r),t)}function $d(e,r){return kr.join(e,".ripplo",".local","skills-loaded",r)}var Ad=Ko.looseObject({command:Ko.string()}),jd="run",Ld=new Set(["tail","head","less","more","wc","sort","uniq","awk","sed","grep"]),Yo=f("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let r=Ad.safeParse(e.tool_input);if(!r.success)return;let t=_d(r.data.command),n=Td(t);if(n==null)return;let{cwd:o}=e;if(!m(o)||!v(o))return;if(!Qe(o,e.session_id,n.skill))return Qo(n.reason);let i=n.kind==="run"?Nd(t):null;if(i!=null)return Qo(`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 Td(e){return Dd(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"}:Od(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:jd}:null}function Dd(e){return e.some((r,t)=>r==="ripplo"&&e[t+1]==="explore"&&e[t+2]==="replay")}function Od(e){return e.some((r,t)=>r==="ripplo"&&e[t+1]==="run")}function _d(e){try{return Id(e)}catch{return[]}}function Fd(e){return typeof e=="object"&&"op"in e&&e.op==="|"}function Nd(e){let r=e.find((t,n)=>{let o=e[n-1];return o!=null&&Fd(o)&&typeof t=="string"&&Ld.has(t)});return typeof r=="string"?r:null}function Qo(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}import Ud from"path";import{z as Xo}from"zod";var Md=new Set(["Edit","Write","NotebookEdit"]),Hd=Xo.looseObject({file_path:Xo.string()}),Wd="create",Zo=f("PreToolUse",e=>{if(!Md.has(e.tool_name))return;let r=Hd.safeParse(e.tool_input);if(!r.success)return;let{cwd:t}=e;if(!m(t)||!v(t))return;let n=Ud.relative(t,r.data.file_path);if(!(n.startsWith("..")||n!==".ripplo"&&!n.startsWith(".ripplo/"))&&!Qe(t,e.session_id,Wd))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Editing \`.ripplo/\` files (${n}) 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 qd from"path";import ei from"picomatch";import{graphql as Bd}from"gql.tada";import{z as ri}from"zod";var Vd=new Set(["Edit","Write","NotebookEdit"]),Gd=ri.looseObject({file_path:ri.string()}),zd=Bd(`
450
451
  query PreEditScopeGate($projectId: String!, $cwd: String!) {
451
452
  project(id: $projectId) {
452
453
  id
@@ -477,8 +478,8 @@ ${c("create","DSL authoring + lint rules")}`,hookEventName:"PreToolUse"}}:{hookS
477
478
  }
478
479
  }
479
480
  }
480
- `),ii=f("UserPromptSubmit",async e=>{let{cwd:r}=e;if(!m(r)||!v(r))return;let t=Je(r);if(ie(r,"scope-reminder")===t)return;let n=w(r).unwrapOr(void 0);if(n==null)return;let o=await u({config:n,document:tc,variables:{cwd:n.cwd,projectId:n.projectId}}).catch(()=>null);se(r,"scope-reminder",t);let i=o?.project?.devSession?.scopeItems??[];return Ao({cwd:r,scope:{available:o!=null,items:i.map(s=>nc(s))}}),o==null?{hookSpecificOutput:{additionalContext:`Ripplo scope: unknown \u2014 server unreachable, scope guardrails are not enforcing. Check the dev session (\`npx ripplo doctor\`). ${c("start")}`,hookEventName:"UserPromptSubmit"}}:{hookSpecificOutput:{additionalContext:i.length===0?`Ripplo scope: empty. ${c("run")}`:`Ripplo scope (${String(i.length)}):
481
- ${i.map(s=>{let l=s.workflow;return l==null?` [intent] (${s.id}) ${s.label??""}`:` [${l.spec==null?"stub":"implemented"}] (${s.id}) ${l.slug}`}).join(`
481
+ `),ii=f("UserPromptSubmit",async e=>{let{cwd:r}=e;if(!m(r)||!v(r))return;let t=Je(r);if(ie(r,"scope-reminder")===t)return;let n=w(r).unwrapOr(void 0);if(n==null)return;let o=await u({config:n,document:tc,variables:{cwd:n.cwd,projectId:n.projectId}}).catch(()=>null);se(r,"scope-reminder",t);let i=o?.project?.devSession?.scopeItems??[];return jo({cwd:r,scope:{available:o!=null,items:i.map(a=>nc(a))}}),o==null?{hookSpecificOutput:{additionalContext:`Ripplo scope: unknown \u2014 server unreachable, scope guardrails are not enforcing. Check the dev session (\`npx ripplo doctor\`). ${c("start")}`,hookEventName:"UserPromptSubmit"}}:{hookSpecificOutput:{additionalContext:i.length===0?`Ripplo scope: empty. ${c("run")}`:`Ripplo scope (${String(i.length)}):
482
+ ${i.map(a=>{let l=a.workflow;return l==null?` [intent] (${a.id}) ${a.label??""}`:` [${l.spec==null?"stub":"implemented"}] (${a.id}) ${l.slug}`}).join(`
482
483
  `)}`,hookEventName:"UserPromptSubmit"}}});function nc(e){let r=e.workflow;return r==null?{label:e.label,slug:null,status:"intent"}:{label:e.label,slug:r.slug,status:r.spec==null?"stub":"implemented"}}var oc="# Ripplo \u2014 always-on session context\n\nEvery user-facing change in this repo ships with a deterministic, backend-aware test that proves it works end-to-end.\n\n## Load the right skill before acting \u2014 this is critical\n\nThe skills below carry the procedural detail you need to do Ripplo work correctly: entity/world modeling, parallel-isolation in engine impls, backend state assertions, scope discipline, artifact-read order on failures, parallelization patterns. **None of that is reproduced in this preamble** \u2014 only the always-on guardrails are. If you act on a Ripplo task without loading the matching skill, you will skip rules that exist to prevent specific past failure modes (cross-run data leakage, \"passing\" tests that never asserted backend state, etc.).\n\nMatch the task to a skill from the triage table \u2014 if unsure between two, load both. If completely unsure, load `/ripplo:discover` or `/ripplo:create`. Loading is cheap. Acting under-informed is not.\n\n## Skill triage\n\n- Load `/ripplo:setup` skill for instructions on initializing Ripplo in a project; `npx ripplo doctor` reports the engine endpoint missing.\n- Load `/ripplo:discover` skill for instructions on planning/stubbing test coverage for a new project or new feature area.\n- Load `/ripplo:create` skill for instructions on authoring a single test spec for a user flow.\n- Load `/ripplo:run` skill for instructions on executing tests, managing the testing scope this session is responsible for, diagnosing a failed run (read artifacts in `.ripplo/debug/<runId>/` before re-running), and filing caught app bugs.\n- Load `/ripplo:fuzz` skill for instructions on triaging findings from the background explorer's findings log.\n\n## Universal rules\n\n- **Two background processes must be running before feature work.** (1) The app's dev server, so the app is reachable; (2) `npx ripplo daemon` (run `/ripplo:start` to spawn it, or invoke it directly via Bash with `run_in_background`), so the dev session is live. Without the daemon, dev-mode hooks don't arm (`ripplo run` will auto-start one if absent). Without the dev server, runs fail when they try to hit the app. `npx ripplo doctor` reports both.\n- **Two funnels.** Definitions \u2192 `createRipplo({ entities, singletons, tests })` in `.ripplo/index.ts`. Implementations \u2192 `createEngine(ripplo, { entities: impls, singletons })` in your app server's `test/engine.ts` \u2014 one `seed`/`read` impl per entity. Never call either elsewhere. TS enforces exhaustiveness across both.\n- **\"Done\" = app code delivers the behavior AND a passing test proves it.** Both halves. Shipping without a test isn't done; writing a test against broken UI/API isn't done.\n- **Scope is your job.** For any non-trivial change, enumerate every flow it could affect and either `scope add` existing tests or stub new ones with `test(\"Intent\")` (no body). Don't wait to be told.\n- **`scope remove` is never for size/effort.** Valid only when an item is genuinely out of scope (wrong flow, duplicate, user said \"not this session,\" feature cut). \"Too many stubs\" \u2192 parallelize with subagents. Don't present \"implement vs. remove\" as a neutral A/B.\n- **Stub gates are not a question.** When `stop-enforce` blocks on unimplemented stubs, implement them \u2014 don't ask the user \"implement or defer?\", don't propose pausing hooks as option B. The fix isn't done until the test is. New scaffolding (entity, world, engine impl) is in-scope work, not follow-up.\n- **Backend assertions are mandatory on mutations.** Every mutation step carries an `Entity.created/updated/deleted` in its `.expect(...)` \u2014 Ripplo checks your app's state against what the test expected. A UI-only check on a mutation ships the bug as green.\n- **Never weaken a test to make it pass.** No `contains`/regex for exact text, no removed assertions, no fabricated locators. App lacks an accessible name \u2192 add one to the app, don't fall back to `testId()`. App bug \u2192 report with evidence.\n- **Artifacts first, re-run last.** Failed runs write `.ripplo/debug/<runId>/behavior.jsonl` \u2014 a causal stream of actions, assertions, rrweb DOM, console, network, and server spans \u2014 and the run output renders the findings. Read them. Never pipe `ripplo run` through `grep`/`tail`/`head`. Form a hypothesis citing an event, make one change, re-run once.\n- **`.ripplo/ripplo.lock` is committed, never hand-edited.** `ripplo lint` / `ripplo compile` regenerates it. Pre-commit runs `ripplo compile --check`.\n- **Scratch files live in `.ripplo/.local/`.** Never loose in `.ripplo/`.\n- **Worktrees are self-contained.** Each worktree has its own `.ripplo/` checkout, DevSession, scope, and debug artifacts. Auth and projectId are shared globally. Env files (typically gitignored) won't carry over to a fresh worktree \u2014 copy from main or point at a shared file. **If sibling worktrees run dev servers on different ports, the worktree's env file must update both `RIPPLO_APP_URL` and `RIPPLO_ENGINE_URL` to match the port that worktree's dev server is bound to** (e.g. main on `:3000`, this worktree on `:3001` \u2192 set `RIPPLO_APP_URL=http://localhost:3001` and `RIPPLO_ENGINE_URL=http://localhost:3001/ripplo` in the worktree's env file). Mismatched ports = `npx ripplo daemon` talks to the wrong server, runs silently fail or hit the sibling worktree's app.\n- **DSL locators are semantic.** Use `role`/`button`/`textbox`/`heading`/`link`; `testId` only when no ARIA role exists.\n- **New backend state?** Add an `entity(...)` in `.ripplo/entities/` and a `seed`/`read` impl in your app's engine funnel (TS flags the missing impl).\n\n## Key files\n\n- `.ripplo/index.ts` \u2014 `createRipplo` call.\n- `.ripplo/{entities,singletons,worlds,tests}/index.ts` \u2014 registry aggregators.\n- `.ripplo/project.json` \u2014 project id + env-file pointers.\n- `<app>/src/test/engine.ts` \u2014 single impl funnel via `createEngine`.\n- `/ripplo:create` skill \u2014 DSL reference (entities, worlds, tests, backend assertions). Full primitive catalog at `node_modules/@ripplo/testing/DSL.md`.\n",si=f("SessionStart",e=>{if(m(e.cwd))return{hookSpecificOutput:{additionalContext:oc,hookEventName:"SessionStart"}}});import ic from"path";import sc from"process";import{CancellationTokenSource as ac}from"vscode-jsonrpc/node";import{graphql as lc}from"gql.tada";function ai(e){return`--- Ripplo Run Failures (scope) ---
483
484
  ${e.join(`
484
485
  `)}
@@ -511,19 +512,19 @@ ${o.errors.join(`
511
512
 
512
513
  `)}});async function cc(e){let r=await Y(e);if(r.isErr())return{errors:[`--- Compilation failed ---
513
514
  ${x(r.error)}
514
- ${c("create")}`],infraOnly:!1};let t=r.value,n=await mc(e,t),o=pc(t),i=uc(t),a=n.runnableSlugs.length>0?await gc(e):null,s=[o,i,a?.error??null,n.error].filter(d=>d!=null),l=s.length>0&&o==null&&i==null&&n.error==null&&(a?.infra??!1);return{errors:s,infraOnly:l}}function pc(e){let r=W(e);return r.length===0?null:`--- Ripplo Lint ---
515
+ ${c("create")}`],infraOnly:!1};let t=r.value,n=await mc(e,t),o=pc(t),i=uc(t),s=n.runnableSlugs.length>0?await gc(e):null,a=[o,i,s?.error??null,n.error].filter(d=>d!=null),l=a.length>0&&o==null&&i==null&&n.error==null&&(s?.infra??!1);return{errors:a,infraOnly:l}}function pc(e){let r=W(e);return r.length===0?null:`--- Ripplo Lint ---
515
516
  ${q(r)}
516
517
  ${c("create")}`}function uc(e){let r=U(e);return r.length===0?null:`--- Unimplemented stubs ---
517
518
  ${r.join(", ")}
518
519
  Implement the stub now with \`workflow("Intent", () => ({ given, steps }))\`. Do not ask the user "implement or defer?" \u2014 that framing is forbidden by /ripplo:create. New scaffolding (entity, world, engine impl) is in-scope, not follow-up.
519
- ${c("create")}`}async function mc(e,r){let t=new Set(U(r).map(p=>A(p))),n=new Set(r.workflows.map(p=>A(p.name))),o=(p,k)=>n.has(p)?t.has(p):fc(k),i=w(e).unwrapOr(void 0);if(i==null)return{error:`--- Testing Scope (not checked) ---
520
- No project config \u2014 \`npx ripplo init\` hasn't run here, so scope/stub done-checks are not enforcing. ${c("setup")}`,runnableSlugs:[]};let a=await u({config:i,document:dc,variables:{cwd:i.cwd,projectId:i.projectId}}).catch(()=>null);if(a==null)return{error:`--- Testing Scope (not checked) ---
521
- Ripplo server unreachable \u2014 scope/stub done-checks are not enforcing. Verify the dev session is live (\`npx ripplo doctor\`, ${c("start")}) before declaring work done.`,runnableSlugs:[]};let s=a.project?.devSession?.scopeItems??[],l=s.flatMap(p=>{let k=p.workflow;return k==null?[` [intent] ${p.label??"(no label)"} \u2014 write a test for this flow`]:o(k.slug,k.spec)?[` [stub] ${k.slug} \u2014 implement \`${k.name}\``]:[]}),d=s.flatMap(p=>p.workflow!=null&&!o(p.workflow.slug,p.workflow.spec)?[p.workflow.slug]:[]);return{error:l.length===0?null:`--- Testing Scope ---
520
+ ${c("create")}`}async function mc(e,r){let t=new Set(U(r).map(p=>j(p))),n=new Set(r.workflows.map(p=>j(p.name))),o=(p,y)=>n.has(p)?t.has(p):fc(y),i=w(e).unwrapOr(void 0);if(i==null)return{error:`--- Testing Scope (not checked) ---
521
+ No project config \u2014 \`npx ripplo init\` hasn't run here, so scope/stub done-checks are not enforcing. ${c("setup")}`,runnableSlugs:[]};let s=await u({config:i,document:dc,variables:{cwd:i.cwd,projectId:i.projectId}}).catch(()=>null);if(s==null)return{error:`--- Testing Scope (not checked) ---
522
+ Ripplo server unreachable \u2014 scope/stub done-checks are not enforcing. Verify the dev session is live (\`npx ripplo doctor\`, ${c("start")}) before declaring work done.`,runnableSlugs:[]};let a=s.project?.devSession?.scopeItems??[],l=a.flatMap(p=>{let y=p.workflow;return y==null?[` [intent] ${p.label??"(no label)"} \u2014 write a test for this flow`]:o(y.slug,y.spec)?[` [stub] ${y.slug} \u2014 implement \`${y.name}\``]:[]}),d=a.flatMap(p=>p.workflow!=null&&!o(p.workflow.slug,p.workflow.spec)?[p.workflow.slug]:[]);return{error:l.length===0?null:`--- Testing Scope ---
522
523
  ${l.join(`
523
524
  `)}
524
- ${c("create")}`,runnableSlugs:d}}function fc(e){return typeof e=="object"&&e!=null&&Reflect.get(e,"stub")===!0}async function gc(e){let r=sc.argv[1];if(r==null)return{error:ye("CLI entry missing (process.argv[1])"),infra:!1};let t=await Fe(e);if(t!=null){let i=Me(t);return Ne(t.serverUrl)?{error:ye(i),infra:!1}:{error:wr({lines:[i],retried:!1}),infra:!0}}let n=await te({cliEntry:r,cwd:e});if(n.isErr())return{error:ye(T(n.error)),infra:!1};let o=n.value;try{return await hc({connection:o,cwd:e})}finally{o.spawned&&await ne(o),o.socket.destroy()}}async function hc({connection:e,cwd:r}){let t=await li({connection:e,cwd:r,tests:[]});if(t.kind==="transport")return{error:ye(t.message),infra:!1};let n=t.notRun.length>0?await li({connection:e,cwd:r,tests:t.notRun.map(a=>a.id)}):null,o=n==null||n.kind==="transport"?t.notRun:n.notRun,i=[...t.failedLines,...n!=null&&n.kind==="done"?n.failedLines:[]];return yc({failedLines:i,notRun:o,retried:n!=null})}function yc({failedLines:e,notRun:r,retried:t}){if(e.length===0&&r.length===0)return{error:null,infra:!1};let n=e.length>0?ai(e):null,o=r.length>0?wr({lines:r.map(i=>i.line),retried:t}):null;return{error:[n,o].filter(i=>i!=null).join(`
525
+ ${c("create")}`,runnableSlugs:d}}function fc(e){return typeof e=="object"&&e!=null&&Reflect.get(e,"stub")===!0}async function gc(e){let r=sc.argv[1];if(r==null)return{error:ye("CLI entry missing (process.argv[1])"),infra:!1};let t=await Fe(e);if(t!=null){let i=Me(t);return Ne(t.serverUrl)?{error:ye(i),infra:!1}:{error:wr({lines:[i],retried:!1}),infra:!0}}let n=await te({cliEntry:r,cwd:e});if(n.isErr())return{error:ye(T(n.error)),infra:!1};let o=n.value;try{return await hc({connection:o,cwd:e})}finally{o.spawned&&await ne(o),o.socket.destroy()}}async function hc({connection:e,cwd:r}){let t=await li({connection:e,cwd:r,tests:[]});if(t.kind==="transport")return{error:ye(t.message),infra:!1};let n=t.notRun.length>0?await li({connection:e,cwd:r,tests:t.notRun.map(s=>s.id)}):null,o=n==null||n.kind==="transport"?t.notRun:n.notRun,i=[...t.failedLines,...n!=null&&n.kind==="done"?n.failedLines:[]];return yc({failedLines:i,notRun:o,retried:n!=null})}function yc({failedLines:e,notRun:r,retried:t}){if(e.length===0&&r.length===0)return{error:null,infra:!1};let n=e.length>0?ai(e):null,o=r.length>0?wr({lines:r.map(i=>i.line),retried:t}):null;return{error:[n,o].filter(i=>i!=null).join(`
525
526
 
526
- `),infra:n==null}}async function li({connection:e,cwd:r,tests:t}){let n=[],o=[],i=ic.join(r,".ripplo","debug"),a=new ac;return(await De({connection:e,request:{all:!1,headed:!1,tests:[...t]},token:a.token,onEvent:l=>{kc({debugDir:i,event:l,failedLines:n,notRun:o})}})).match(l=>l.kind==="daemon-error"?{kind:"transport",message:He(l.error)}:{failedLines:n,kind:"done",notRun:o},l=>({kind:"transport",message:T(l)}))}function kc({debugDir:e,event:r,failedLines:t,notRun:n}){if(r.kind!=="test-outcome"||r.outcome.kind==="pass")return;let o=Ue({debugDir:e,event:r})??r.testName;if(r.outcome.kind==="dispatch-error"||r.outcome.kind==="infra-error"){n.push({id:`${r.workflowName}/${r.testName}`,line:o});return}t.push(o)}import{z as ci}from"zod";var wc=ci.looseObject({skill:ci.string()}),pi=f("PostToolUse",e=>{if(e.tool_name!=="Skill")return;let r=wc.safeParse(e.tool_input);if(!r.success)return;let t=/^ripplo:(.+)$/.exec(r.data.skill);if(t==null)return;let n=t[1];n!=null&&m(e.cwd)&&Ke(e.cwd,e.session_id,n)});var vc=/(?:^|\s)\/ripplo:([a-z][a-z0-9-]*)\b/gi,ui=f("UserPromptSubmit",e=>{m(e.cwd)&&[...e.prompt.matchAll(vc)].map(r=>r[1]).filter(r=>r!=null).forEach(r=>{Ke(e.cwd,e.session_id,r)})});Fc();V(process.cwd());Pr();var mi={"exit-plan-gate":Eo,"plan-reminder":$o,"post-edit-flag-stubs":Uo,"post-edit-lint":Vo,"pre-bash-hooks-pause-gate":zo,"pre-bash-run-gate":Yo,"pre-edit-ripplo-skill-gate":Zo,"pre-edit-scope-gate":ti,"pre-edit-watch-gate":oi,"scope-reminder":ii,"session-preamble":si,"stop-enforce":di,"track-skill-load":pi,"track-skill-prompt":ui};async function Cc(){Sc({pkg:{name:"ripplo",version:C()}}).notify({message:Ot()}),await xc(Rc(process.argv)).scriptName("ripplo").version(C()).command(Ic()).command("concurrency [value]","Show or set max local concurrent runs (daemon applies live)",e=>e.positional("value",{type:"number"}),e=>Vt({value:e.value})).command("auth <subcommand>","Manage authentication",_c).command("projects <subcommand>","Inspect Ripplo projects",Oc).command("hooks <subcommand>","Pause or resume Ripplo hooks",Dc).command("init","Scaffold .ripplo/ in this project",e=>e.option("project",{type:"string"}).option("env",{type:"string"}).option("app-url",{type:"string"}).option("engine-url",{type:"string"}),e=>gn({appUrl:e["app-url"],engineUrl:e["engine-url"],envFile:e.env,projectId:e.project})).command("run [ids..]","Run tests locally via the daemon (auto-starts it if absent)",Lc,e=>Bn({all:e.all,headed:e.headed,ids:e.ids,keepAlive:e["keep-alive"]})).command(Ac()).command($c()).command(jc()).command("lint","Static model analysis (cascade gaps + law conflicts; no live app)",()=>{},()=>hn()).command("sync","Push the compiled .ripplo/ resources to the server (no run)",()=>{},()=>so()).command("compile","Compile the DSL and write .ripplo/ripplo.lock",e=>e.option("check",{default:!1,describe:"Exit non-zero if the lockfile is missing or stale (does not write)",type:"boolean"}),e=>qt({check:e.check})).command("update","Update ripplo to the latest published version (and hand the daemon off to it)",()=>{},()=>ho()).command("doctor","Check project health",()=>{},()=>tn()).command("status","Report stub tests and daemon status",e=>e.option("format",{choices:["json","summary"],default:"json",describe:"Output format"}),e=>Co({format:e.format})).command("scope <subcommand>","Manage testing scope",Tc).command("run-worker",!1,()=>{},()=>Jn()).command("hook <name>","Internal: run a Claude Code plugin hook",e=>e.positional("name",{choices:Object.keys(mi),demandOption:!0,type:"string"}),e=>Pc(e.name)).strict().help().parse()}async function Pc(e){let r=mi[e];r==null&&(process.stderr.write(`Unknown hook: ${e}
527
+ `),infra:n==null}}async function li({connection:e,cwd:r,tests:t}){let n=[],o=[],i=ic.join(r,".ripplo","debug"),s=new ac;return(await De({connection:e,request:{all:!1,headed:!1,tests:[...t]},token:s.token,onEvent:l=>{kc({debugDir:i,event:l,failedLines:n,notRun:o})}})).match(l=>l.kind==="daemon-error"?{kind:"transport",message:He(l.error)}:{failedLines:n,kind:"done",notRun:o},l=>({kind:"transport",message:T(l)}))}function kc({debugDir:e,event:r,failedLines:t,notRun:n}){if(r.kind!=="test-outcome"||r.outcome.kind==="pass")return;let o=Ue({debugDir:e,event:r})??r.testName;if(r.outcome.kind==="dispatch-error"||r.outcome.kind==="infra-error"){n.push({id:`${r.workflowName}/${r.testName}`,line:o});return}t.push(o)}import{z as ci}from"zod";var wc=ci.looseObject({skill:ci.string()}),pi=f("PostToolUse",e=>{if(e.tool_name!=="Skill")return;let r=wc.safeParse(e.tool_input);if(!r.success)return;let t=/^ripplo:(.+)$/.exec(r.data.skill);if(t==null)return;let n=t[1];n!=null&&m(e.cwd)&&Ke(e.cwd,e.session_id,n)});var vc=/(?:^|\s)\/ripplo:([a-z][a-z0-9-]*)\b/gi,ui=f("UserPromptSubmit",e=>{m(e.cwd)&&[...e.prompt.matchAll(vc)].map(r=>r[1]).filter(r=>r!=null).forEach(r=>{Ke(e.cwd,e.session_id,r)})});Fc();V(process.cwd());Pr();var mi={"exit-plan-gate":Eo,"plan-reminder":$o,"post-edit-flag-stubs":Uo,"post-edit-lint":Vo,"pre-bash-hooks-pause-gate":zo,"pre-bash-run-gate":Yo,"pre-edit-ripplo-skill-gate":Zo,"pre-edit-scope-gate":ti,"pre-edit-watch-gate":oi,"scope-reminder":ii,"session-preamble":si,"stop-enforce":di,"track-skill-load":pi,"track-skill-prompt":ui};async function Cc(){Sc({pkg:{name:"ripplo",version:C()}}).notify({message:Ot()}),await xc(Rc(process.argv)).scriptName("ripplo").version(C()).command(Ic()).command("concurrency [value]","Show or set max local concurrent runs (daemon applies live)",e=>e.positional("value",{type:"number"}),e=>Vt({value:e.value})).command("auth <subcommand>","Manage authentication",_c).command("projects <subcommand>","Inspect Ripplo projects",Oc).command("hooks <subcommand>","Pause or resume Ripplo hooks",Dc).command("init","Scaffold .ripplo/ in this project",e=>e.option("project",{type:"string"}).option("env",{type:"string"}).option("app-url",{type:"string"}).option("engine-url",{type:"string"}),e=>gn({appUrl:e["app-url"],engineUrl:e["engine-url"],envFile:e.env,projectId:e.project})).command("run [ids..]","Run tests locally via the daemon (auto-starts it if absent)",Lc,e=>Bn({all:e.all,headed:e.headed,ids:e.ids,keepAlive:e["keep-alive"]})).command(jc()).command($c()).command(Ac()).command("lint","Static model analysis (cascade gaps + law conflicts; no live app)",()=>{},()=>hn()).command("sync","Push the compiled .ripplo/ resources to the server (no run)",()=>{},()=>so()).command("compile","Compile the DSL and write .ripplo/ripplo.lock",e=>e.option("check",{default:!1,describe:"Exit non-zero if the lockfile is missing or stale (does not write)",type:"boolean"}),e=>qt({check:e.check})).command("update","Update ripplo to the latest published version (and hand the daemon off to it)",()=>{},()=>ho()).command("doctor","Check project health",()=>{},()=>tn()).command("status","Report stub tests and daemon status",e=>e.option("format",{choices:["json","summary"],default:"json",describe:"Output format"}),e=>Co({format:e.format})).command("scope <subcommand>","Manage testing scope",Tc).command("run-worker",!1,()=>{},()=>Jn()).command("hook <name>","Internal: run a Claude Code plugin hook",e=>e.positional("name",{choices:Object.keys(mi),demandOption:!0,type:"string"}),e=>Pc(e.name)).strict().help().parse()}async function Pc(e){let r=mi[e];r==null&&(process.stderr.write(`Unknown hook: ${e}
527
528
  `),process.exit(1));let t=await Ec(),n=t.trim()===""?{}:JSON.parse(t),o=await r.run(n);o!=null&&process.stdout.write(JSON.stringify(o))}function Ec(){return new Promise((e,r)=>{if(process.stdin.isTTY){e("");return}let t=[];process.stdin.on("data",n=>t.push(n)),process.stdin.on("end",()=>{e(Buffer.concat(t).toString("utf8"))}),process.stdin.on("error",r)})}Cc().catch(e=>{process.stderr.write(`${Nr(e)}
528
- `),process.exit(1)});function $c(){return{command:"report-bug",describe:"Report a critical application bug caught while building or testing",builder:e=>e.option("kind",{choices:["new_feature_bug","regression","latent_bug"],demandOption:!0,describe:"new_feature_bug: broke the new thing being built; regression: broke previously working behavior; latent_bug: pre-existing bug exposed by new test coverage"}).option("title",{demandOption:!0,describe:"Short bug name",type:"string"}).option("root-cause",{demandOption:!0,describe:"What was actually wrong",type:"string"}).option("surfaced-by",{demandOption:!0,describe:"How the test/run exposed it (cite evidence)",type:"string"}).option("run",{demandOption:!0,describe:"Run id where it surfaced \u2014 links the bug to its replay. For an exploration finding, pass its explore-\u2026 id (recorded, but not replay-linkable)",type:"string"}).option("test",{describe:"Test id that surfaced it",type:"string"}),handler:e=>qn({kind:e.kind,rootCause:e["root-cause"],runId:e.run,surfacedBy:e["surfaced-by"],testId:e.test,title:e.title})}}function Ic(){return{command:"daemon",describe:"Run the long-lived local executor (IPC socket + run subscription)",builder:e=>e.option("explore",{default:!1,describe:"Opt in to background exploration when idle (experimental)",type:"boolean"}).option("exploreConcurrency",{default:2,describe:"Max concurrent background exploration trails (keeps the machine responsive)",type:"number"}),handler:e=>yo({explore:e.explore,exploreConcurrency:e.exploreConcurrency})}}function jc(){return{command:"explore",describe:"Run state-space exploration trails in the foreground (app + engine must be running)",builder:e=>e.option("trails",{default:10,describe:"Number of trails to execute before stopping (stops early on saturation)",type:"number"}).option("headed",{default:!1,describe:"Watch trails in a visible browser (stop the daemon explorer first if it holds the machine lock)",type:"boolean"}).option("max-length",{describe:"Max steps per trail (default 12 \u2014 lower means shorter, easier-to-watch trails)",type:"number"}).command("findings [findingId]","List pending exploration findings, or show one finding's full detail",r=>r.positional("findingId",{type:"string"}).option("json",{default:!1,describe:"Emit the findings list as JSON",type:"boolean"}),r=>_n({findingId:r.findingId,json:r.json})).command("replay <findingId>","Replay a finding's minimal trail \u2014 a clean replay resolves the finding and covers its targets",r=>r.positional("findingId",{demandOption:!0,type:"string"}),r=>Fn({findingId:r.findingId})).command("dismiss <findingId>","Dismiss a finding you don't intend to act on \u2014 prune later to drop it from the log",r=>r.positional("findingId",{demandOption:!0,type:"string"}),r=>Nn({findingId:r.findingId})).command("prune","Rewrite the findings log, dropping dismissed findings and ones no longer in the model",r=>r,()=>Un()),handler:e=>Mn({headed:e.headed,maxLength:e["max-length"],trails:e.trails})}}function Ac(){return{command:"snapshot <runId>",describe:"Render a PNG of the page at a point in a run's rrweb recording",builder:e=>e.positional("runId",{demandOption:!0,type:"string"}).option("at",{describe:"Moment to render: epoch-ms timestamp exactly as found in behavior.jsonl",type:"number"}).option("offset",{describe:"Moment to render: ms from the start of the recording (alternative to --at)",type:"number"}).conflicts("at","offset"),handler:e=>io({at:e.at,offset:e.offset,runId:e.runId})}}function Lc(e){let r=[];return e.positional("ids",{array:!0,default:r,describe:"Test ids to run",type:"string"}).option("all",{default:!1,describe:"Run every test in the suite (expensive)",type:"boolean"}).option("headed",{default:!1,describe:"Run with a visible browser window",type:"boolean"}).option("keep-alive",{default:!1,describe:"Leave an auto-started daemon running after the batch",type:"boolean"})}function Tc(e){return e.command("status","Print the current scope",r=>r.option("format",{choices:["json","text"],default:"text",describe:"Output format"}),r=>bo({format:r.format})).command("add <test-ids..>","Bind one or more existing tests (stubs or implemented) to scope as agent intent",r=>{let t=[];return r.positional("test-ids",{array:!0,default:t,demandOption:!0,describe:"Slugs of existing workflows",type:"string"})},r=>So({testIds:r["test-ids"]})).command("link <id> <test-id>","Link an existing scope item to a test",r=>r.positional("id",{demandOption:!0,describe:"Scope item id",type:"string"}).positional("test-id",{demandOption:!0,describe:"Slug of the workflow to link",type:"string"}),r=>xo({id:r.id,testId:r["test-id"]})).command("remove <ids..>","Remove one or more scope items by id",r=>{let t=[];return r.positional("ids",{array:!0,default:t,demandOption:!0,describe:"Scope item ids",type:"string"})},r=>Ro({ids:r.ids})).demandCommand(1)}function Dc(e){return e.command("pause","Disable all Ripplo pre-edit gates and stop enforcement until resumed",()=>{},()=>nn()).command("resume","Re-enable Ripplo hooks paused via `ripplo hooks pause`",()=>{},()=>on()).demandCommand(1)}function Oc(e){return e.command("list","List projects you have access to (JSON)",()=>{},()=>Hn()).demandCommand(1)}function _c(e){return e.command("login","Authenticate via device flow",()=>{},()=>Mt()).command("status","Show authentication status",()=>{},()=>Ht()).command("logout","Remove the saved token",()=>{},()=>{Wt()}).demandCommand(1)}function Fc(){let e=process.cwd(),r=vr(e);r!=null&&r!==e&&(process.chdir(r),process.stderr.write(`ripplo: resolved .ripplo/ at ${r}
529
+ `),process.exit(1)});function $c(){return{command:"report-bug",describe:"Report a critical application bug caught while building or testing",builder:e=>e.option("kind",{choices:["new_feature_bug","regression","latent_bug"],demandOption:!0,describe:"new_feature_bug: broke the new thing being built; regression: broke previously working behavior; latent_bug: pre-existing bug exposed by new test coverage"}).option("title",{demandOption:!0,describe:"Short bug name",type:"string"}).option("root-cause",{demandOption:!0,describe:"What was actually wrong",type:"string"}).option("surfaced-by",{demandOption:!0,describe:"How the test/run exposed it (cite evidence)",type:"string"}).option("run",{demandOption:!0,describe:"Run id where it surfaced \u2014 links the bug to its replay. For an exploration finding, pass its explore-\u2026 id (recorded, but not replay-linkable)",type:"string"}).option("test",{describe:"Test id that surfaced it",type:"string"}),handler:e=>qn({kind:e.kind,rootCause:e["root-cause"],runId:e.run,surfacedBy:e["surfaced-by"],testId:e.test,title:e.title})}}function Ic(){return{command:"daemon",describe:"Run the long-lived local executor (IPC socket + run subscription)",builder:e=>e.option("executor",{choices:["local","cloud"],default:"local",describe:"Run executor (local pool, or cloud fleet via a per-session tunnel)"}).option("explore",{default:!1,describe:"Opt in to background exploration when idle (experimental)",type:"boolean"}).option("exploreConcurrency",{default:2,describe:"Max concurrent background exploration trails (keeps the machine responsive)",type:"number"}),handler:e=>yo({executor:e.executor,explore:e.explore,exploreConcurrency:e.exploreConcurrency})}}function Ac(){return{command:"explore",describe:"Run state-space exploration trails in the foreground (app + engine must be running)",builder:e=>e.option("trails",{default:10,describe:"Number of trails to execute before stopping (stops early on saturation)",type:"number"}).option("headed",{default:!1,describe:"Watch trails in a visible browser (stop the daemon explorer first if it holds the machine lock)",type:"boolean"}).option("max-length",{describe:"Max steps per trail (default 12 \u2014 lower means shorter, easier-to-watch trails)",type:"number"}).command("findings [findingId]","List pending exploration findings, or show one finding's full detail",r=>r.positional("findingId",{type:"string"}).option("json",{default:!1,describe:"Emit the findings list as JSON",type:"boolean"}),r=>_n({findingId:r.findingId,json:r.json})).command("replay <findingId>","Replay a finding's minimal trail \u2014 a clean replay resolves the finding and covers its targets",r=>r.positional("findingId",{demandOption:!0,type:"string"}),r=>Fn({findingId:r.findingId})).command("dismiss <findingId>","Dismiss a finding you don't intend to act on \u2014 prune later to drop it from the log",r=>r.positional("findingId",{demandOption:!0,type:"string"}),r=>Nn({findingId:r.findingId})).command("prune","Rewrite the findings log, dropping dismissed findings and ones no longer in the model",r=>r,()=>Un()),handler:e=>Mn({headed:e.headed,maxLength:e["max-length"],trails:e.trails})}}function jc(){return{command:"snapshot <runId>",describe:"Render a PNG of the page at a point in a run's rrweb recording",builder:e=>e.positional("runId",{demandOption:!0,type:"string"}).option("at",{describe:"Moment to render: epoch-ms timestamp exactly as found in behavior.jsonl",type:"number"}).option("offset",{describe:"Moment to render: ms from the start of the recording (alternative to --at)",type:"number"}).conflicts("at","offset"),handler:e=>io({at:e.at,offset:e.offset,runId:e.runId})}}function Lc(e){let r=[];return e.positional("ids",{array:!0,default:r,describe:"Test ids to run",type:"string"}).option("all",{default:!1,describe:"Run every test in the suite (expensive)",type:"boolean"}).option("headed",{default:!1,describe:"Run with a visible browser window",type:"boolean"}).option("keep-alive",{default:!1,describe:"Leave an auto-started daemon running after the batch",type:"boolean"})}function Tc(e){return e.command("status","Print the current scope",r=>r.option("format",{choices:["json","text"],default:"text",describe:"Output format"}),r=>bo({format:r.format})).command("add <test-ids..>","Bind one or more existing tests (stubs or implemented) to scope as agent intent",r=>{let t=[];return r.positional("test-ids",{array:!0,default:t,demandOption:!0,describe:"Slugs of existing workflows",type:"string"})},r=>So({testIds:r["test-ids"]})).command("link <id> <test-id>","Link an existing scope item to a test",r=>r.positional("id",{demandOption:!0,describe:"Scope item id",type:"string"}).positional("test-id",{demandOption:!0,describe:"Slug of the workflow to link",type:"string"}),r=>xo({id:r.id,testId:r["test-id"]})).command("remove <ids..>","Remove one or more scope items by id",r=>{let t=[];return r.positional("ids",{array:!0,default:t,demandOption:!0,describe:"Scope item ids",type:"string"})},r=>Ro({ids:r.ids})).demandCommand(1)}function Dc(e){return e.command("pause","Disable all Ripplo pre-edit gates and stop enforcement until resumed",()=>{},()=>nn()).command("resume","Re-enable Ripplo hooks paused via `ripplo hooks pause`",()=>{},()=>on()).demandCommand(1)}function Oc(e){return e.command("list","List projects you have access to (JSON)",()=>{},()=>Hn()).demandCommand(1)}function _c(e){return e.command("login","Authenticate via device flow",()=>{},()=>Mt()).command("status","Show authentication status",()=>{},()=>Ht()).command("logout","Remove the saved token",()=>{},()=>{Wt()}).demandCommand(1)}function Fc(){let e=process.cwd(),r=vr(e);r!=null&&r!==e&&(process.chdir(r),process.stderr.write(`ripplo: resolved .ripplo/ at ${r}
529
530
  `))}export{Cc as main};