ripplo 0.4.12 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +214 -181
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- var cn=Object.defineProperty;var ps=Object.getOwnPropertyDescriptor;var ms=Object.getOwnPropertyNames;var fs=Object.prototype.hasOwnProperty;var gs=(e,t)=>{for(var r in t)cn(e,r,{get:t[r],enumerable:!0})},ln=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ms(t))!fs.call(e,o)&&o!==r&&cn(e,o,{get:()=>t[o],enumerable:!(n=ps(t,o))||n.enumerable});return e},Kt=(e,t,r)=>(ln(e,t,"default"),r&&ln(r,t,"default"));import{execFileSync as ys}from"child_process";import hs from"fs";import ut from"path";function Jt(e){let t=bs(e);return t??un(e)}function un(e){let t=ut.dirname(e);return ws(ut.join(e,".ripplo"))?e:t===e?null:un(t)}function ws(e){try{return hs.statSync(e).isDirectory()}catch{return!1}}function bs(e){let t=vs(["rev-parse","--show-toplevel"],e);if(t==null)return null;let r=t.trim();return r.length===0?null:ut.isAbsolute(r)?r:ut.resolve(e,r)}function vs(e,t){try{return ys("git",e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"]})}catch{return null}}import mn from"fs";import mt from"path";import{config as Rs}from"dotenv";import{z as pt}from"zod";import ze from"fs";import Yt from"path";import{z as dt}from"zod";var dn=Yt.join(".ripplo","project.json"),Qt=[".env",".env.local"],ks=dt.object({envFiles:dt.array(dt.string().min(1)).optional(),projectId:dt.string().min(1)});function Xt(e){return Yt.join(e,dn)}function Zt(e){let t=Xt(e);if(!ze.existsSync(t))throw new Error(`ripplo: missing ${dn}. Run \`ripplo init\` to create it.`);let r=JSON.parse(ze.readFileSync(t,"utf8")),n=ks.parse(r);return{envFiles:n.envFiles??Qt,projectId:n.projectId}}function er(e){let t=Xt(e);if(ze.existsSync(t))try{return Zt(e)}catch{return}}function pn({cwd:e,envFiles:t,projectId:r}){let n=Xt(e);ze.mkdirSync(Yt.dirname(n),{recursive:!0});let o={projectId:r};t!=null&&!Ss(t)&&(o.envFiles=[...t]),ze.writeFileSync(n,JSON.stringify(o,null,2)+`
3
- `)}function Ss(e){return e.length!==Qt.length?!1:e.every((t,r)=>t===Qt[r])}function fn(e){let r=er(e)?.envFiles??[".env",".env.local"],n=mt.join(e,".ripplo");r.forEach(o=>{let i=mt.resolve(n,o);mn.existsSync(i)&&Rs({override:!0,path:i,quiet:!0})}),tr=void 0}var Ps=pt.object({RIPPLO_APP_URL:pt.url(),RIPPLO_ENGINE_URL:pt.url(),RIPPLO_WEBHOOK_SECRET:pt.string().min(1)}),tr;function Ie(){return tr??=xs(),tr}function xs(){let e=Ps.safeParse(process.env);if(!e.success){let r=e.error.issues.map(s=>` ${s.path.join(".")}: ${s.message}`).join(`
2
+ var on=Object.defineProperty;var cs=Object.getOwnPropertyDescriptor;var ds=Object.getOwnPropertyNames;var us=Object.prototype.hasOwnProperty;var ps=(e,t)=>{for(var r in t)on(e,r,{get:t[r],enumerable:!0})},nn=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ds(t))!us.call(e,o)&&o!==r&&on(e,o,{get:()=>t[o],enumerable:!(n=cs(t,o))||n.enumerable});return e},Kt=(e,t,r)=>(nn(e,t,"default"),r&&nn(r,t,"default"));import{execFileSync as ms}from"child_process";import fs from"fs";import dt from"path";function Jt(e){let t=ys(e);return t??sn(e)}function sn(e){let t=dt.dirname(e);return gs(dt.join(e,".ripplo"))?e:t===e?null:sn(t)}function gs(e){try{return fs.statSync(e).isDirectory()}catch{return!1}}function ys(e){let t=hs(["rev-parse","--show-toplevel"],e);if(t==null)return null;let r=t.trim();return r.length===0?null:dt.isAbsolute(r)?r:dt.resolve(e,r)}function hs(e,t){try{return ms("git",e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"]})}catch{return null}}import cn from"fs";import mt from"path";import{config as vs}from"dotenv";import{z as pt}from"zod";import ze from"fs";import Yt from"path";import{z as ut}from"zod";var an=Yt.join(".ripplo","project.json"),Qt=[".env",".env.local"],ws=ut.object({envFiles:ut.array(ut.string().min(1)).optional(),projectId:ut.string().min(1)});function Xt(e){return Yt.join(e,an)}function Zt(e){let t=Xt(e);if(!ze.existsSync(t))throw new Error(`ripplo: missing ${an}. Run \`ripplo init\` to create it.`);let r=JSON.parse(ze.readFileSync(t,"utf8")),n=ws.parse(r);return{envFiles:n.envFiles??Qt,projectId:n.projectId}}function er(e){let t=Xt(e);if(ze.existsSync(t))try{return Zt(e)}catch{return}}function ln({cwd:e,envFiles:t,projectId:r}){let n=Xt(e);ze.mkdirSync(Yt.dirname(n),{recursive:!0});let o={projectId:r};t!=null&&!bs(t)&&(o.envFiles=[...t]),ze.writeFileSync(n,JSON.stringify(o,null,2)+`
3
+ `)}function bs(e){return e.length!==Qt.length?!1:e.every((t,r)=>t===Qt[r])}function dn(e){let r=er(e)?.envFiles??[".env",".env.local"],n=mt.join(e,".ripplo");r.forEach(o=>{let i=mt.resolve(n,o);cn.existsSync(i)&&vs({override:!0,path:i,quiet:!0})}),tr=void 0}var ks=pt.object({RIPPLO_APP_URL:pt.url(),RIPPLO_ENGINE_URL:pt.url(),RIPPLO_WEBHOOK_SECRET:pt.string().min(1)}),tr;function Ee(){return tr??=Ss(),tr}function Ss(){let e=ks.safeParse(process.env);if(!e.success){let r=e.error.issues.map(s=>` ${s.path.join(".")}: ${s.message}`).join(`
4
4
  `),n=rr(process.cwd()),o=n.map(s=>` ${s}`).join(`
5
5
  `),i=n.length>0?`
6
6
 
@@ -9,23 +9,23 @@ ${o}
9
9
  If you're in a git worktree, the env file may not have been copied from the main checkout \u2014 recreate it (or symlink to a shared file outside the working tree).`:"";throw new Error(`ripplo: env config invalid:
10
10
  ${r}
11
11
 
12
- Add missing values to the env file(s) declared in .ripplo/project.json.${i}`)}let t=e.data;return{appUrl:t.RIPPLO_APP_URL,engineUrl:t.RIPPLO_ENGINE_URL,webhookSecret:t.RIPPLO_WEBHOOK_SECRET}}function rr(e){let r=er(e)?.envFiles??[],n=mt.join(e,".ripplo");return r.map(o=>mt.resolve(n,o)).filter(o=>!mn.existsSync(o))}import Xg from"yargs";import{hideBin as Zg}from"yargs/helpers";import{graphql as Vc}from"gql.tada";async function ft(e,t,r){let n;try{return n=await e.context().newCDPSession(e),await r(n)}catch{return t}finally{await n?.detach().catch(()=>{})}}async function z({awaitPromise:e,expression:t,page:r}){return ft(r,void 0,async n=>{let o=await n.send("Runtime.evaluate",{awaitPromise:e,expression:t,returnByValue:!0});if(o.exceptionDetails==null)return o.result.value})}var Cs=Object.prototype.toString,je=Array.isArray||function(t){return Cs.call(t)==="[object Array]"};function or(e){return typeof e=="function"}function Es(e){return je(e)?"array":typeof e}function nr(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function gn(e,t){return e!=null&&typeof e=="object"&&t in e}function Is(e,t){return e!=null&&typeof e!="object"&&e.hasOwnProperty&&e.hasOwnProperty(t)}var As=RegExp.prototype.test;function js(e,t){return As.call(e,t)}var Ts=/\S/;function $s(e){return!js(Ts,e)}var Ns={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};function Os(e){return String(e).replace(/[&<>"'`=\/]/g,function(r){return Ns[r]})}var Ds=/\s*/,Ls=/\s+/,yn=/\s*=/,_s=/\s*\}/,Us=/#|\^|\/|>|\{|&|=|!/;function Vs(e,t){if(!e)return[];var r=!1,n=[],o=[],i=[],s=!1,a=!1,l="",c=0;function p(){if(s&&!a)for(;i.length;)delete o[i.pop()];else i=[];s=!1,a=!1}var m,k,L;function j(re){if(typeof re=="string"&&(re=re.split(Ls,2)),!je(re)||re.length!==2)throw new Error("Invalid tags: "+re);m=new RegExp(nr(re[0])+"\\s*"),k=new RegExp("\\s*"+nr(re[1])),L=new RegExp("\\s*"+nr("}"+re[1]))}j(t||X.tags);for(var y=new Ke(e),N,T,F,te,Ee,oe;!y.eos();){if(N=y.pos,F=y.scanUntil(m),F)for(var qe=0,W=F.length;qe<W;++qe)te=F.charAt(qe),$s(te)?(i.push(o.length),l+=te):(a=!0,r=!0,l+=" "),o.push(["text",te,N,N+1]),N+=1,te===`
13
- `&&(p(),l="",c=0,r=!1);if(!y.scan(m))break;if(s=!0,T=y.scan(Us)||"name",y.scan(Ds),T==="="?(F=y.scanUntil(yn),y.scan(yn),y.scanUntil(k)):T==="{"?(F=y.scanUntil(L),y.scan(_s),y.scanUntil(k),T="&"):F=y.scanUntil(k),!y.scan(k))throw new Error("Unclosed tag at "+y.pos);if(T==">"?Ee=[T,F,N,y.pos,l,c,r]:Ee=[T,F,N,y.pos],c++,o.push(Ee),T==="#"||T==="^")n.push(Ee);else if(T==="/"){if(oe=n.pop(),!oe)throw new Error('Unopened section "'+F+'" at '+N);if(oe[1]!==F)throw new Error('Unclosed section "'+oe[1]+'" at '+N)}else T==="name"||T==="{"||T==="&"?a=!0:T==="="&&j(F)}if(p(),oe=n.pop(),oe)throw new Error('Unclosed section "'+oe[1]+'" at '+y.pos);return Hs(Fs(o))}function Fs(e){for(var t=[],r,n,o=0,i=e.length;o<i;++o)r=e[o],r&&(r[0]==="text"&&n&&n[0]==="text"?(n[1]+=r[1],n[3]=r[3]):(t.push(r),n=r));return t}function Hs(e){for(var t=[],r=t,n=[],o,i,s=0,a=e.length;s<a;++s)switch(o=e[s],o[0]){case"#":case"^":r.push(o),n.push(o),r=o[4]=[];break;case"/":i=n.pop(),i[5]=o[2],r=n.length>0?n[n.length-1][4]:t;break;default:r.push(o)}return t}function Ke(e){this.string=e,this.tail=e,this.pos=0}Ke.prototype.eos=function(){return this.tail===""};Ke.prototype.scan=function(t){var r=this.tail.match(t);if(!r||r.index!==0)return"";var n=r[0];return this.tail=this.tail.substring(n.length),this.pos+=n.length,n};Ke.prototype.scanUntil=function(t){var r=this.tail.search(t),n;switch(r){case-1:n=this.tail,this.tail="";break;case 0:n="";break;default:n=this.tail.substring(0,r),this.tail=this.tail.substring(r)}return this.pos+=n.length,n};function Ae(e,t){this.view=e,this.cache={".":this.view},this.parent=t}Ae.prototype.push=function(t){return new Ae(t,this)};Ae.prototype.lookup=function(t){var r=this.cache,n;if(r.hasOwnProperty(t))n=r[t];else{for(var o=this,i,s,a,l=!1;o;){if(t.indexOf(".")>0)for(i=o.view,s=t.split("."),a=0;i!=null&&a<s.length;)a===s.length-1&&(l=gn(i,s[a])||Is(i,s[a])),i=i[s[a++]];else i=o.view[t],l=gn(o.view,t);if(l){n=i;break}o=o.parent}r[t]=n}return or(n)&&(n=n.call(this.view)),n};function B(){this.templateCache={_cache:{},set:function(t,r){this._cache[t]=r},get:function(t){return this._cache[t]},clear:function(){this._cache={}}}}B.prototype.clearCache=function(){typeof this.templateCache<"u"&&this.templateCache.clear()};B.prototype.parse=function(t,r){var n=this.templateCache,o=t+":"+(r||X.tags).join(":"),i=typeof n<"u",s=i?n.get(o):void 0;return s==null&&(s=Vs(t,r),i&&n.set(o,s)),s};B.prototype.render=function(t,r,n,o){var i=this.getConfigTags(o),s=this.parse(t,i),a=r instanceof Ae?r:new Ae(r,void 0);return this.renderTokens(s,a,n,t,o)};B.prototype.renderTokens=function(t,r,n,o,i){for(var s="",a,l,c,p=0,m=t.length;p<m;++p)c=void 0,a=t[p],l=a[0],l==="#"?c=this.renderSection(a,r,n,o,i):l==="^"?c=this.renderInverted(a,r,n,o,i):l===">"?c=this.renderPartial(a,r,n,i):l==="&"?c=this.unescapedValue(a,r):l==="name"?c=this.escapedValue(a,r,i):l==="text"&&(c=this.rawValue(a)),c!==void 0&&(s+=c);return s};B.prototype.renderSection=function(t,r,n,o,i){var s=this,a="",l=r.lookup(t[1]);function c(k){return s.render(k,r,n,i)}if(l){if(je(l))for(var p=0,m=l.length;p<m;++p)a+=this.renderTokens(t[4],r.push(l[p]),n,o,i);else if(typeof l=="object"||typeof l=="string"||typeof l=="number")a+=this.renderTokens(t[4],r.push(l),n,o,i);else if(or(l)){if(typeof o!="string")throw new Error("Cannot use higher-order sections without the original template");l=l.call(r.view,o.slice(t[3],t[5]),c),l!=null&&(a+=l)}else a+=this.renderTokens(t[4],r,n,o,i);return a}};B.prototype.renderInverted=function(t,r,n,o,i){var s=r.lookup(t[1]);if(!s||je(s)&&s.length===0)return this.renderTokens(t[4],r,n,o,i)};B.prototype.indentPartial=function(t,r,n){for(var o=r.replace(/[^ \t]/g,""),i=t.split(`
12
+ Add missing values to the env file(s) declared in .ripplo/project.json.${i}`)}let t=e.data;return{appUrl:t.RIPPLO_APP_URL,engineUrl:t.RIPPLO_ENGINE_URL,webhookSecret:t.RIPPLO_WEBHOOK_SECRET}}function rr(e){let r=er(e)?.envFiles??[],n=mt.join(e,".ripplo");return r.map(o=>mt.resolve(n,o)).filter(o=>!cn.existsSync(o))}import Yg from"yargs";import{hideBin as Xg}from"yargs/helpers";import{graphql as _c}from"gql.tada";async function ft(e,t,r){let n;try{return n=await e.context().newCDPSession(e),await r(n)}catch{return t}finally{await n?.detach().catch(()=>{})}}async function z({awaitPromise:e,expression:t,page:r}){return ft(r,void 0,async n=>{let o=await n.send("Runtime.evaluate",{awaitPromise:e,expression:t,returnByValue:!0});if(o.exceptionDetails==null)return o.result.value})}var Rs=Object.prototype.toString,Ie=Array.isArray||function(t){return Rs.call(t)==="[object Array]"};function or(e){return typeof e=="function"}function Ps(e){return Ie(e)?"array":typeof e}function nr(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function un(e,t){return e!=null&&typeof e=="object"&&t in e}function xs(e,t){return e!=null&&typeof e!="object"&&e.hasOwnProperty&&e.hasOwnProperty(t)}var Cs=RegExp.prototype.test;function Es(e,t){return Cs.call(e,t)}var As=/\S/;function Is(e){return!Es(As,e)}var Ts={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};function $s(e){return String(e).replace(/[&<>"'`=\/]/g,function(r){return Ts[r]})}var js=/\s*/,Ns=/\s+/,pn=/\s*=/,Os=/\s*\}/,Ds=/#|\^|\/|>|\{|&|=|!/;function Ls(e,t){if(!e)return[];var r=!1,n=[],o=[],i=[],s=!1,a=!1,l="",c=0;function p(){if(s&&!a)for(;i.length;)delete o[i.pop()];else i=[];s=!1,a=!1}var m,k,j;function N(re){if(typeof re=="string"&&(re=re.split(Ns,2)),!Ie(re)||re.length!==2)throw new Error("Invalid tags: "+re);m=new RegExp(nr(re[0])+"\\s*"),k=new RegExp("\\s*"+nr(re[1])),j=new RegExp("\\s*"+nr("}"+re[1]))}N(t||X.tags);for(var f=new Ke(e),W,$,V,te,Ce,oe;!f.eos();){if(W=f.pos,V=f.scanUntil(m),V)for(var qe=0,B=V.length;qe<B;++qe)te=V.charAt(qe),Is(te)?(i.push(o.length),l+=te):(a=!0,r=!0,l+=" "),o.push(["text",te,W,W+1]),W+=1,te===`
13
+ `&&(p(),l="",c=0,r=!1);if(!f.scan(m))break;if(s=!0,$=f.scan(Ds)||"name",f.scan(js),$==="="?(V=f.scanUntil(pn),f.scan(pn),f.scanUntil(k)):$==="{"?(V=f.scanUntil(j),f.scan(Os),f.scanUntil(k),$="&"):V=f.scanUntil(k),!f.scan(k))throw new Error("Unclosed tag at "+f.pos);if($==">"?Ce=[$,V,W,f.pos,l,c,r]:Ce=[$,V,W,f.pos],c++,o.push(Ce),$==="#"||$==="^")n.push(Ce);else if($==="/"){if(oe=n.pop(),!oe)throw new Error('Unopened section "'+V+'" at '+W);if(oe[1]!==V)throw new Error('Unclosed section "'+oe[1]+'" at '+W)}else $==="name"||$==="{"||$==="&"?a=!0:$==="="&&N(V)}if(p(),oe=n.pop(),oe)throw new Error('Unclosed section "'+oe[1]+'" at '+f.pos);return Us(_s(o))}function _s(e){for(var t=[],r,n,o=0,i=e.length;o<i;++o)r=e[o],r&&(r[0]==="text"&&n&&n[0]==="text"?(n[1]+=r[1],n[3]=r[3]):(t.push(r),n=r));return t}function Us(e){for(var t=[],r=t,n=[],o,i,s=0,a=e.length;s<a;++s)switch(o=e[s],o[0]){case"#":case"^":r.push(o),n.push(o),r=o[4]=[];break;case"/":i=n.pop(),i[5]=o[2],r=n.length>0?n[n.length-1][4]:t;break;default:r.push(o)}return t}function Ke(e){this.string=e,this.tail=e,this.pos=0}Ke.prototype.eos=function(){return this.tail===""};Ke.prototype.scan=function(t){var r=this.tail.match(t);if(!r||r.index!==0)return"";var n=r[0];return this.tail=this.tail.substring(n.length),this.pos+=n.length,n};Ke.prototype.scanUntil=function(t){var r=this.tail.search(t),n;switch(r){case-1:n=this.tail,this.tail="";break;case 0:n="";break;default:n=this.tail.substring(0,r),this.tail=this.tail.substring(r)}return this.pos+=n.length,n};function Ae(e,t){this.view=e,this.cache={".":this.view},this.parent=t}Ae.prototype.push=function(t){return new Ae(t,this)};Ae.prototype.lookup=function(t){var r=this.cache,n;if(r.hasOwnProperty(t))n=r[t];else{for(var o=this,i,s,a,l=!1;o;){if(t.indexOf(".")>0)for(i=o.view,s=t.split("."),a=0;i!=null&&a<s.length;)a===s.length-1&&(l=un(i,s[a])||xs(i,s[a])),i=i[s[a++]];else i=o.view[t],l=un(o.view,t);if(l){n=i;break}o=o.parent}r[t]=n}return or(n)&&(n=n.call(this.view)),n};function H(){this.templateCache={_cache:{},set:function(t,r){this._cache[t]=r},get:function(t){return this._cache[t]},clear:function(){this._cache={}}}}H.prototype.clearCache=function(){typeof this.templateCache<"u"&&this.templateCache.clear()};H.prototype.parse=function(t,r){var n=this.templateCache,o=t+":"+(r||X.tags).join(":"),i=typeof n<"u",s=i?n.get(o):void 0;return s==null&&(s=Ls(t,r),i&&n.set(o,s)),s};H.prototype.render=function(t,r,n,o){var i=this.getConfigTags(o),s=this.parse(t,i),a=r instanceof Ae?r:new Ae(r,void 0);return this.renderTokens(s,a,n,t,o)};H.prototype.renderTokens=function(t,r,n,o,i){for(var s="",a,l,c,p=0,m=t.length;p<m;++p)c=void 0,a=t[p],l=a[0],l==="#"?c=this.renderSection(a,r,n,o,i):l==="^"?c=this.renderInverted(a,r,n,o,i):l===">"?c=this.renderPartial(a,r,n,i):l==="&"?c=this.unescapedValue(a,r):l==="name"?c=this.escapedValue(a,r,i):l==="text"&&(c=this.rawValue(a)),c!==void 0&&(s+=c);return s};H.prototype.renderSection=function(t,r,n,o,i){var s=this,a="",l=r.lookup(t[1]);function c(k){return s.render(k,r,n,i)}if(l){if(Ie(l))for(var p=0,m=l.length;p<m;++p)a+=this.renderTokens(t[4],r.push(l[p]),n,o,i);else if(typeof l=="object"||typeof l=="string"||typeof l=="number")a+=this.renderTokens(t[4],r.push(l),n,o,i);else if(or(l)){if(typeof o!="string")throw new Error("Cannot use higher-order sections without the original template");l=l.call(r.view,o.slice(t[3],t[5]),c),l!=null&&(a+=l)}else a+=this.renderTokens(t[4],r,n,o,i);return a}};H.prototype.renderInverted=function(t,r,n,o,i){var s=r.lookup(t[1]);if(!s||Ie(s)&&s.length===0)return this.renderTokens(t[4],r,n,o,i)};H.prototype.indentPartial=function(t,r,n){for(var o=r.replace(/[^ \t]/g,""),i=t.split(`
14
14
  `),s=0;s<i.length;s++)i[s].length&&(s>0||!n)&&(i[s]=o+i[s]);return i.join(`
15
- `)};B.prototype.renderPartial=function(t,r,n,o){if(n){var i=this.getConfigTags(o),s=or(n)?n(t[1]):n[t[1]];if(s!=null){var a=t[6],l=t[5],c=t[4],p=s;l==0&&c&&(p=this.indentPartial(s,c,a));var m=this.parse(p,i);return this.renderTokens(m,r,n,p,o)}}};B.prototype.unescapedValue=function(t,r){var n=r.lookup(t[1]);if(n!=null)return n};B.prototype.escapedValue=function(t,r,n){var o=this.getConfigEscape(n)||X.escape,i=r.lookup(t[1]);if(i!=null)return typeof i=="number"&&o===X.escape?String(i):o(i)};B.prototype.rawValue=function(t){return t[1]};B.prototype.getConfigTags=function(t){return je(t)?t:t&&typeof t=="object"?t.tags:void 0};B.prototype.getConfigEscape=function(t){if(t&&typeof t=="object"&&!je(t))return t.escape};var X={name:"mustache.js",version:"4.2.0",tags:["{{","}}"],clearCache:void 0,escape:void 0,parse:void 0,render:void 0,Scanner:void 0,Context:void 0,Writer:void 0,set templateCache(e){Ge.templateCache=e},get templateCache(){return Ge.templateCache}},Ge=new B;X.clearCache=function(){return Ge.clearCache()};X.parse=function(t,r){return Ge.parse(t,r)};X.render=function(t,r,n,o){if(typeof t!="string")throw new TypeError('Invalid template! Template should be a "string" but "'+Es(t)+'" was given as the first argument for mustache#render(template, view, partials)');return Ge.render(t,r,n,o)};X.escape=Os;X.Scanner=Ke;X.Context=Ae;X.Writer=B;var hn=X;function ir({defs:e}){return e==null?{}:Object.fromEntries(Object.entries(e).map(([t,r])=>[t,Ws({def:r,name:t})]))}var Bs={boolean:!1,number:0,string:""};function Ws({def:e,name:t}){if(e.type==="env"){let r=process.env[e.key];if(r==null)throw new Error(`Environment variable "${e.key}" not set for variable "${t}"`);return r}return e.default??Bs[e.type]??""}function gt({ref:e,variables:t}){if(e.type==="static")return e.value;let r=t[e.name];if(r==null||typeof r=="object")throw new Error(`Variable "${e.name}" is not defined`);return r}function M({ref:e,variables:t}){let r=String(gt({ref:e,variables:t}));return ht({raw:r,variables:t})}function yt({ref:e,variables:t}){let r=gt({ref:e,variables:t});return typeof r=="string"?ht({raw:r,variables:t}):r}function ht({raw:e,variables:t}){return hn.render(e,t,{},{escape:r=>r})}function sr({ref:e,variables:t}){let r=gt({ref:e,variables:t});if(typeof r=="number")return r;let n=Number(r);if(Number.isNaN(n))throw new TypeError(`Cannot convert "${String(r)}" to number`);return n}function Te({name:e,store:t,value:r}){return{...t,[e]:r}}function O({locator:e,page:t,variables:r}){switch(e.by){case"testId":return t.getByTestId(wn(e.value,r));case"role":return t.getByRole(e.role,{exact:!0,name:e.name==null?void 0:wn(e.name,r)})}}function wn(e,t){return t==null?e:ht({raw:e,variables:t})}async function bn({node:e,page:t,timeout:r}){await O({locator:e.source,page:t,variables:void 0}).dragTo(O({locator:e.target,page:t,variables:void 0}),{timeout:r})}async function vn({node:e,page:t,timeout:r}){await O({locator:e.locator,page:t,variables:void 0}).click({button:"right",timeout:r})}function kn({node:e,page:t,variables:r}){return t.once("dialog",async n=>{e.action==="accept"?await n.accept(e.promptText??void 0):await n.dismiss()}),{variables:r}}async function Sn({node:e,page:t,variables:r}){if(e.action==="write"){if(e.value==null)throw new Error("clipboard write requires a value");let o=M({ref:e.value,variables:r}),i=JSON.stringify(o);return await z({awaitPromise:!0,expression:`navigator.clipboard.writeText(${i})`,page:t}),{variables:r}}if(e.variable==null)throw new Error("clipboard read requires a variable name to store the result");let n=await z({awaitPromise:!0,expression:"navigator.clipboard.readText()",page:t})??"";return{variables:Te({name:e.variable,store:r,value:n})}}async function Rn({node:e,page:t}){let r=t.context();e.state==="granted"?await r.grantPermissions([e.permission]):await r.clearPermissions()}async function ar({node:e,page:t,timeout:r,variables:n}){switch(e.type){case"click":case"check":case"uncheck":case"clear":case"dblclick":case"focus":case"hover":case"scrollIntoView":return qs({node:e,page:t,timeout:r,variables:n});case"rightClick":return await vn({node:e,page:t,timeout:r}),{variables:n};case"goto":{let o=M({ref:e.url,variables:n});return await t.goto(o,{timeout:r,waitUntil:"domcontentloaded"}),await t.waitForLoadState("load",{timeout:r}),await t.waitForFunction("document.body != null && document.body.childElementCount > 0 && (document.body.textContent?.trim().length ?? 0) > 0",void 0,{timeout:r}),await z({awaitPromise:!0,expression:"new Promise((resolve) => { requestAnimationFrame(() => requestAnimationFrame(() => resolve(undefined))); })",page:t}),{variables:n}}case"fill":return Gs({node:e,page:t,timeout:r,variables:n});case"select":return Ks({node:e,page:t,timeout:r,variables:n});case"type":return Js({node:e,page:t,timeout:r,variables:n});case"press":return await zs({node:e,page:t,timeout:r}),{variables:n};case"drag":return await bn({node:e,page:t,timeout:r}),{variables:n};case"upload":return Qs({node:e,page:t,timeout:r,variables:n});case"extractText":return Ys({node:e,page:t,timeout:r,variables:n});case"setVariable":{let o=yt({ref:e.value,variables:n});return{variables:Te({name:e.variable,store:n,value:o})}}case"fail":throw new Error(e.message);case"setViewport":return await t.setViewportSize({height:e.height,width:e.width}),{variables:n};case"handleDialog":return kn({node:e,page:t,variables:n});case"clipboard":return Sn({node:e,page:t,variables:n});case"setPermission":return await Rn({node:e,page:t}),{variables:n}}}var Ms={check:"check",clear:"clear",click:"click",dblclick:"dblclick",focus:"focus",hover:"hover",scrollIntoView:"scrollIntoViewIfNeeded",uncheck:"uncheck"};async function qs({node:e,page:t,timeout:r,variables:n}){return await O({locator:e.locator,page:t,variables:n})[Ms[e.type]]({timeout:r}),{variables:n}}async function zs({node:e,page:t,timeout:r}){e.locator==null?await t.keyboard.press(e.key):await O({locator:e.locator,page:t,variables:void 0}).press(e.key,{timeout:r})}async function Gs({node:e,page:t,timeout:r,variables:n}){let o=M({ref:e.value,variables:n});return await O({locator:e.locator,page:t,variables:n}).fill(o,{timeout:r}),{variables:n}}async function Ks({node:e,page:t,timeout:r,variables:n}){let o=M({ref:e.value,variables:n});return await O({locator:e.locator,page:t,variables:n}).selectOption(o,{timeout:r}),{variables:n}}async function Js({node:e,page:t,timeout:r,variables:n}){let o=M({ref:e.value,variables:n});return await O({locator:e.locator,page:t,variables:n}).pressSequentially(o,{timeout:r}),{variables:n}}async function Qs({node:e,page:t,timeout:r,variables:n}){return await O({locator:e.locator,page:t,variables:n}).setInputFiles(e.files,{timeout:r}),{variables:n}}async function Ys({node:e,page:t,timeout:r,variables:n}){let o=await O({locator:e.locator,page:t,variables:n}).textContent({timeout:r});if(o==null)throw new Error("extractText: element had no text content");return{variables:Te({name:e.variable,store:n,value:o})}}var H={};gs(H,{default:()=>Xs});Kt(H,Uy);import*as Uy from"playwright/test";import{default as Xs}from"playwright/test";async function lr({expected:e,locator:t,operator:r,timeout:n}){await Je({expected:e,operator:r,timeout:n,read:async()=>await t.textContent()??""});let o=await Qe(async()=>await t.textContent()??"");return $e({actual:o,expected:e,label:"Text",operator:r})}async function cr({expected:e,operator:t,page:r,timeout:n}){return await Je({expected:e,operator:t,timeout:n,read:()=>Promise.resolve(r.url())}),$e({actual:r.url(),expected:e,label:"URL",operator:t})}async function Pn({expected:e,operator:t,page:r,timeout:n}){await Je({expected:e,operator:t,timeout:n,read:()=>r.title()});let o=await Qe(()=>r.title());return $e({actual:o,expected:e,label:"Title",operator:t})}async function ur({expected:e,locator:t,operator:r,timeout:n}){await H.expect.poll(async()=>On({actual:await t.count(),expected:e,operator:r}),{timeout:n}).toBe(!0).catch(()=>{});let o=await t.count();return Nn({actual:o,expected:e,label:"Count",operator:r})}async function dr({expected:e,locator:t,operator:r,timeout:n}){await Je({expected:e,operator:r,timeout:n,read:()=>t.inputValue()});let o=await Qe(()=>t.inputValue());return $e({actual:o,expected:e,label:"Value",operator:r})}async function pr({locator:e,timeout:t}){return ge({description:"Element is visible",failureDetail:"Element not visible",run:()=>(0,H.expect)(e).toBeVisible({timeout:t})})}async function mr({locator:e,timeout:t}){return ge({description:"Element is not visible",failureDetail:"Element still visible",run:()=>(0,H.expect)(e).toBeHidden({timeout:t})})}async function xn({attribute:e,expected:t,locator:r,operator:n,timeout:o}){await Je({expected:t,operator:n,timeout:o,read:async()=>await r.getAttribute(e)??""});let i=await Qe(async()=>await r.getAttribute(e)??"");return $e({actual:i,expected:t,label:`Attribute "${e}"`,operator:n})}async function Cn({locator:e,timeout:t}){return ge({description:"Element is enabled",failureDetail:"Element is disabled or not found",run:()=>(0,H.expect)(e).toBeEnabled({timeout:t})})}async function En({locator:e,timeout:t}){return ge({description:"Element is disabled",failureDetail:"Element is enabled or not found",run:()=>(0,H.expect)(e).toBeDisabled({timeout:t})})}async function In({locator:e,timeout:t}){return ge({description:"Element is checked",failureDetail:"Element is not checked",run:()=>(0,H.expect)(e).toBeChecked({timeout:t})})}async function An({locator:e,timeout:t}){return ge({description:"Element is not checked",failureDetail:"Element is checked",run:()=>(0,H.expect)(e).not.toBeChecked({timeout:t})})}async function jn({locator:e,timeout:t}){return ge({description:"Element is focused",failureDetail:"Element is not focused",run:()=>(0,H.expect)(e).toBeFocused({timeout:t})})}async function Tn({locator:e,timeout:t}){return ge({description:"Element is not focused",failureDetail:"Element is focused",run:()=>(0,H.expect)(e).not.toBeFocused({timeout:t})})}async function ge({description:e,failureDetail:t,run:r}){try{return await r(),{description:e,detail:void 0,status:"passed"}}catch{return{description:e,detail:t,status:"failed"}}}async function Je({expected:e,operator:t,read:r,timeout:n}){await H.expect.poll(async()=>{let o=await Qe(r);return $n({actual:o,expected:e,operator:t})},{timeout:n}).toBe(!0).catch(()=>{})}async function Qe(e){return e().catch(()=>"")}function $e(e){let t=`${e.label} ${e.operator} "${e.expected}"`,r=$n(e),n=r?void 0:`Got: "${e.actual}"`;return{description:t,detail:n,status:r?"passed":"failed"}}function $n({actual:e,expected:t,operator:r}){return r==="equals"?e===t:r==="notEquals"?e!==t:r==="contains"?e.includes(t):r==="startsWith"?e.startsWith(t):r==="endsWith"?e.endsWith(t):new RegExp(t).test(e)}function Nn(e){let t=`${e.label} ${e.operator} ${String(e.expected)}`,r=On(e),n=r?void 0:`Got: ${String(e.actual)}`;return{description:t,detail:n,status:r?"passed":"failed"}}function On({actual:e,expected:t,operator:r}){return r==="equals"?e===t:r==="notEquals"?e!==t:r==="greaterThan"?e>t:r==="greaterThanOrEqual"?e>=t:r==="lessThan"?e<t:e<=t}import{execFile as ea}from"child_process";import{createRequire as ta}from"module";import Dn from"fs";import Ln from"path";import{chromium as _n}from"playwright";import Wy from"fs";import qy from"path";import Zs from"pino";var f=Zs({transport:{options:{ignore:"pid,hostname"},target:"pino-pretty"}});async function Ye({headed:e}){process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY="1";try{return await _n.launch({headless:!e})}catch(t){throw ra(t)?new Error(`Playwright browsers are not installed. Run:
15
+ `)};H.prototype.renderPartial=function(t,r,n,o){if(n){var i=this.getConfigTags(o),s=or(n)?n(t[1]):n[t[1]];if(s!=null){var a=t[6],l=t[5],c=t[4],p=s;l==0&&c&&(p=this.indentPartial(s,c,a));var m=this.parse(p,i);return this.renderTokens(m,r,n,p,o)}}};H.prototype.unescapedValue=function(t,r){var n=r.lookup(t[1]);if(n!=null)return n};H.prototype.escapedValue=function(t,r,n){var o=this.getConfigEscape(n)||X.escape,i=r.lookup(t[1]);if(i!=null)return typeof i=="number"&&o===X.escape?String(i):o(i)};H.prototype.rawValue=function(t){return t[1]};H.prototype.getConfigTags=function(t){return Ie(t)?t:t&&typeof t=="object"?t.tags:void 0};H.prototype.getConfigEscape=function(t){if(t&&typeof t=="object"&&!Ie(t))return t.escape};var X={name:"mustache.js",version:"4.2.0",tags:["{{","}}"],clearCache:void 0,escape:void 0,parse:void 0,render:void 0,Scanner:void 0,Context:void 0,Writer:void 0,set templateCache(e){Ge.templateCache=e},get templateCache(){return Ge.templateCache}},Ge=new H;X.clearCache=function(){return Ge.clearCache()};X.parse=function(t,r){return Ge.parse(t,r)};X.render=function(t,r,n,o){if(typeof t!="string")throw new TypeError('Invalid template! Template should be a "string" but "'+Ps(t)+'" was given as the first argument for mustache#render(template, view, partials)');return Ge.render(t,r,n,o)};X.escape=$s;X.Scanner=Ke;X.Context=Ae;X.Writer=H;var mn=X;function ir({defs:e}){return e==null?{}:Object.fromEntries(Object.entries(e).map(([t,r])=>[t,Fs({def:r,name:t})]))}var Vs={boolean:!1,number:0,string:""};function Fs({def:e,name:t}){if(e.type==="env"){let r=process.env[e.key];if(r==null)throw new Error(`Environment variable "${e.key}" not set for variable "${t}"`);return r}return e.default??Vs[e.type]??""}function gt({ref:e,variables:t}){if(e.type==="static")return e.value;let r=t[e.name];if(r==null||typeof r=="object")throw new Error(`Variable "${e.name}" is not defined`);return r}function M({ref:e,variables:t}){let r=String(gt({ref:e,variables:t}));return ht({raw:r,variables:t})}function yt({ref:e,variables:t}){let r=gt({ref:e,variables:t});return typeof r=="string"?ht({raw:r,variables:t}):r}function ht({raw:e,variables:t}){return mn.render(e,t,{},{escape:r=>r})}function sr({ref:e,variables:t}){let r=gt({ref:e,variables:t});if(typeof r=="number")return r;let n=Number(r);if(Number.isNaN(n))throw new TypeError(`Cannot convert "${String(r)}" to number`);return n}function Te({name:e,store:t,value:r}){return{...t,[e]:r}}function O({locator:e,page:t,variables:r}){switch(e.by){case"testId":return t.getByTestId(fn(e.value,r));case"role":return t.getByRole(e.role,{exact:!0,name:e.name==null?void 0:fn(e.name,r)})}}function fn(e,t){return t==null?e:ht({raw:e,variables:t})}async function gn({node:e,page:t,timeout:r}){await O({locator:e.source,page:t,variables:void 0}).dragTo(O({locator:e.target,page:t,variables:void 0}),{timeout:r})}async function yn({node:e,page:t,timeout:r}){await O({locator:e.locator,page:t,variables:void 0}).click({button:"right",timeout:r})}function hn({node:e,page:t,variables:r}){return t.once("dialog",async n=>{e.action==="accept"?await n.accept(e.promptText??void 0):await n.dismiss()}),{variables:r}}async function wn({node:e,page:t,variables:r}){if(e.action==="write"){if(e.value==null)throw new Error("clipboard write requires a value");let o=M({ref:e.value,variables:r}),i=JSON.stringify(o);return await z({awaitPromise:!0,expression:`navigator.clipboard.writeText(${i})`,page:t}),{variables:r}}if(e.variable==null)throw new Error("clipboard read requires a variable name to store the result");let n=await z({awaitPromise:!0,expression:"navigator.clipboard.readText()",page:t})??"";return{variables:Te({name:e.variable,store:r,value:n})}}async function bn({node:e,page:t}){let r=t.context();e.state==="granted"?await r.grantPermissions([e.permission]):await r.clearPermissions()}async function ar({node:e,page:t,timeout:r,variables:n}){switch(e.type){case"click":case"check":case"uncheck":case"clear":case"dblclick":case"focus":case"hover":case"scrollIntoView":return Ws({node:e,page:t,timeout:r,variables:n});case"rightClick":return await yn({node:e,page:t,timeout:r}),{variables:n};case"goto":{let o=M({ref:e.url,variables:n});return await t.goto(o,{timeout:r,waitUntil:"domcontentloaded"}),await t.waitForLoadState("load",{timeout:r}),await t.waitForFunction("document.body != null && document.body.childElementCount > 0 && (document.body.textContent?.trim().length ?? 0) > 0",void 0,{timeout:r}),await z({awaitPromise:!0,expression:"new Promise((resolve) => { requestAnimationFrame(() => requestAnimationFrame(() => resolve(undefined))); })",page:t}),{variables:n}}case"fill":return Ms({node:e,page:t,timeout:r,variables:n});case"select":return qs({node:e,page:t,timeout:r,variables:n});case"type":return zs({node:e,page:t,timeout:r,variables:n});case"press":return await Bs({node:e,page:t,timeout:r}),{variables:n};case"drag":return await gn({node:e,page:t,timeout:r}),{variables:n};case"upload":return Gs({node:e,page:t,timeout:r,variables:n});case"extractText":return Ks({node:e,page:t,timeout:r,variables:n});case"setVariable":{let o=yt({ref:e.value,variables:n});return{variables:Te({name:e.variable,store:n,value:o})}}case"fail":throw new Error(e.message);case"setViewport":return await t.setViewportSize({height:e.height,width:e.width}),{variables:n};case"handleDialog":return hn({node:e,page:t,variables:n});case"clipboard":return wn({node:e,page:t,variables:n});case"setPermission":return await bn({node:e,page:t}),{variables:n}}}var Hs={check:"check",clear:"clear",click:"click",dblclick:"dblclick",focus:"focus",hover:"hover",scrollIntoView:"scrollIntoViewIfNeeded",uncheck:"uncheck"};async function Ws({node:e,page:t,timeout:r,variables:n}){return await O({locator:e.locator,page:t,variables:n})[Hs[e.type]]({timeout:r}),{variables:n}}async function Bs({node:e,page:t,timeout:r}){e.locator==null?await t.keyboard.press(e.key):await O({locator:e.locator,page:t,variables:void 0}).press(e.key,{timeout:r})}async function Ms({node:e,page:t,timeout:r,variables:n}){let o=M({ref:e.value,variables:n});return await O({locator:e.locator,page:t,variables:n}).fill(o,{timeout:r}),{variables:n}}async function qs({node:e,page:t,timeout:r,variables:n}){let o=M({ref:e.value,variables:n});return await O({locator:e.locator,page:t,variables:n}).selectOption(o,{timeout:r}),{variables:n}}async function zs({node:e,page:t,timeout:r,variables:n}){let o=M({ref:e.value,variables:n});return await O({locator:e.locator,page:t,variables:n}).pressSequentially(o,{timeout:r}),{variables:n}}async function Gs({node:e,page:t,timeout:r,variables:n}){return await O({locator:e.locator,page:t,variables:n}).setInputFiles(e.files,{timeout:r}),{variables:n}}async function Ks({node:e,page:t,timeout:r,variables:n}){let o=await O({locator:e.locator,page:t,variables:n}).textContent({timeout:r});if(o==null)throw new Error("extractText: element had no text content");return{variables:Te({name:e.variable,store:n,value:o})}}var F={};ps(F,{default:()=>Js});Kt(F,_y);import*as _y from"playwright/test";import{default as Js}from"playwright/test";async function lr({expected:e,locator:t,operator:r,timeout:n}){await Je({expected:e,operator:r,timeout:n,read:async()=>await t.textContent()??""});let o=await Qe(async()=>await t.textContent()??"");return $e({actual:o,expected:e,label:"Text",operator:r})}async function cr({expected:e,operator:t,page:r,timeout:n}){return await Je({expected:e,operator:t,timeout:n,read:()=>Promise.resolve(r.url())}),$e({actual:r.url(),expected:e,label:"URL",operator:t})}async function vn({expected:e,operator:t,page:r,timeout:n}){await Je({expected:e,operator:t,timeout:n,read:()=>r.title()});let o=await Qe(()=>r.title());return $e({actual:o,expected:e,label:"Title",operator:t})}async function dr({expected:e,locator:t,operator:r,timeout:n}){await F.expect.poll(async()=>Tn({actual:await t.count(),expected:e,operator:r}),{timeout:n}).toBe(!0).catch(()=>{});let o=await t.count();return In({actual:o,expected:e,label:"Count",operator:r})}async function ur({expected:e,locator:t,operator:r,timeout:n}){await Je({expected:e,operator:r,timeout:n,read:()=>t.inputValue()});let o=await Qe(()=>t.inputValue());return $e({actual:o,expected:e,label:"Value",operator:r})}async function pr({locator:e,timeout:t}){return me({description:"Element is visible",failureDetail:"Element not visible",run:()=>(0,F.expect)(e).toBeVisible({timeout:t})})}async function mr({locator:e,timeout:t}){return me({description:"Element is not visible",failureDetail:"Element still visible",run:()=>(0,F.expect)(e).toBeHidden({timeout:t})})}async function kn({attribute:e,expected:t,locator:r,operator:n,timeout:o}){await Je({expected:t,operator:n,timeout:o,read:async()=>await r.getAttribute(e)??""});let i=await Qe(async()=>await r.getAttribute(e)??"");return $e({actual:i,expected:t,label:`Attribute "${e}"`,operator:n})}async function Sn({locator:e,timeout:t}){return me({description:"Element is enabled",failureDetail:"Element is disabled or not found",run:()=>(0,F.expect)(e).toBeEnabled({timeout:t})})}async function Rn({locator:e,timeout:t}){return me({description:"Element is disabled",failureDetail:"Element is enabled or not found",run:()=>(0,F.expect)(e).toBeDisabled({timeout:t})})}async function Pn({locator:e,timeout:t}){return me({description:"Element is checked",failureDetail:"Element is not checked",run:()=>(0,F.expect)(e).toBeChecked({timeout:t})})}async function xn({locator:e,timeout:t}){return me({description:"Element is not checked",failureDetail:"Element is checked",run:()=>(0,F.expect)(e).not.toBeChecked({timeout:t})})}async function Cn({locator:e,timeout:t}){return me({description:"Element is focused",failureDetail:"Element is not focused",run:()=>(0,F.expect)(e).toBeFocused({timeout:t})})}async function En({locator:e,timeout:t}){return me({description:"Element is not focused",failureDetail:"Element is focused",run:()=>(0,F.expect)(e).not.toBeFocused({timeout:t})})}async function me({description:e,failureDetail:t,run:r}){try{return await r(),{description:e,detail:void 0,status:"passed"}}catch{return{description:e,detail:t,status:"failed"}}}async function Je({expected:e,operator:t,read:r,timeout:n}){await F.expect.poll(async()=>{let o=await Qe(r);return An({actual:o,expected:e,operator:t})},{timeout:n}).toBe(!0).catch(()=>{})}async function Qe(e){return e().catch(()=>"")}function $e(e){let t=`${e.label} ${e.operator} "${e.expected}"`,r=An(e),n=r?void 0:`Got: "${e.actual}"`;return{description:t,detail:n,status:r?"passed":"failed"}}function An({actual:e,expected:t,operator:r}){return r==="equals"?e===t:r==="notEquals"?e!==t:r==="contains"?e.includes(t):r==="startsWith"?e.startsWith(t):r==="endsWith"?e.endsWith(t):new RegExp(t).test(e)}function In(e){let t=`${e.label} ${e.operator} ${String(e.expected)}`,r=Tn(e),n=r?void 0:`Got: ${String(e.actual)}`;return{description:t,detail:n,status:r?"passed":"failed"}}function Tn({actual:e,expected:t,operator:r}){return r==="equals"?e===t:r==="notEquals"?e!==t:r==="greaterThan"?e>t:r==="greaterThanOrEqual"?e>=t:r==="lessThan"?e<t:e<=t}import{execFile as Ys}from"child_process";import{createRequire as Xs}from"module";import $n from"fs";import jn from"path";import{chromium as Nn}from"playwright";import Wy from"fs";import My from"path";import Qs from"pino";var y=Qs({transport:{options:{ignore:"pid,hostname"},target:"pino-pretty"}});async function Ye({headed:e}){process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY="1";try{return await Nn.launch({headless:!e})}catch(t){throw Zs(t)?new Error(`Playwright browsers are not installed. Run:
16
16
 
17
17
  npx playwright install chromium
18
- `):t}}async function fr(){let e=_n.executablePath();if(Dn.existsSync(e))return;f.info("Chromium not found. Installing via Playwright...");let t=ta(import.meta.url),r=Ln.dirname(t.resolve("playwright/package.json")),n=Ln.join(r,"cli.js");if(await new Promise((o,i)=>{ea(process.execPath,[n,"install","chromium"],s=>{if(s!=null){i(new Error(`Playwright install failed: ${s.message}`));return}o()})}),!Dn.existsSync(e))throw new Error(`Playwright browser installation failed. Try running manually:
18
+ `):t}}async function fr(){let e=Nn.executablePath();if($n.existsSync(e))return;y.info("Chromium not found. Installing via Playwright...");let t=Xs(import.meta.url),r=jn.dirname(t.resolve("playwright/package.json")),n=jn.join(r,"cli.js");if(await new Promise((o,i)=>{Ys(process.execPath,[n,"install","chromium"],s=>{if(s!=null){i(new Error(`Playwright install failed: ${s.message}`));return}o()})}),!$n.existsSync(e))throw new Error(`Playwright browser installation failed. Try running manually:
19
19
 
20
20
  npx playwright install chromium
21
- `);f.info("\u2713 Chromium installed")}function ra(e){return e instanceof Error?e.message.includes("Executable doesn't exist"):!1}import{readdir as la,rm as ca,stat as ua}from"fs/promises";import ih from"os";import Fn from"path";import{graphql as da}from"gql.tada";import{print as na}from"graphql";var oa=15e3;async function g(e){let t=na(e.document),r=JSON.stringify({query:t,variables:e.variables}),n=`${e.config.ripploServerUrl}/graphql`,o;try{o=await fetch(n,{body:r,headers:{Authorization:`Bearer ${e.config.token}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(oa)})}catch(s){let a=s instanceof Error?s.message:String(s);throw new Error(`Failed to connect to Ripplo server at ${e.config.ripploServerUrl}. Make sure \`npx ripplo\` is running and your internet connection is active. Details: ${a}`)}let i=await ia({res:o,url:n});if(!sa(i))throw new Error("Invalid GraphQL response");if(aa(i))throw new Error(i.errors.map(s=>s.message).join(", "));if(i.data==null)throw new Error("No data returned from server");return i.data}async function ia({res:e,url:t}){let r=await e.text();if(r.length===0)throw new Error(`Empty response from Ripplo server at ${t} (status ${String(e.status)}). The server may have restarted or the request was rejected before a body was sent.`);try{return JSON.parse(r)}catch{throw new Error(`Non-JSON response from Ripplo server at ${t} (status ${String(e.status)}): ${r.slice(0,200)}`)}}function sa(e){return typeof e=="object"&&e!=null&&("data"in e||"errors"in e)}function aa(e){return Array.isArray(e.errors)&&e.errors.length>0}var Un=Fn.join(process.cwd(),".ripplo","debug"),Vn=360*60*1e3,uh=da(`
21
+ `);y.info("\u2713 Chromium installed")}function Zs(e){return e instanceof Error?e.message.includes("Executable doesn't exist"):!1}import{readdir as ia,rm as sa,stat as aa}from"fs/promises";import oh from"os";import Ln from"path";import{graphql as la}from"gql.tada";import{print as ea}from"graphql";var ta=15e3;async function g(e){let t=ea(e.document),r=JSON.stringify({query:t,variables:e.variables}),n=`${e.config.ripploServerUrl}/graphql`,o;try{o=await fetch(n,{body:r,headers:{Authorization:`Bearer ${e.config.token}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(ta)})}catch(s){let a=s instanceof Error?s.message:String(s);throw new Error(`Failed to connect to Ripplo server at ${e.config.ripploServerUrl}. Make sure \`npx ripplo\` is running and your internet connection is active. Details: ${a}`)}let i=await ra({res:o,url:n});if(!na(i))throw new Error("Invalid GraphQL response");if(oa(i))throw new Error(i.errors.map(s=>s.message).join(", "));if(i.data==null)throw new Error("No data returned from server");return i.data}async function ra({res:e,url:t}){let r=await e.text();if(r.length===0)throw new Error(`Empty response from Ripplo server at ${t} (status ${String(e.status)}). The server may have restarted or the request was rejected before a body was sent.`);try{return JSON.parse(r)}catch{throw new Error(`Non-JSON response from Ripplo server at ${t} (status ${String(e.status)}): ${r.slice(0,200)}`)}}function na(e){return typeof e=="object"&&e!=null&&("data"in e||"errors"in e)}function oa(e){return Array.isArray(e.errors)&&e.errors.length>0}var On=Ln.join(process.cwd(),".ripplo","debug"),Dn=360*60*1e3,ch=la(`
22
22
  mutation RevokeCurrentCliToken {
23
23
  revokeCurrentCliToken
24
24
  }
25
- `);async function gr({maxRuns:e}){try{let r=(await la(Un,{withFileTypes:!0})).filter(c=>c.isDirectory()),n=Date.now(),o=await Promise.all(r.map(async c=>{let p=Fn.join(Un,c.name),m=await ua(p);return{dirPath:p,mtime:m.mtimeMs}})),i=o.filter(c=>n-c.mtime>Vn),a=o.filter(c=>n-c.mtime<=Vn).toSorted((c,p)=>p.mtime-c.mtime).slice(e),l=[...i,...a];if(l.length===0)return;await Promise.allSettled(l.map(c=>ca(c.dirPath,{force:!0,recursive:!0}))),f.info("Pruned %d old debug run(s)",l.length)}catch{f.warn("Debug run pruning failed, ignoring")}}var pa=5e3;async function yr({appUrl:e}){try{await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(pa)})}catch(t){let r=t instanceof Error?t.message:String(t);throw f.debug("App URL reachability check failed: %s",r),new Error([`Could not reach your dev server at ${e}.`,"","Troubleshooting:",` 1. Make sure your app is running at ${e}`," 2. Check RIPPLO_APP_URL in the env file declared in .ripplo/project.json"," 3. If you're in a git worktree, RIPPLO_APP_URL and RIPPLO_ENGINE_URL must point at this worktree's dev server port (not main's)"].join(`
26
- `))}}import tt from"fs";import xe from"fs";import ma from"os";import Xe from"path";var fa=".local",ga=".ripplo";function hr(e){return Xe.join(e,".ripplo",fa)}function Ze(e){let t=hr(e);xe.existsSync(t)||xe.mkdirSync(t,{recursive:!0});let r=Xe.join(t,".gitignore");return xe.existsSync(r)||xe.writeFileSync(r,`*
27
- `),t}function et(e,t){return Xe.join(hr(e),t)}function wr(){return Xe.join(ma.homedir(),ga)}function br(){let e=wr();return xe.existsSync(e)?xe.chmodSync(e,448):xe.mkdirSync(e,{mode:448,recursive:!0}),e}function ye(e){return Xe.join(wr(),e)}function Z(){let e=process.env.RIPPLO_TOKEN;if(e!=null&&e.trim().length>0)return e.trim();let t=ye("token");if(!tt.existsSync(t))return null;let r=tt.readFileSync(t,"utf8").trim();return r.length===0?null:r}function vr(e){br(),tt.writeFileSync(ye("token"),e+`
28
- `,{mode:384})}function kr(){let e=ye("token");return tt.existsSync(e)?(tt.unlinkSync(e),!0):!1}import{z as de}from"zod";import{z as wt}from"zod";var ya=wt.object({__codec:wt.string().min(1),data:wt.unknown(),version:wt.number().int().positive()}),bt=class extends Error{codec;currentVersion;gotVersion;constructor(t){super(`Unsupported ${t.codec} version ${String(t.gotVersion)} (current ${String(t.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`),this.name="CodecVersionError",this.codec=t.codec,this.currentVersion=t.currentVersion,this.gotVersion=t.gotVersion}},vt=class extends Error{constructor(t){super(`Codec mismatch: expected "${t.expected}", got "${t.got}"`),this.name="CodecMismatchError"}};function kt(e){return Hn({legacy:void 0,migrators:[],name:e,schemas:[]})}function Hn(e){return{initial:t=>Sr(t,{...e,schemas:[t]}),legacy:t=>Hn({...e,legacy:t})}}function Sr(e,t){return{build:()=>ha(e,t),legacy:r=>Sr(e,{...t,legacy:r}),upgrade:(r,n)=>{let o=n;return Sr(r,{...t,migrators:[...t.migrators,o],schemas:[...t.schemas,r]})}}}function ha(e,t){let r=t.schemas.length;return{currentVersion:r,name:t.name,decode:n=>wa(e,t,n),encode:n=>({__codec:t.name,data:n,version:r})}}function wa(e,t,r){let{data:n,version:o}=ba(t,r),i=Bn(t,n,o);return e.parse(i)}function ba(e,t){let r=ya.safeParse(t);if(r.success){if(r.data.__codec!==e.name)throw new vt({expected:e.name,got:r.data.__codec});return{data:r.data.data,version:r.data.version}}if(e.legacy!=null&&e.legacy.detect(t))return{data:t,version:e.legacy.assumedVersion};throw new Error(`Cannot decode "${e.name}": value is not a codec envelope and no legacy detector matched.`)}function Bn(e,t,r){let n=e.schemas.length;if(r>n)throw new bt({codec:e.name,currentVersion:n,gotVersion:r});if(r===n)return t;let o=e.schemas[r-1];if(o==null)throw new Error(`Codec "${e.name}" missing schema for v${String(r)}; cannot migrate.`);let i=o.parse(t),s=e.migrators[r-1];if(s==null)throw new Error(`Codec "${e.name}" missing migrator v${String(r)} \u2192 v${String(r+1)}.`);return Bn(e,s(i),r+1)}import{z as zn}from"zod";import{z as Ne}from"zod";var Rr=Ne.object({depends:Ne.array(Ne.string().min(1)).optional().describe("Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."),description:Ne.string().min(1).describe("Human-readable description of what this precondition ensures"),returns:Ne.array(Ne.string().min(1)).optional().describe("Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition.")}).describe("A named precondition declared at the graph level. States reference these by name.");import{z as u}from"zod";import{z as he}from"zod";var va=he.object({by:he.literal("testId"),value:he.string().min(1)}),ka=he.object({by:he.literal("role"),name:he.string().optional(),role:he.string().min(1)}),S=he.discriminatedUnion("by",[va,ka]);import{z as Wn}from"zod";var we=Wn.enum(["equals","notEquals","contains","startsWith","endsWith","matches"]),St=Wn.enum(["equals","notEquals","greaterThan","greaterThanOrEqual","lessThan","lessThanOrEqual"]);import{z as I}from"zod";var Sa=I.object({type:I.literal("static"),value:I.union([I.string(),I.number(),I.boolean()])}),Rt=I.object({name:I.string().min(1),type:I.literal("variable")}),Pt=I.discriminatedUnion("type",[Sa,Rt]),G=I.discriminatedUnion("type",[I.object({type:I.literal("static"),value:I.string()}),Rt]),xt=I.discriminatedUnion("type",[I.object({type:I.literal("static"),value:I.number().int().nonnegative()}),Rt]),Pr=I.discriminatedUnion("type",[I.object({type:I.literal("static"),value:I.union([I.string(),I.number(),I.boolean()])}),Rt]);import{z as K}from"zod";var Ct=K.discriminatedUnion("type",[K.object({default:K.string().optional(),type:K.literal("string")}),K.object({default:K.number().optional(),type:K.literal("number")}),K.object({default:K.boolean().optional(),type:K.literal("boolean")}),K.object({key:K.string().min(1),type:K.literal("env")})]);var b={id:u.string().min(1).max(200),label:u.string().max(500).optional(),next:u.string().max(200).optional(),uiOnly:u.boolean().optional()},Mn=500,Ra=u.object({...b,type:u.literal("goto"),url:G}),Pa=u.object({...b,locator:S,type:u.literal("click")}),xa=u.object({...b,locator:S,type:u.literal("fill"),value:G}),Ca=u.object({...b,locator:S,type:u.literal("select"),value:G}),Ea=u.object({...b,locator:S,type:u.literal("hover")}),Ia=u.object({...b,key:u.string().min(1),locator:S.optional(),type:u.literal("press")}),Aa=u.object({...b,locator:S,type:u.literal("check")}),ja=u.object({...b,locator:S,type:u.literal("uncheck")}),Ta=u.object({...b,height:u.number().int().positive(),type:u.literal("setViewport"),width:u.number().int().positive()}),$a=u.object({...b,message:u.string().min(1),type:u.literal("fail")}),Na=u.object({...b,type:u.literal("setVariable"),value:Pt,variable:u.string().min(1)}),Oa=u.object({...b,locator:S,type:u.literal("extractText"),variable:u.string().min(1)}),Da=u.object({...b,files:u.array(u.string()).min(1),locator:S,type:u.literal("upload")}),La=u.object({...b,locator:S,type:u.literal("dblclick")}),_a=u.object({...b,source:S,target:S,type:u.literal("drag")}),Ua=u.object({...b,locator:S,type:u.literal("scrollIntoView")}),Va=u.object({...b,locator:S,type:u.literal("type"),value:G}),Fa=u.object({...b,locator:S,type:u.literal("focus")}),Ha=u.object({...b,locator:S,type:u.literal("clear")}),Ba=u.object({...b,locator:S,type:u.literal("rightClick")}),Wa=u.object({...b,action:u.enum(["accept","dismiss"]),promptText:u.string().optional(),type:u.literal("handleDialog")}),Ma=u.object({...b,action:u.enum(["read","write"]),type:u.literal("clipboard"),value:G.optional(),variable:u.string().min(1).optional()}),qa=u.object({...b,permission:u.string().min(1),state:u.enum(["granted","prompt"]),type:u.literal("setPermission")}),za=u.object({...b,locator:S,type:u.literal("assertVisible")}),Ga=u.object({...b,locator:S,type:u.literal("assertNotVisible")}),Ka=u.object({...b,expected:G,locator:S,operator:we,type:u.literal("assertText")}),Ja=u.object({...b,expected:G,operator:we,type:u.literal("assertUrl")}),Qa=u.object({...b,expected:xt,locator:S,operator:St,type:u.literal("assertCount")}),Ya=u.object({...b,expected:G,locator:S,operator:we,type:u.literal("assertValue")}),Xa=u.object({...b,attribute:u.string().min(1),expected:G,locator:S,operator:we,type:u.literal("assertAttribute")}),Za=u.object({...b,locator:S,type:u.literal("assertEnabled")}),el=u.object({...b,locator:S,type:u.literal("assertDisabled")}),tl=u.object({...b,expected:G,operator:we,type:u.literal("assertTitle")}),rl=u.object({...b,locator:S,type:u.literal("assertChecked")}),nl=u.object({...b,locator:S,type:u.literal("assertNotChecked")}),ol=u.object({...b,locator:S,type:u.literal("assertFocused")}),il=u.object({...b,locator:S,type:u.literal("assertNotFocused")}),sl=u.object({...b,budget:u.enum(["fast","slow","async"]),observer:u.string().min(1).max(200),params:u.record(u.string().max(200),Pr),type:u.literal("assertObserver")}),qn=u.discriminatedUnion("type",[Ra,Pa,xa,Ca,Ea,Ia,Aa,ja,za,Ga,Ka,Ja,Qa,Ya,Xa,Za,el,Ta,$a,Na,Oa,Da,La,_a,Ua,Va,Fa,Ha,Ba,Wa,Ma,qa,tl,rl,nl,ol,il,sl]),Et=u.object({entryNode:u.string().min(1).max(200),nodes:u.record(u.string().max(200),qn).refine(e=>Object.keys(e).length<=Mn,`Workflow has more than ${String(Mn)} nodes`),uiOnly:u.boolean().optional(),variableNamespaces:u.record(u.string().max(200),u.string().max(500)).optional(),variables:u.record(u.string().max(200),Ct).optional()});var al=zn.record(zn.string().max(200),Rr),ll={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&"entryNode"in e&&"nodes"in e},cl={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&!Array.isArray(e)&&!("__codec"in e)},xr=kt("workflow-spec").legacy(ll).initial(Et).build(),ul=kt("precondition-map").legacy(cl).initial(al).build();import{z as Cr}from"zod";var Gn=["fast","slow","async"],dl=Cr.object({budget:Cr.enum(Gn).describe("Polling budget tier: fast | slow | async"),description:Cr.string().min(1).describe("Human-readable description of what this observer checks")}).describe("A named backend state observer. Tests assert against it with assert.backend(observer, params). Implementation lives on the user's server.");import{graphql as Lr,readFragment as It}from"gql.tada";import{mkdir as Er,writeFile as be}from"fs/promises";import ne from"path";async function Kn({context:e,page:t,runId:r,stepIndex:n,stepResult:o}){try{let i=ne.join(process.cwd(),".ripplo","debug",r,"steps",String(n));await Er(i,{recursive:!0});let s={assertions:o.assertions,detail:o.detail,duration:o.duration,nodeId:o.nodeId,nodeType:o.nodeType,snapshotTimestamp:o.snapshotTimestamp,status:o.status,stepIndex:o.stepIndex,title:o.title,url:o.url};await Promise.all([be(ne.join(i,"info.json"),JSON.stringify(s,null,2)),pl({context:e,page:t,stepDir:i})])}catch(i){let s=i instanceof Error?i.message:String(i);f.warn("Failed to write step debug data for step %d: %s",n,s)}}async function pl({context:e,page:t,stepDir:r}){let[n,o,i,s]=await Promise.all([ml(t),fl(t),hl({context:e,page:t}),gl(t)]);await Promise.all([be(ne.join(r,"dom.html"),n),be(ne.join(r,"accessibility-tree.txt"),o),be(ne.join(r,"storage.json"),JSON.stringify(i,null,2)),s==null?Promise.resolve():be(ne.join(r,"screenshot.png"),s)])}async function ml(e){return await z({awaitPromise:!1,expression:"document.documentElement.outerHTML",page:e})??"(unable to capture DOM)"}async function fl(e){return ft(e,"(unable to capture accessibility tree)",async t=>{let r=await t.send("Accessibility.getFullAXTree");return JSON.stringify(r,null,2)})}async function gl(e){return ft(e,void 0,async t=>{let r=await t.send("Page.captureScreenshot",{format:"png"});return Buffer.from(r.data,"base64")})}var yl=`(() => {
25
+ `);async function gr({maxRuns:e}){try{let r=(await ia(On,{withFileTypes:!0})).filter(c=>c.isDirectory()),n=Date.now(),o=await Promise.all(r.map(async c=>{let p=Ln.join(On,c.name),m=await aa(p);return{dirPath:p,mtime:m.mtimeMs}})),i=o.filter(c=>n-c.mtime>Dn),a=o.filter(c=>n-c.mtime<=Dn).toSorted((c,p)=>p.mtime-c.mtime).slice(e),l=[...i,...a];if(l.length===0)return;await Promise.allSettled(l.map(c=>sa(c.dirPath,{force:!0,recursive:!0}))),y.info("Pruned %d old debug run(s)",l.length)}catch{y.warn("Debug run pruning failed, ignoring")}}var ca=5e3;async function yr({appUrl:e}){try{await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(ca)})}catch(t){let r=t instanceof Error?t.message:String(t);throw y.debug("App URL reachability check failed: %s",r),new Error([`Could not reach your dev server at ${e}.`,"","Troubleshooting:",` 1. Make sure your app is running at ${e}`," 2. Check RIPPLO_APP_URL in the env file declared in .ripplo/project.json"," 3. If you're in a git worktree, RIPPLO_APP_URL and RIPPLO_ENGINE_URL must point at this worktree's dev server port (not main's)"].join(`
26
+ `))}}import tt from"fs";import Pe from"fs";import da from"os";import Xe from"path";var ua=".local",pa=".ripplo";function hr(e){return Xe.join(e,".ripplo",ua)}function Ze(e){let t=hr(e);Pe.existsSync(t)||Pe.mkdirSync(t,{recursive:!0});let r=Xe.join(t,".gitignore");return Pe.existsSync(r)||Pe.writeFileSync(r,`*
27
+ `),t}function et(e,t){return Xe.join(hr(e),t)}function wr(){return Xe.join(da.homedir(),pa)}function br(){let e=wr();return Pe.existsSync(e)?Pe.chmodSync(e,448):Pe.mkdirSync(e,{mode:448,recursive:!0}),e}function fe(e){return Xe.join(wr(),e)}function Z(){let e=process.env.RIPPLO_TOKEN;if(e!=null&&e.trim().length>0)return e.trim();let t=fe("token");if(!tt.existsSync(t))return null;let r=tt.readFileSync(t,"utf8").trim();return r.length===0?null:r}function vr(e){br(),tt.writeFileSync(fe("token"),e+`
28
+ `,{mode:384})}function kr(){let e=fe("token");return tt.existsSync(e)?(tt.unlinkSync(e),!0):!1}import{z as cc}from"zod";import{z as wt}from"zod";var ma=wt.object({__codec:wt.string().min(1),data:wt.unknown(),version:wt.number().int().positive()}),bt=class extends Error{codec;currentVersion;gotVersion;constructor(t){super(`Unsupported ${t.codec} version ${String(t.gotVersion)} (current ${String(t.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`),this.name="CodecVersionError",this.codec=t.codec,this.currentVersion=t.currentVersion,this.gotVersion=t.gotVersion}},vt=class extends Error{constructor(t){super(`Codec mismatch: expected "${t.expected}", got "${t.got}"`),this.name="CodecMismatchError"}};function kt(e){return _n({legacy:void 0,migrators:[],name:e,schemas:[]})}function _n(e){return{initial:t=>Sr(t,{...e,schemas:[t]}),legacy:t=>_n({...e,legacy:t})}}function Sr(e,t){return{build:()=>fa(e,t),legacy:r=>Sr(e,{...t,legacy:r}),upgrade:(r,n)=>{let o=n;return Sr(r,{...t,migrators:[...t.migrators,o],schemas:[...t.schemas,r]})}}}function fa(e,t){let r=t.schemas.length;return{currentVersion:r,name:t.name,decode:n=>ga(e,t,n),encode:n=>({__codec:t.name,data:n,version:r})}}function ga(e,t,r){let{data:n,version:o}=ya(t,r),i=Un(t,n,o);return e.parse(i)}function ya(e,t){let r=ma.safeParse(t);if(r.success){if(r.data.__codec!==e.name)throw new vt({expected:e.name,got:r.data.__codec});return{data:r.data.data,version:r.data.version}}if(e.legacy!=null&&e.legacy.detect(t))return{data:t,version:e.legacy.assumedVersion};throw new Error(`Cannot decode "${e.name}": value is not a codec envelope and no legacy detector matched.`)}function Un(e,t,r){let n=e.schemas.length;if(r>n)throw new bt({codec:e.name,currentVersion:n,gotVersion:r});if(r===n)return t;let o=e.schemas[r-1];if(o==null)throw new Error(`Codec "${e.name}" missing schema for v${String(r)}; cannot migrate.`);let i=o.parse(t),s=e.migrators[r-1];if(s==null)throw new Error(`Codec "${e.name}" missing migrator v${String(r)} \u2192 v${String(r+1)}.`);return Un(e,s(i),r+1)}import{z as Wn}from"zod";import{z as je}from"zod";var Rr=je.object({depends:je.array(je.string().min(1)).optional().describe("Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."),description:je.string().min(1).describe("Human-readable description of what this precondition ensures"),returns:je.array(je.string().min(1)).optional().describe("Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition.")}).describe("A named precondition declared at the graph level. States reference these by name.");import{z as d}from"zod";import{z as ge}from"zod";var ha=ge.object({by:ge.literal("testId"),value:ge.string().min(1)}),wa=ge.object({by:ge.literal("role"),name:ge.string().optional(),role:ge.string().min(1)}),S=ge.discriminatedUnion("by",[ha,wa]);import{z as Vn}from"zod";var ye=Vn.enum(["equals","notEquals","contains","startsWith","endsWith","matches"]),St=Vn.enum(["equals","notEquals","greaterThan","greaterThanOrEqual","lessThan","lessThanOrEqual"]);import{z as A}from"zod";var ba=A.object({type:A.literal("static"),value:A.union([A.string(),A.number(),A.boolean()])}),Rt=A.object({name:A.string().min(1),type:A.literal("variable")}),Pt=A.discriminatedUnion("type",[ba,Rt]),G=A.discriminatedUnion("type",[A.object({type:A.literal("static"),value:A.string()}),Rt]),xt=A.discriminatedUnion("type",[A.object({type:A.literal("static"),value:A.number().int().nonnegative()}),Rt]),Pr=A.discriminatedUnion("type",[A.object({type:A.literal("static"),value:A.union([A.string(),A.number(),A.boolean()])}),Rt]);import{z as K}from"zod";var Ct=K.discriminatedUnion("type",[K.object({default:K.string().optional(),type:K.literal("string")}),K.object({default:K.number().optional(),type:K.literal("number")}),K.object({default:K.boolean().optional(),type:K.literal("boolean")}),K.object({key:K.string().min(1),type:K.literal("env")})]);var b={id:d.string().min(1).max(200),label:d.string().max(500).optional(),next:d.string().max(200).optional(),uiOnly:d.boolean().optional()},Fn=500,va=d.object({...b,type:d.literal("goto"),url:G}),ka=d.object({...b,locator:S,type:d.literal("click")}),Sa=d.object({...b,locator:S,type:d.literal("fill"),value:G}),Ra=d.object({...b,locator:S,type:d.literal("select"),value:G}),Pa=d.object({...b,locator:S,type:d.literal("hover")}),xa=d.object({...b,key:d.string().min(1),locator:S.optional(),type:d.literal("press")}),Ca=d.object({...b,locator:S,type:d.literal("check")}),Ea=d.object({...b,locator:S,type:d.literal("uncheck")}),Aa=d.object({...b,height:d.number().int().positive(),type:d.literal("setViewport"),width:d.number().int().positive()}),Ia=d.object({...b,message:d.string().min(1),type:d.literal("fail")}),Ta=d.object({...b,type:d.literal("setVariable"),value:Pt,variable:d.string().min(1)}),$a=d.object({...b,locator:S,type:d.literal("extractText"),variable:d.string().min(1)}),ja=d.object({...b,files:d.array(d.string()).min(1),locator:S,type:d.literal("upload")}),Na=d.object({...b,locator:S,type:d.literal("dblclick")}),Oa=d.object({...b,source:S,target:S,type:d.literal("drag")}),Da=d.object({...b,locator:S,type:d.literal("scrollIntoView")}),La=d.object({...b,locator:S,type:d.literal("type"),value:G}),_a=d.object({...b,locator:S,type:d.literal("focus")}),Ua=d.object({...b,locator:S,type:d.literal("clear")}),Va=d.object({...b,locator:S,type:d.literal("rightClick")}),Fa=d.object({...b,action:d.enum(["accept","dismiss"]),promptText:d.string().optional(),type:d.literal("handleDialog")}),Ha=d.object({...b,action:d.enum(["read","write"]),type:d.literal("clipboard"),value:G.optional(),variable:d.string().min(1).optional()}),Wa=d.object({...b,permission:d.string().min(1),state:d.enum(["granted","prompt"]),type:d.literal("setPermission")}),Ba=d.object({...b,locator:S,type:d.literal("assertVisible")}),Ma=d.object({...b,locator:S,type:d.literal("assertNotVisible")}),qa=d.object({...b,expected:G,locator:S,operator:ye,type:d.literal("assertText")}),za=d.object({...b,expected:G,operator:ye,type:d.literal("assertUrl")}),Ga=d.object({...b,expected:xt,locator:S,operator:St,type:d.literal("assertCount")}),Ka=d.object({...b,expected:G,locator:S,operator:ye,type:d.literal("assertValue")}),Ja=d.object({...b,attribute:d.string().min(1),expected:G,locator:S,operator:ye,type:d.literal("assertAttribute")}),Qa=d.object({...b,locator:S,type:d.literal("assertEnabled")}),Ya=d.object({...b,locator:S,type:d.literal("assertDisabled")}),Xa=d.object({...b,expected:G,operator:ye,type:d.literal("assertTitle")}),Za=d.object({...b,locator:S,type:d.literal("assertChecked")}),el=d.object({...b,locator:S,type:d.literal("assertNotChecked")}),tl=d.object({...b,locator:S,type:d.literal("assertFocused")}),rl=d.object({...b,locator:S,type:d.literal("assertNotFocused")}),nl=d.object({...b,budget:d.enum(["fast","slow","async"]),observer:d.string().min(1).max(200),params:d.record(d.string().max(200),Pr),type:d.literal("assertObserver")}),Hn=d.discriminatedUnion("type",[va,ka,Sa,Ra,Pa,xa,Ca,Ea,Ba,Ma,qa,za,Ga,Ka,Ja,Qa,Ya,Aa,Ia,Ta,$a,ja,Na,Oa,Da,La,_a,Ua,Va,Fa,Ha,Wa,Xa,Za,el,tl,rl,nl]),Et=d.object({entryNode:d.string().min(1).max(200),nodes:d.record(d.string().max(200),Hn).refine(e=>Object.keys(e).length<=Fn,`Workflow has more than ${String(Fn)} nodes`),uiOnly:d.boolean().optional(),variableNamespaces:d.record(d.string().max(200),d.string().max(500)).optional(),variables:d.record(d.string().max(200),Ct).optional()});var ol=Wn.record(Wn.string().max(200),Rr),il={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&"entryNode"in e&&"nodes"in e},sl={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&!Array.isArray(e)&&!("__codec"in e)},xr=kt("workflow-spec").legacy(il).initial(Et).build(),al=kt("precondition-map").legacy(sl).initial(ol).build();import{z as Cr}from"zod";var Bn=["fast","slow","async"],ll=Cr.object({budget:Cr.enum(Bn).describe("Polling budget tier: fast | slow | async"),description:Cr.string().min(1).describe("Human-readable description of what this observer checks")}).describe("A named backend state observer. Tests assert against it with assert.backend(observer, params). Implementation lives on the user's server.");import{graphql as eo,readFragment as dc}from"gql.tada";import{mkdir as Er,writeFile as he}from"fs/promises";import ne from"path";async function Mn({context:e,page:t,runId:r,stepIndex:n,stepResult:o}){try{let i=ne.join(process.cwd(),".ripplo","debug",r,"steps",String(n));await Er(i,{recursive:!0});let s={assertions:o.assertions,detail:o.detail,duration:o.duration,nodeId:o.nodeId,nodeType:o.nodeType,snapshotTimestamp:o.snapshotTimestamp,status:o.status,stepIndex:o.stepIndex,title:o.title,url:o.url};await Promise.all([he(ne.join(i,"info.json"),JSON.stringify(s,null,2)),cl({context:e,page:t,stepDir:i})])}catch(i){let s=i instanceof Error?i.message:String(i);y.warn("Failed to write step debug data for step %d: %s",n,s)}}async function cl({context:e,page:t,stepDir:r}){let[n,o,i,s]=await Promise.all([dl(t),ul(t),fl({context:e,page:t}),pl(t)]);await Promise.all([he(ne.join(r,"dom.html"),n),he(ne.join(r,"accessibility-tree.txt"),o),he(ne.join(r,"storage.json"),JSON.stringify(i,null,2)),s==null?Promise.resolve():he(ne.join(r,"screenshot.png"),s)])}async function dl(e){return await z({awaitPromise:!1,expression:"document.documentElement.outerHTML",page:e})??"(unable to capture DOM)"}async function ul(e){return ft(e,"(unable to capture accessibility tree)",async t=>{let r=await t.send("Accessibility.getFullAXTree");return JSON.stringify(r,null,2)})}async function pl(e){return ft(e,void 0,async t=>{let r=await t.send("Page.captureScreenshot",{format:"png"});return Buffer.from(r.data,"base64")})}var ml=`(() => {
29
29
  function dump(s) {
30
30
  var out = {};
31
31
  for (var i = 0; i < s.length; i++) {
@@ -35,39 +35,43 @@ Add missing values to the env file(s) declared in .ripplo/project.json.${i}`)}le
35
35
  return out;
36
36
  }
37
37
  return { localStorage: dump(localStorage), sessionStorage: dump(sessionStorage) };
38
- })()`;async function hl({context:e,page:t}){let[r,n]=await Promise.all([z({awaitPromise:!1,expression:yl,page:t}),e.cookies().catch(()=>[])]);return{cookies:n,localStorage:r?.localStorage??{},sessionStorage:r?.sessionStorage??{}}}async function Jn({runId:e,steps:t,summary:r}){try{let n=ne.join(process.cwd(),".ripplo","debug",e);await Er(n,{recursive:!0}),await be(ne.join(n,"summary.txt"),wl({steps:t,summary:r})),f.info("Debug artifacts written to .ripplo/debug/%s/",e)}catch(n){let o=n instanceof Error?n.message:String(n);f.warn("Failed to write run debug summary: %s",o)}}function wl({steps:e,summary:t}){return[`Run ID: ${t.runId}`,`Workflow: ${t.workflowName}`,`Status: ${t.status}`,`Duration: ${String(t.duration)}ms`,`Passed: ${String(t.passCount)}`,`Failed: ${String(t.failCount)}`,"","Steps:",...e.map(n=>` [${String(n.stepIndex)}] ${n.status==="passed"?"\u2713":"\u2717"} ${n.title} (${String(n.duration)}ms) \u2014 ${n.detail??"ok"}`)].join(`
39
- `)}async function Qn({error:e,runId:t,stack:r,workflowName:n}){try{let o=ne.join(process.cwd(),".ripplo","debug",t);await Er(o,{recursive:!0});let i=r==null?e:`${e}
38
+ })()`;async function fl({context:e,page:t}){let[r,n]=await Promise.all([z({awaitPromise:!1,expression:ml,page:t}),e.cookies().catch(()=>[])]);return{cookies:n,localStorage:r?.localStorage??{},sessionStorage:r?.sessionStorage??{}}}async function qn({runId:e,steps:t,summary:r}){try{let n=ne.join(process.cwd(),".ripplo","debug",e);await Er(n,{recursive:!0}),await he(ne.join(n,"summary.txt"),gl({steps:t,summary:r})),y.info("Debug artifacts written to .ripplo/debug/%s/",e)}catch(n){let o=n instanceof Error?n.message:String(n);y.warn("Failed to write run debug summary: %s",o)}}function gl({steps:e,summary:t}){return[`Run ID: ${t.runId}`,`Workflow: ${t.workflowName}`,`Status: ${t.status}`,`Duration: ${String(t.duration)}ms`,`Passed: ${String(t.passCount)}`,`Failed: ${String(t.failCount)}`,"","Steps:",...e.map(n=>` [${String(n.stepIndex)}] ${n.status==="passed"?"\u2713":"\u2717"} ${n.title} (${String(n.duration)}ms) \u2014 ${n.detail??"ok"}`)].join(`
39
+ `)}async function zn({error:e,runId:t,stack:r,workflowName:n}){try{let o=ne.join(process.cwd(),".ripplo","debug",t);await Er(o,{recursive:!0});let i=r==null?e:`${e}
40
40
 
41
41
  ${r}`,s=[`Run ID: ${t}`,`Workflow: ${n}`,"Status: failed","",`Error: ${e}`,"",r==null?"":`Stack trace:
42
42
  ${r}`].join(`
43
- `);await Promise.all([be(ne.join(o,"error.txt"),i),be(ne.join(o,"summary.txt"),s)]),f.info("Debug error artifacts written to .ripplo/debug/%s/",t)}catch(o){let i=o instanceof Error?o.message:String(o);f.warn("Failed to write error debug artifacts: %s",i)}}import{readFileSync as bl}from"fs";import{createRequire as vl}from"module";import Yn from"path";import{gzipSync as kl}from"zlib";var Sl=vl(import.meta.url),Rl=Sl.resolve("rrweb"),Pl=Yn.join(Yn.dirname(Rl),"rrweb.umd.min.cjs"),xl=bl(Pl,"utf8"),Cl=[";(function() {"," try {"," if (window.__ripploRrwebLoaded) return;"," window.__ripploRrwebLoaded = true;"," window.__ripploRrwebBuffer = [];"," window.__ripploRrwebDrain = function() {"," var out = window.__ripploRrwebBuffer;"," window.__ripploRrwebBuffer = [];"," return out;"," };"," var r = window.rrweb;"," if (r == null || r.record == null) {"," console.warn('[ripplo] rrweb global not found');"," return;"," }"," function startRecording() {"," if (window.__ripploRrwebStartedAt != null) return;"," window.__ripploRrwebStartedAt = Date.now();"," var stop = r.record({"," emit: function(event) { window.__ripploRrwebBuffer.push(event); },"," recordCanvas: false,"," recordCrossOriginIframes: false,"," });"," window.__ripploRrwebStop = stop;"," }"," window.__ripploRrwebStart = startRecording;"," // Auto-start on every bootstrap run so agent-driven tests (which don't"," // emit a 'first step' signal before navigating) capture from the start."," startRecording();"," } catch (err) {"," console.error('[ripplo] rrweb bootstrap failed', err && err.message ? err.message : err);"," }","})();"].join(`
44
- `),El=xl+`
45
- `+Cl;async function Xn(e){let t,r=0;async function n(){await e.addScriptTag({content:El}).catch(()=>{})}await n(),e.on("load",()=>{n()});async function o(){await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebStart && globalThis.__ripploRrwebStart()",page:e});let l=await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebStartedAt",page:e});typeof l=="number"&&(t??=l)}function i(){if(t!=null)return Date.now()-t}async function s(){let l=await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebDrain ? globalThis.__ripploRrwebDrain() : []",page:e})??[],c=l[0],p=l.at(-1);if(c==null||p==null)return;t??=c.timestamp;let m=t,k=l.map(y=>JSON.stringify(y)).join(`
46
- `),j={body:kl(Buffer.from(k,"utf8")),chunkIndex:r,endTimestamp:p.timestamp-m,eventCount:l.length,startTimestamp:c.timestamp-m};return r+=1,j}async function a(){await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebStop && globalThis.__ripploRrwebStop()",page:e})}return{currentOffsetMs:i,detach:a,start:o,takeChunk:s}}import{z as ee}from"zod";import Zn from"crypto";import{Webhook as Il}from"standardwebhooks";function ve({body:e,secret:t}){let r=new Il(t),n=`msg_${Zn.randomUUID()}`,o=new Date,i=r.sign(n,o,e),s=Math.floor(o.getTime()/1e3);return{"webhook-id":n,"webhook-signature":i,"webhook-timestamp":String(s)}}function Ir(){return`whsec_${Zn.randomBytes(24).toString("base64")}`}var Al=15e4,jl={async:{backoffMs:[500,1e3,2e3,5e3],timeoutMs:12e4},fast:{backoffMs:[100,250,500,1e3],timeoutMs:5e3},slow:{backoffMs:[250,500,1e3,2e3],timeoutMs:3e4}},Tl=ee.discriminatedUnion("kind",[ee.object({kind:ee.literal("pass")}),ee.object({kind:ee.literal("retry"),reason:ee.string()}),ee.object({kind:ee.literal("fail"),reason:ee.string()})]),$l=ee.object({error:ee.string().optional(),outcome:Tl.optional(),success:ee.boolean()});async function eo({apiUrl:e,budget:t,observer:r,params:n,webhookSecret:o}){let i=jl[t],s=performance.now(),a={lastReason:void 0,pollCount:0};for(;performance.now()-s<i.timeoutMs;){a.pollCount+=1;let l=await Ol({apiUrl:e,observer:r,params:n,webhookSecret:o}),c=Nl({observer:r,outcome:l,start:s,state:a});if(c!=null)return c;let p=i.timeoutMs-(performance.now()-s);if(p<=0)break;let m=Math.min(Dl({backoff:i.backoffMs,pollCount:a.pollCount}),p);await Ll(m)}return{description:`observer "${r}"`,detail:`budget "${t}" exhausted after ${String(a.pollCount)} poll(s) (${to(s)}); last: ${a.lastReason??"no retry reason"}
47
- \u2192 /ripplo:debug \u2014 check .ripplo/debug/<runId>/ artifacts`,status:"failed"}}function Nl({observer:e,outcome:t,start:r,state:n}){return t.kind==="transport-error"?{description:`observer "${e}"`,detail:`transport error after ${String(n.pollCount)} poll(s): ${t.reason}
48
- Load \`/ripplo:setup\` skill for instructions on engine endpoint + webhook secret wiring.`,status:"failed"}:t.kind==="pass"?{description:`observer "${e}"`,detail:n.pollCount>1?`passed after ${String(n.pollCount)} polls (${to(r)})`:void 0,status:"passed"}:t.kind==="fail"?{description:`observer "${e}"`,detail:`failed (invariant): ${t.reason} (after ${String(n.pollCount)} poll(s)) \u2014 ctx.fail stops polling immediately and is reserved for invariant violations (wrong shape, contradictory/forbidden state). For transient conditions like "not yet committed", "status not yet X", or "row not found", use ctx.retry(reason) so the runtime keeps polling until the budget expires.
49
- Load \`/ripplo:create\` skill for instructions.`,status:"failed"}:(n.lastReason=t.reason,null)}async function Ol({apiUrl:e,observer:t,params:r,webhookSecret:n}){let o=JSON.stringify({observer:t,params:r});try{let i=await fetch(`${e}/execute-observer`,{body:o,headers:{"Content-Type":"application/json",...ve({body:o,secret:n})},method:"PUT",signal:AbortSignal.timeout(Al)});if(!i.ok){let l=await i.text().catch(()=>"");return{kind:"transport-error",reason:`status ${String(i.status)}: ${l.slice(0,200)}`}}let s=await i.json(),a=$l.safeParse(s);return a.success?!a.data.success||a.data.outcome==null?{kind:"transport-error",reason:a.data.error??"engine reported failure without outcome"}:a.data.outcome:{kind:"transport-error",reason:`invalid response shape: ${a.error.message}`}}catch(i){let s=i instanceof Error?i.message:String(i);return f.error("observer request failed: %s",s),{kind:"transport-error",reason:s}}}function Dl({backoff:e,pollCount:t}){let r=Math.min(t-1,e.length-1);return e[r]??1e3}function to(e){return`${String(Math.round(performance.now()-e))}ms`}async function Ll(e){return new Promise(t=>setTimeout(t,e))}var _l=1e4;async function Ar({failFast:e,getRecordingOffsetMs:t,observerTransport:r,onStep:n,page:o,spec:i,variables:s}){let a=performance.now();return Vl({currentId:i.entryNode,failFast:e,getRecordingOffsetMs:t,observerTransport:r,onStep:n,page:o,runStartTime:a,spec:i,variables:s})}function Ul({onStep:e,steps:t,target:r}){t.forEach(n=>{let o={...n,stepIndex:r.length};r.push(o),e?.(o)})}async function Vl({currentId:e,failFast:t,getRecordingOffsetMs:r,observerTransport:n,onStep:o,page:i,runStartTime:s,spec:a,variables:l}){let c=[],p=l,m=e;for(;m!=null;){let k=a.nodes[m];if(k==null)throw new Error(`Node "${m}" not found in spec`);let L=m,j=k.label??`${k.type} (${m})`;f.info("Executing node: %s [%s]",j,k.type);let y=await Fl({getRecordingOffsetMs:r,node:k,nodeId:L,observerTransport:n,page:i,runStartTime:s,variables:p});if(Ul({onStep:o,steps:[y],target:c}),p=y.variables,t&&y.status==="failed")break;m=k.next}return{steps:c,variables:p}}async function Fl({getRecordingOffsetMs:e,node:t,nodeId:r,observerTransport:n,page:o,runStartTime:i,variables:s}){let a=performance.now(),l=e?.(),c=t.label??`${t.type} (${t.id})`,p=_l;try{let m=await Hl({node:t,observerTransport:n,page:o,timeout:p,variables:s}),k=Math.round(performance.now()-a),L=m.assertions.some(y=>y.status==="failed"),j=e?.();return{assertions:m.assertions,detail:void 0,duration:k,nodeId:r,nodeType:t.type,recordingEndMs:j,recordingStartMs:l,snapshotTimestamp:Math.round(performance.now()-i),status:L?"failed":"passed",stepIndex:0,title:c,url:o.url(),variables:m.variables}}catch(m){let k=Math.round(performance.now()-a),L=m instanceof Error?m.message:String(m),j=e?.();return{assertions:[],detail:L,duration:k,nodeId:r,nodeType:t.type,recordingEndMs:j,recordingStartMs:l,snapshotTimestamp:Math.round(performance.now()-i),status:"failed",stepIndex:0,title:c,url:o.url(),variables:s}}}async function Hl({node:e,observerTransport:t,page:r,timeout:n,variables:o}){if(Bl(e)){let s=await ar({node:e,page:r,timeout:n,variables:o});return{assertions:[],variables:s.variables}}return{assertions:await Ml({node:e,observerTransport:t,page:r,timeout:n,variables:o}),variables:o}}function Bl(e){return["goto","click","dblclick","fill","select","hover","press","check","uncheck","clear","rightClick","handleDialog","clipboard","setPermission","setViewport","fail","setVariable","extractText","upload","drag","scrollIntoView","type","focus"].includes(e.type)}function Wl(e){return e.type.startsWith("assert")}async function Ml({node:e,observerTransport:t,page:r,timeout:n,variables:o}){if(!Wl(e))throw new Error(`Unknown node type: ${e.type}`);let i=e;switch(i.type){case"assertVisible":case"assertNotVisible":case"assertEnabled":case"assertDisabled":case"assertChecked":case"assertNotChecked":case"assertFocused":case"assertNotFocused":return zl({assertNode:i,page:r,timeout:n,variables:o});case"assertText":{let s=M({ref:i.expected,variables:o});return[await lr({expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertUrl":{let s=M({ref:i.expected,variables:o});return[await cr({expected:s,operator:i.operator,page:r,timeout:n})]}case"assertTitle":{let s=M({ref:i.expected,variables:o});return[await Pn({expected:s,operator:i.operator,page:r,timeout:n})]}case"assertCount":{let s=sr({ref:i.expected,variables:o});return[await ur({expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertValue":{let s=M({ref:i.expected,variables:o});return[await dr({expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertAttribute":{let s=M({ref:i.expected,variables:o});return[await xn({attribute:i.attribute,expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertObserver":{if(t==null)return[{description:`observer "${i.observer}"`,detail:"observer transport not configured \u2014 set engineUrl + webhookSecret in ripplo.config",status:"failed"}];let s={};return Object.entries(i.params).forEach(([a,l])=>{s[a]=yt({ref:l,variables:o})}),[await eo({apiUrl:t.apiUrl,budget:i.budget,observer:i.observer,params:s,webhookSecret:t.webhookSecret})]}}}var ql={assertChecked:In,assertDisabled:En,assertEnabled:Cn,assertFocused:jn,assertNotChecked:An,assertNotFocused:Tn,assertNotVisible:mr,assertVisible:pr};async function zl({assertNode:e,page:t,timeout:r,variables:n}){let o=ql[e.type];return[await o({locator:O({locator:e.locator,page:t,variables:n}),timeout:r})]}var ro=1e4;async function jr({baseUrl:e,browser:t,cookies:r,extraVariables:n,observerTransport:o,onRecordingChunk:i,onStep:s,runId:a,spec:l}){let c=await t.newContext({baseURL:e});c.setDefaultTimeout(ro),c.setDefaultNavigationTimeout(ro),r.length>0&&await c.addCookies(r);let p=await c.newPage(),m=await Xn(p);async function k(){let W=await m.takeChunk();W!=null&&i?.(W)}let L=setInterval(()=>{k()},2e3),j=[],y=!1;function N(W){s?.(W),y||(y=!0,m.start()),j.push(Kn({context:c,page:p,runId:a,stepIndex:W.stepIndex,stepResult:W}))}let T=Gl(n,l.variableNamespaces),F={...ir({defs:l.variables}),...T},te=await Ar({failFast:!0,getRecordingOffsetMs:m.currentOffsetMs,observerTransport:o,onStep:N,page:p,spec:l,variables:F});clearInterval(L);let Ee=te.steps.filter(W=>W.status==="passed").length,oe=te.steps.filter(W=>W.status==="failed").length,qe={duration:te.steps.reduce((W,re)=>W+re.duration,0),failCount:oe,passCount:Ee,runId:a,status:oe>0?"failed":"passed",workflowName:""};return await Promise.all([Promise.allSettled(j).then(()=>Jn({runId:a,steps:te.steps,summary:qe})),m.detach().then(k)]),await c.close(),te.steps}function Gl(e,t){if(e==null||t==null)return{};let r=new Map;Object.entries(t).forEach(([o,i])=>{r.set(i,o)});let n={};return Object.entries(e).forEach(([o,i])=>{let s=r.get(o);s!=null&&(n[s]={...n[s],...i})}),n}async function ue({fn:e,label:t}){let r=performance.now(),n=await e();return f.info("%s: %dms",t,Math.round(performance.now()-r)),n}import{parseSetCookie as Kl}from"set-cookie-parser";import{z as J}from"zod";var no=3e4,Jl=J.object({data:J.record(J.string(),J.record(J.string(),J.string())),error:J.string().optional(),executed:J.array(J.string()),runId:J.string(),success:J.boolean()});async function Tr({project:e,webhookSecret:t,workflowSlug:r}){let n={apiUrl:void 0,cookies:[],data:{},executed:[]},o=e.workflows.find(a=>a.slug===r);if(o==null)return n;let i=e.engineBaseUrl.length===0?void 0:e.engineBaseUrl,s=o.preconditions;return s.length===0?{...n,apiUrl:i}:i==null?n:oo({apiUrl:i,preconditionNames:s,webhookSecret:t})}async function $r({apiUrl:e,cookies:t,data:r,executed:n,webhookSecret:o}){if(n.length!==0)try{let i=JSON.stringify({data:r,preconditions:n}),s=await fetch(`${e}/teardown-preconditions`,{body:i,headers:{"Content-Type":"application/json",...Zl({cookies:t}),...ve({body:i,secret:o})},method:"PUT",signal:AbortSignal.timeout(no)});s.ok||f.error("Teardown returned %s",String(s.status))}catch{f.error("Teardown request failed")}}async function oo({apiUrl:e,preconditionNames:t,webhookSecret:r}){let n=performance.now(),o=JSON.stringify({preconditions:t}),i=await fetch(`${e}/execute-preconditions`,{body:o,headers:{"Content-Type":"application/json",...ve({body:o,secret:r})},method:"PUT",signal:AbortSignal.timeout(no)});if(!i.ok){let c=await Yl(i);throw new Error(`execute-preconditions returned ${String(i.status)}: ${c}`)}let s=await i.json(),a=Jl.safeParse(s);if(!a.success)throw new Error(`execute-preconditions response has invalid shape: ${a.error.message}`);if(!a.data.success){let c=a.data.error??"no error detail";throw new Error(`Precondition batch failed: ${c}`)}let l=ec({domain:new URL(e).hostname,res:i});return f.info("Preconditions resolved: %s (%dms)",a.data.executed.join(", "),Math.round(performance.now()-n)),{apiUrl:e,cookies:l,data:a.data.data,executed:a.data.executed}}var Ql=J.object({error:J.string()});async function Yl(e){let t=await e.text().catch(()=>"");if(t.length===0)return"no body";let r=Xl(t),n=r==null?null:Ql.safeParse(r);return n!=null&&n.success?n.data.error:t.slice(0,500)}function Xl(e){try{return JSON.parse(e)}catch{return null}}function Zl({cookies:e}){return e.length===0?{}:{Cookie:e.map(r=>`${r.name}=${r.value}`).join("; ")}}function ec({domain:e,res:t}){let r=t.headers.getSetCookie();return Kl(r,{decodeValues:!1}).map(o=>tc({cookie:o,domain:e}))}function tc({cookie:e,domain:t}){let r={domain:e.domain??t,httpOnly:e.httpOnly??!1,name:e.name,path:e.path??"/",secure:e.secure??!1,value:e.value};return e.sameSite!=null&&(r.sameSite=nc({raw:e.sameSite})),e.expires!=null?r.expires=Math.floor(e.expires.getTime()/1e3):e.maxAge!=null&&(r.expires=Math.floor(Date.now()/1e3)+e.maxAge),r}var rc={lax:"Lax",none:"None",strict:"Strict"};function nc({raw:e}){return rc[e.toLowerCase()]??"Lax"}async function Nr({engineBaseUrl:e,preconditionNames:t,webhookSecret:r}){let n=e.length===0?void 0:e;return n==null||t.length===0?{apiUrl:n,cookies:[],data:{},executed:[]}:oo({apiUrl:n,preconditionNames:t,webhookSecret:r})}import{graphql as rt}from"gql.tada";async function Or({config:e,runId:t}){let r=await g({config:e,document:ac,variables:{platform:"chromium",runId:t}});if(r.startRun==null)throw new Error("Failed to start run");let n=r.startRun.id,o=[];function i(c){let p=ic({config:e,runResultId:n,steps:[c]}).catch(m=>{f.error(m,"Failed to submit step %d",c.stepIndex)});o.push(p)}function s(c){let p=oc({chunk:c,config:e,runResultId:n}).catch(m=>{f.error(m,"Failed to submit recording chunk %d",c.chunkIndex)});o.push(p)}async function a(){await Promise.all(o)}async function l({statusOverride:c,steps:p,summary:m}){await ue({label:`flushSteps (${String(o.length)} steps)`,fn:()=>Promise.all(o)});let k=p.filter(N=>N.status==="passed").length,L=p.filter(N=>N.status==="failed").length,j=p.reduce((N,T)=>N+T.duration,0),y=c??(L>0?"failed":"passed");await ue({label:"completeRun",fn:()=>g({config:e,document:uc,variables:{duration:j,failCount:L,passCount:k,runResultId:n,status:y,summary:m??null,warnCount:0}})})}return{complete:l,enqueueRecordingChunk:s,enqueueStep:i,flushSteps:a,runResultId:n}}async function oc({chunk:e,config:t,runResultId:r}){await g({config:t,document:cc,variables:{input:{bodyBase64:e.body.toString("base64"),chunkIndex:e.chunkIndex,endTimestamp:e.endTimestamp,eventCount:e.eventCount,runResultId:r,startTimestamp:e.startTimestamp}}})}async function ic({config:e,runResultId:t,steps:r}){let n=r.map(o=>({assertions:o.assertions.map(i=>({description:i.description,detail:i.detail,status:i.status})),detail:o.detail,duration:o.duration,nodeType:o.nodeType,recordingEndMs:o.recordingEndMs??null,recordingStartMs:o.recordingStartMs??null,snapshotTimestamp:o.snapshotTimestamp,status:o.status,stepIndex:o.stepIndex,title:o.title,url:o.url}));await g({config:e,document:lc,variables:{runResultId:t,steps:n}})}async function Dr({config:e,reason:t,runId:r}){await g({config:e,document:sc,variables:{reason:t,runId:r}})}var sc=rt(`
43
+ `);await Promise.all([he(ne.join(o,"error.txt"),i),he(ne.join(o,"summary.txt"),s)]),y.info("Debug error artifacts written to .ripplo/debug/%s/",t)}catch(o){let i=o instanceof Error?o.message:String(o);y.warn("Failed to write error debug artifacts: %s",i)}}import{readFileSync as yl}from"fs";import{createRequire as hl}from"module";import Gn from"path";import{gzipSync as wl}from"zlib";var bl=hl(import.meta.url),vl=bl.resolve("rrweb"),kl=Gn.join(Gn.dirname(vl),"rrweb.umd.min.cjs"),Sl=yl(kl,"utf8"),Rl=[";(function() {"," try {"," if (window.__ripploRrwebLoaded) return;"," window.__ripploRrwebLoaded = true;"," window.__ripploRrwebBuffer = [];"," window.__ripploRrwebDrain = function() {"," var out = window.__ripploRrwebBuffer;"," window.__ripploRrwebBuffer = [];"," return out;"," };"," var r = window.rrweb;"," if (r == null || r.record == null) {"," console.warn('[ripplo] rrweb global not found');"," return;"," }"," function startRecording() {"," if (window.__ripploRrwebStartedAt != null) return;"," window.__ripploRrwebStartedAt = Date.now();"," var stop = r.record({"," emit: function(event) { window.__ripploRrwebBuffer.push(event); },"," recordCanvas: false,"," recordCrossOriginIframes: false,"," });"," window.__ripploRrwebStop = stop;"," }"," window.__ripploRrwebStart = startRecording;"," // Auto-start on every bootstrap run so agent-driven tests (which don't"," // emit a 'first step' signal before navigating) capture from the start."," startRecording();"," } catch (err) {"," console.error('[ripplo] rrweb bootstrap failed', err && err.message ? err.message : err);"," }","})();"].join(`
44
+ `),Pl=Sl+`
45
+ `+Rl;async function Kn(e){let t,r=0;async function n(){await e.addScriptTag({content:Pl}).catch(()=>{})}await n(),e.on("load",()=>{n()});async function o(){await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebStart && globalThis.__ripploRrwebStart()",page:e});let l=await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebStartedAt",page:e});typeof l=="number"&&(t??=l)}function i(){if(t!=null)return Date.now()-t}async function s(){let l=await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebDrain ? globalThis.__ripploRrwebDrain() : []",page:e})??[],c=l[0],p=l.at(-1);if(c==null||p==null)return;t??=c.timestamp;let m=t,k=l.map(f=>JSON.stringify(f)).join(`
46
+ `),N={body:wl(Buffer.from(k,"utf8")),chunkIndex:r,endTimestamp:p.timestamp-m,eventCount:l.length,startTimestamp:c.timestamp-m};return r+=1,N}async function a(){await z({awaitPromise:!1,expression:"globalThis.__ripploRrwebStop && globalThis.__ripploRrwebStop()",page:e})}return{currentOffsetMs:i,detach:a,start:o,takeChunk:s}}import{z as ee}from"zod";import Jn from"crypto";import{Webhook as xl}from"standardwebhooks";function we({body:e,secret:t}){let r=new xl(t),n=`msg_${Jn.randomUUID()}`,o=new Date,i=r.sign(n,o,e),s=Math.floor(o.getTime()/1e3);return{"webhook-id":n,"webhook-signature":i,"webhook-timestamp":String(s)}}function Ar(){return`whsec_${Jn.randomBytes(24).toString("base64")}`}var Cl=15e4,El={async:{backoffMs:[500,1e3,2e3,5e3],timeoutMs:12e4},fast:{backoffMs:[100,250,500,1e3],timeoutMs:5e3},slow:{backoffMs:[250,500,1e3,2e3],timeoutMs:3e4}},Al=ee.discriminatedUnion("kind",[ee.object({kind:ee.literal("pass")}),ee.object({kind:ee.literal("retry"),reason:ee.string()}),ee.object({kind:ee.literal("fail"),reason:ee.string()})]),Il=ee.object({error:ee.string().optional(),outcome:Al.optional(),success:ee.boolean()});async function Qn({apiUrl:e,budget:t,observer:r,params:n,webhookSecret:o}){let i=El[t],s=performance.now(),a={lastReason:void 0,pollCount:0};for(;performance.now()-s<i.timeoutMs;){a.pollCount+=1;let l=await $l({apiUrl:e,observer:r,params:n,webhookSecret:o}),c=Tl({observer:r,outcome:l,start:s,state:a});if(c!=null)return c;let p=i.timeoutMs-(performance.now()-s);if(p<=0)break;let m=Math.min(jl({backoff:i.backoffMs,pollCount:a.pollCount}),p);await Nl(m)}return{description:`observer "${r}"`,detail:`budget "${t}" exhausted after ${String(a.pollCount)} poll(s) (${Yn(s)}); last: ${a.lastReason??"no retry reason"}
47
+ \u2192 /ripplo:debug \u2014 check .ripplo/debug/<runId>/ artifacts`,status:"failed"}}function Tl({observer:e,outcome:t,start:r,state:n}){return t.kind==="transport-error"?{description:`observer "${e}"`,detail:`transport error after ${String(n.pollCount)} poll(s): ${t.reason}
48
+ Load \`/ripplo:setup\` skill for instructions on engine endpoint + webhook secret wiring.`,status:"failed"}:t.kind==="pass"?{description:`observer "${e}"`,detail:n.pollCount>1?`passed after ${String(n.pollCount)} polls (${Yn(r)})`:void 0,status:"passed"}:t.kind==="fail"?{description:`observer "${e}"`,detail:`failed (invariant): ${t.reason} (after ${String(n.pollCount)} poll(s)) \u2014 ctx.fail stops polling immediately and is reserved for invariant violations (wrong shape, contradictory/forbidden state). For transient conditions like "not yet committed", "status not yet X", or "row not found", use ctx.retry(reason) so the runtime keeps polling until the budget expires.
49
+ Load \`/ripplo:create\` skill for instructions.`,status:"failed"}:(n.lastReason=t.reason,null)}async function $l({apiUrl:e,observer:t,params:r,webhookSecret:n}){let o=JSON.stringify({observer:t,params:r});try{let i=await fetch(`${e}/execute-observer`,{body:o,headers:{"Content-Type":"application/json",...we({body:o,secret:n})},method:"PUT",signal:AbortSignal.timeout(Cl)});if(!i.ok){let l=await i.text().catch(()=>"");return{kind:"transport-error",reason:`status ${String(i.status)}: ${l.slice(0,200)}`}}let s=await i.json(),a=Il.safeParse(s);return a.success?!a.data.success||a.data.outcome==null?{kind:"transport-error",reason:a.data.error??"engine reported failure without outcome"}:a.data.outcome:{kind:"transport-error",reason:`invalid response shape: ${a.error.message}`}}catch(i){let s=i instanceof Error?i.message:String(i);return y.error("observer request failed: %s",s),{kind:"transport-error",reason:s}}}function jl({backoff:e,pollCount:t}){let r=Math.min(t-1,e.length-1);return e[r]??1e3}function Yn(e){return`${String(Math.round(performance.now()-e))}ms`}async function Nl(e){return new Promise(t=>setTimeout(t,e))}var Ol=1e4;async function Ir({failFast:e,getRecordingOffsetMs:t,observerTransport:r,onStep:n,page:o,spec:i,variables:s}){let a=performance.now();return Ll({currentId:i.entryNode,failFast:e,getRecordingOffsetMs:t,observerTransport:r,onStep:n,page:o,runStartTime:a,spec:i,variables:s})}function Dl({onStep:e,steps:t,target:r}){t.forEach(n=>{let o={...n,stepIndex:r.length};r.push(o),e?.(o)})}async function Ll({currentId:e,failFast:t,getRecordingOffsetMs:r,observerTransport:n,onStep:o,page:i,runStartTime:s,spec:a,variables:l}){let c=[],p=l,m=e;for(;m!=null;){let k=a.nodes[m];if(k==null)throw new Error(`Node "${m}" not found in spec`);let j=m,N=k.label??`${k.type} (${m})`;y.info("Executing node: %s [%s]",N,k.type);let f=await _l({getRecordingOffsetMs:r,node:k,nodeId:j,observerTransport:n,page:i,runStartTime:s,variables:p});if(Dl({onStep:o,steps:[f],target:c}),p=f.variables,t&&f.status==="failed")break;m=k.next}return{steps:c,variables:p}}async function _l({getRecordingOffsetMs:e,node:t,nodeId:r,observerTransport:n,page:o,runStartTime:i,variables:s}){let a=performance.now(),l=e?.(),c=t.label??`${t.type} (${t.id})`,p=Ol;try{let m=await Ul({node:t,observerTransport:n,page:o,timeout:p,variables:s}),k=Math.round(performance.now()-a),j=m.assertions.some(f=>f.status==="failed"),N=e?.();return{assertions:m.assertions,detail:void 0,duration:k,nodeId:r,nodeType:t.type,recordingEndMs:N,recordingStartMs:l,snapshotTimestamp:Math.round(performance.now()-i),status:j?"failed":"passed",stepIndex:0,title:c,url:o.url(),variables:m.variables}}catch(m){let k=Math.round(performance.now()-a),j=m instanceof Error?m.message:String(m),N=e?.();return{assertions:[],detail:j,duration:k,nodeId:r,nodeType:t.type,recordingEndMs:N,recordingStartMs:l,snapshotTimestamp:Math.round(performance.now()-i),status:"failed",stepIndex:0,title:c,url:o.url(),variables:s}}}async function Ul({node:e,observerTransport:t,page:r,timeout:n,variables:o}){if(Vl(e)){let s=await ar({node:e,page:r,timeout:n,variables:o});return{assertions:[],variables:s.variables}}return{assertions:await Hl({node:e,observerTransport:t,page:r,timeout:n,variables:o}),variables:o}}function Vl(e){return["goto","click","dblclick","fill","select","hover","press","check","uncheck","clear","rightClick","handleDialog","clipboard","setPermission","setViewport","fail","setVariable","extractText","upload","drag","scrollIntoView","type","focus"].includes(e.type)}function Fl(e){return e.type.startsWith("assert")}async function Hl({node:e,observerTransport:t,page:r,timeout:n,variables:o}){if(!Fl(e))throw new Error(`Unknown node type: ${e.type}`);let i=e;switch(i.type){case"assertVisible":case"assertNotVisible":case"assertEnabled":case"assertDisabled":case"assertChecked":case"assertNotChecked":case"assertFocused":case"assertNotFocused":return Bl({assertNode:i,page:r,timeout:n,variables:o});case"assertText":{let s=M({ref:i.expected,variables:o});return[await lr({expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertUrl":{let s=M({ref:i.expected,variables:o});return[await cr({expected:s,operator:i.operator,page:r,timeout:n})]}case"assertTitle":{let s=M({ref:i.expected,variables:o});return[await vn({expected:s,operator:i.operator,page:r,timeout:n})]}case"assertCount":{let s=sr({ref:i.expected,variables:o});return[await dr({expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertValue":{let s=M({ref:i.expected,variables:o});return[await ur({expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertAttribute":{let s=M({ref:i.expected,variables:o});return[await kn({attribute:i.attribute,expected:s,locator:O({locator:i.locator,page:r,variables:o}),operator:i.operator,timeout:n})]}case"assertObserver":{if(t==null)return[{description:`observer "${i.observer}"`,detail:"observer transport not configured \u2014 set engineUrl + webhookSecret in ripplo.config",status:"failed"}];let s={};return Object.entries(i.params).forEach(([a,l])=>{s[a]=yt({ref:l,variables:o})}),[await Qn({apiUrl:t.apiUrl,budget:i.budget,observer:i.observer,params:s,webhookSecret:t.webhookSecret})]}}}var Wl={assertChecked:Pn,assertDisabled:Rn,assertEnabled:Sn,assertFocused:Cn,assertNotChecked:xn,assertNotFocused:En,assertNotVisible:mr,assertVisible:pr};async function Bl({assertNode:e,page:t,timeout:r,variables:n}){let o=Wl[e.type];return[await o({locator:O({locator:e.locator,page:t,variables:n}),timeout:r})]}var Xn=1e4;async function Tr({baseUrl:e,browser:t,cookies:r,extraVariables:n,observerTransport:o,onRecordingChunk:i,onStep:s,runId:a,spec:l}){let c=await t.newContext({baseURL:e});c.setDefaultTimeout(Xn),c.setDefaultNavigationTimeout(Xn),r.length>0&&await c.addCookies(r);let p=await c.newPage(),m=await Kn(p);async function k(){let B=await m.takeChunk();B!=null&&i?.(B)}let j=setInterval(()=>{k()},2e3),N=[],f=!1;function W(B){s?.(B),f||(f=!0,m.start()),N.push(Mn({context:c,page:p,runId:a,stepIndex:B.stepIndex,stepResult:B}))}let $=Ml(n,l.variableNamespaces),V={...ir({defs:l.variables}),...$},te=await Ir({failFast:!0,getRecordingOffsetMs:m.currentOffsetMs,observerTransport:o,onStep:W,page:p,spec:l,variables:V});clearInterval(j);let Ce=te.steps.filter(B=>B.status==="passed").length,oe=te.steps.filter(B=>B.status==="failed").length,qe={duration:te.steps.reduce((B,re)=>B+re.duration,0),failCount:oe,passCount:Ce,runId:a,status:oe>0?"failed":"passed",workflowName:""};return await Promise.all([Promise.allSettled(N).then(()=>qn({runId:a,steps:te.steps,summary:qe})),m.detach().then(k)]),await c.close(),te.steps}function Ml(e,t){if(e==null||t==null)return{};let r=new Map;Object.entries(t).forEach(([o,i])=>{r.set(i,o)});let n={};return Object.entries(e).forEach(([o,i])=>{let s=r.get(o);s!=null&&(n[s]={...n[s],...i})}),n}async function ce({fn:e,label:t}){let r=performance.now(),n=await e();return y.info("%s: %dms",t,Math.round(performance.now()-r)),n}import{parseSetCookie as ql}from"set-cookie-parser";import{z as J}from"zod";var Zn=3e4,zl=J.object({data:J.record(J.string(),J.record(J.string(),J.string())),error:J.string().optional(),executed:J.array(J.string()),runId:J.string(),success:J.boolean()});async function $r({apiUrl:e,cookies:t,data:r,executed:n,webhookSecret:o}){if(n.length!==0)try{let i=JSON.stringify({data:r,preconditions:n}),s=await fetch(`${e}/teardown-preconditions`,{body:i,headers:{"Content-Type":"application/json",...Yl({cookies:t}),...we({body:i,secret:o})},method:"PUT",signal:AbortSignal.timeout(Zn)});s.ok||y.error("Teardown returned %s",String(s.status))}catch{y.error("Teardown request failed")}}async function Gl({apiUrl:e,preconditionNames:t,webhookSecret:r}){let n=performance.now(),o=JSON.stringify({preconditions:t}),i=await fetch(`${e}/execute-preconditions`,{body:o,headers:{"Content-Type":"application/json",...we({body:o,secret:r})},method:"PUT",signal:AbortSignal.timeout(Zn)});if(!i.ok){let c=await Jl(i);throw new Error(`execute-preconditions returned ${String(i.status)}: ${c}`)}let s=await i.json(),a=zl.safeParse(s);if(!a.success)throw new Error(`execute-preconditions response has invalid shape: ${a.error.message}`);if(!a.data.success){let c=a.data.error??"no error detail";throw new Error(`Precondition batch failed: ${c}`)}let l=Xl({domain:new URL(e).hostname,res:i});return y.info("Preconditions resolved: %s (%dms)",a.data.executed.join(", "),Math.round(performance.now()-n)),{apiUrl:e,cookies:l,data:a.data.data,executed:a.data.executed}}var Kl=J.object({error:J.string()});async function Jl(e){let t=await e.text().catch(()=>"");if(t.length===0)return"no body";let r=Ql(t),n=r==null?null:Kl.safeParse(r);return n!=null&&n.success?n.data.error:t.slice(0,500)}function Ql(e){try{return JSON.parse(e)}catch{return null}}function Yl({cookies:e}){return e.length===0?{}:{Cookie:e.map(r=>`${r.name}=${r.value}`).join("; ")}}function Xl({domain:e,res:t}){let r=t.headers.getSetCookie();return ql(r,{decodeValues:!1}).map(o=>Zl({cookie:o,domain:e}))}function Zl({cookie:e,domain:t}){let r={domain:e.domain??t,httpOnly:e.httpOnly??!1,name:e.name,path:e.path??"/",secure:e.secure??!1,value:e.value};return e.sameSite!=null&&(r.sameSite=tc({raw:e.sameSite})),e.expires!=null?r.expires=Math.floor(e.expires.getTime()/1e3):e.maxAge!=null&&(r.expires=Math.floor(Date.now()/1e3)+e.maxAge),r}var ec={lax:"Lax",none:"None",strict:"Strict"};function tc({raw:e}){return ec[e.toLowerCase()]??"Lax"}async function At({engineBaseUrl:e,preconditionNames:t,webhookSecret:r}){let n=e.length===0?void 0:e;return n==null||t.length===0?{apiUrl:n,cookies:[],data:{},executed:[]}:Gl({apiUrl:n,preconditionNames:t,webhookSecret:r})}import{graphql as Ne}from"gql.tada";async function jr({config:e,runId:t}){if((await g({config:e,document:ic,variables:{runId:t}})).startRun==null)throw new Error("Failed to start run");let n=[];function o(l){let c=nc({config:e,runId:t,steps:[l]}).catch(p=>{y.error(p,"Failed to submit step %d",l.stepIndex)});n.push(c)}function i(l){let c=rc({chunk:l,config:e,runId:t}).catch(p=>{y.error(p,"Failed to submit recording chunk %d",l.chunkIndex)});n.push(c)}async function s(){await Promise.all(n)}async function a({statusOverride:l,steps:c,summary:p}){await ce({label:`flushSteps (${String(n.length)} steps)`,fn:()=>Promise.all(n)});let m=c.filter(f=>f.status==="passed").length,k=c.filter(f=>f.status==="failed").length,j=c.reduce((f,W)=>f+W.duration,0),N=l??(k>0?"failed":"passed");await ce({label:"completeRun",fn:()=>g({config:e,document:lc,variables:{duration:j,failCount:k,passCount:m,runId:t,status:N,summary:p??null,warnCount:0}})})}return{complete:a,enqueueRecordingChunk:i,enqueueStep:o,flushSteps:s,runId:t}}async function rc({chunk:e,config:t,runId:r}){await g({config:t,document:ac,variables:{input:{bodyBase64:e.body.toString("base64"),chunkIndex:e.chunkIndex,endTimestamp:e.endTimestamp,eventCount:e.eventCount,runId:r,startTimestamp:e.startTimestamp}}})}async function nc({config:e,runId:t,steps:r}){let n=r.map(o=>({assertions:o.assertions.map(i=>({description:i.description,detail:i.detail,status:i.status})),detail:o.detail,duration:o.duration,nodeType:o.nodeType,recordingEndMs:o.recordingEndMs??null,recordingStartMs:o.recordingStartMs??null,snapshotTimestamp:o.snapshotTimestamp,status:o.status,stepIndex:o.stepIndex,title:o.title,url:o.url}));await g({config:e,document:sc,variables:{runId:t,steps:n}})}async function Nr({config:e,reason:t,runId:r}){await g({config:e,document:oc,variables:{reason:t,runId:r}})}var Kw=Ne(`
50
+ mutation MarkRunArtifactsUploadedCLI($runId: String!) {
51
+ markRunArtifactsUploaded(runId: $runId)
52
+ }
53
+ `),oc=Ne(`
50
54
  mutation FailRunCLI($runId: String!, $reason: String!) {
51
55
  failRun(runId: $runId, reason: $reason)
52
56
  }
53
- `),ac=rt(`
54
- mutation StartRunCLI($runId: String!, $platform: String!) {
55
- startRun(runId: $runId, platform: $platform) {
57
+ `),ic=Ne(`
58
+ mutation StartRunCLI($runId: String!) {
59
+ startRun(runId: $runId) {
56
60
  id
57
61
  }
58
62
  }
59
- `),lc=rt(`
60
- mutation SubmitRunStepsCLI($runResultId: String!, $steps: [StepInput!]!) {
61
- submitRunSteps(runResultId: $runResultId, steps: $steps)
63
+ `),sc=Ne(`
64
+ mutation SubmitRunStepsCLI($runId: String!, $steps: [StepInput!]!) {
65
+ submitRunSteps(runId: $runId, steps: $steps)
62
66
  }
63
- `),cc=rt(`
67
+ `),ac=Ne(`
64
68
  mutation SubmitRunRecordingChunkCLI($input: SubmitRunRecordingChunkInput!) {
65
69
  submitRunRecordingChunk(input: $input)
66
70
  }
67
- `),uc=rt(`
71
+ `),lc=Ne(`
68
72
  mutation CompleteRunCLI(
69
- $runResultId: String!
70
- $status: String!
73
+ $runId: String!
74
+ $status: RunStatus!
71
75
  $duration: Int!
72
76
  $passCount: Int!
73
77
  $failCount: Int!
@@ -75,7 +79,7 @@ Load \`/ripplo:create\` skill for instructions.`,status:"failed"}:(n.lastReason=
75
79
  $summary: String
76
80
  ) {
77
81
  completeRun(
78
- runResultId: $runResultId
82
+ runId: $runId
79
83
  status: $status
80
84
  duration: $duration
81
85
  passCount: $passCount
@@ -87,7 +91,7 @@ Load \`/ripplo:create\` skill for instructions.`,status:"failed"}:(n.lastReason=
87
91
  status
88
92
  }
89
93
  }
90
- `);var dc=12e4,At=Lr(`
94
+ `);var uc=12e4,to=eo(`
91
95
  fragment WorkflowRun on Workflow {
92
96
  id
93
97
  slug
@@ -99,45 +103,69 @@ Load \`/ripplo:create\` skill for instructions.`,status:"failed"}:(n.lastReason=
99
103
  preconditionName
100
104
  }
101
105
  }
102
- `),io=Lr(`
103
- fragment ProjectRun on Project {
104
- id
105
- cloudBaseUrl
106
- engineBaseUrl
107
- workflows {
108
- ...WorkflowRun
106
+ `),pc=eo(`
107
+ query RunWorkflowSpec($runId: String!) {
108
+ run(id: $runId) {
109
+ id
110
+ workflow {
111
+ ...WorkflowRun
112
+ project {
113
+ id
114
+ engineBaseUrl
115
+ }
116
+ }
109
117
  }
110
118
  }
111
- `,[At]),pc=Lr(`
112
- query ProjectRun($projectId: String!) {
113
- project(id: $projectId) {
114
- ...ProjectRun
119
+ `,[to]);async function Or({baseUrl:e,browser:t,config:r,headed:n,preconditionNames:o,runId:i,webhookSecret:s}){let a="unknown",l,c,p=!1;try{p=t==null,c=mc({externalBrowser:t,headed:n});let m=o==null?void 0:At({engineBaseUrl:r.engineUrl,preconditionNames:o,webhookSecret:s}),[{engineBaseUrl:k,workflow:j},N]=await Promise.all([ce({label:"Run context resolved",fn:()=>wc({config:r,runId:i})}),ce({label:"Streaming run started",fn:()=>jr({config:r,runId:i})}),yr({appUrl:e})]);a=j.name,l=N;let f=bc(j.spec);y.info("Executing workflow: %s",j.name),await Sc(gc({baseUrl:e,browserPromise:c,engineBaseUrl:k,preconditionPromise:m,runId:i,specData:f,streaming:l,webhookSecret:s,workflow:j}),kc())}catch(m){throw await fc({browserPromise:c,config:r,error:m,ownsBrowser:p,runId:i,streaming:l,workflowName:a}),m}}function mc({externalBrowser:e,headed:t}){return e!=null?Promise.resolve(e):ce({label:"Browser launched",fn:()=>Ye({headed:t})})}async function fc({browserPromise:e,config:t,error:r,ownsBrowser:n,runId:o,streaming:i,workflowName:s}){let a=r instanceof Error?r.message:String(r),l=r instanceof Error?r.stack:void 0;await zn({error:a,runId:o,stack:l,workflowName:s}),i==null?await Nr({config:t,reason:a,runId:o}).catch(()=>{}):await i.complete({statusOverride:"failed",steps:[],summary:a}).catch(()=>{}),n&&e!=null&&await(await e.catch(()=>{}))?.close()}async function gc({baseUrl:e,browserPromise:t,engineBaseUrl:r,preconditionPromise:n,runId:o,specData:i,streaming:s,webhookSecret:a,workflow:l}){let c=n??At({engineBaseUrl:r,preconditionNames:l.preconditions,webhookSecret:a}),[p,m]=await Promise.all([c,t]);await yc({baseUrl:e,browser:m,preconditionResult:p,runId:o,specData:i,streaming:s,webhookSecret:a})}async function yc({baseUrl:e,browser:t,preconditionResult:r,runId:n,specData:o,streaming:i,webhookSecret:s}){let a=await ce({label:"Spec executed",fn:()=>Tr({baseUrl:e,browser:t,cookies:r.cookies,extraVariables:r.data,observerTransport:r.apiUrl==null?void 0:{apiUrl:r.apiUrl,webhookSecret:s},onRecordingChunk:i.enqueueRecordingChunk,onStep:i.enqueueStep,runId:n,spec:o})});await hc({preconditionResult:r,webhookSecret:s}),await i.complete({statusOverride:void 0,steps:a,summary:void 0});let l=a.filter(p=>p.status==="passed").length,c=a.filter(p=>p.status==="failed").length;y.info("Run complete: %d passed, %d failed",l,c)}async function hc({preconditionResult:e,webhookSecret:t}){e.apiUrl!=null&&await $r({apiUrl:e.apiUrl,cookies:e.cookies,data:e.data,executed:e.executed,webhookSecret:t})}async function wc({config:e,runId:t}){let r=await g({config:e,document:pc,variables:{runId:t}});if(r.run?.workflow==null)throw new Error(`Run ${t} not found`);let n=dc(to,r.run.workflow),o=r.run.workflow.project?.engineBaseUrl??"";return{engineBaseUrl:e.engineUrl.length>0?e.engineUrl:o,workflow:n}}function bc(e){if(e==null)throw new Error("Workflow has no spec");return xr.decode(e)}var vc=cc.coerce.number().int().positive().catch(uc);function kc(){return vc.parse(process.env.RIPPLO_RUN_TIMEOUT_MS)}async function Sc(e,t){let r,n=new Promise((o,i)=>{r=setTimeout(()=>{i(new Error(`Run timed out after ${String(t)}ms`))},t)});try{await Promise.race([e,n])}finally{r!=null&&clearTimeout(r)}}import{graphql as It}from"gql.tada";var ub=It(`
120
+ query AgentReviewContextWorker($reviewId: String!) {
121
+ agentReview(id: $reviewId) {
122
+ id
123
+ runId
124
+ agentProfile {
125
+ id
126
+ name
127
+ description
128
+ output
129
+ successCriteria
115
130
  }
116
131
  }
117
- `,[io]);async function _r({baseUrl:e,browser:t,config:r,headed:n,preconditionNames:o,runId:i,webhookSecret:s,workflowId:a}){let l="unknown",c,p,m=!1;try{m=t==null,p=mc({externalBrowser:t,headed:n});let k=o==null?void 0:Nr({engineBaseUrl:r.engineUrl,preconditionNames:o,webhookSecret:s}),[{project:L,workflow:j},y]=await Promise.all([ue({label:"Run context resolved",fn:()=>vc({config:r,workflowId:a})}),ue({label:"Streaming run started",fn:()=>Or({config:r,runId:i})}),yr({appUrl:e})]);l=j.name,c=y;let N=kc(j.spec);f.info("Executing workflow: %s",j.name),await Pc(gc({baseUrl:e,browserPromise:p,preconditionPromise:k,project:L,runId:i,specData:N,streaming:c,webhookSecret:s,workflow:j}),Rc())}catch(k){throw await fc({browserPromise:p,config:r,error:k,ownsBrowser:m,runId:i,streaming:c,workflowName:l}),k}}function mc({externalBrowser:e,headed:t}){return e!=null?Promise.resolve(e):ue({label:"Browser launched",fn:()=>Ye({headed:t})})}async function fc({browserPromise:e,config:t,error:r,ownsBrowser:n,runId:o,streaming:i,workflowName:s}){let a=r instanceof Error?r.message:String(r),l=r instanceof Error?r.stack:void 0;await Qn({error:a,runId:o,stack:l,workflowName:s}),i==null?await Dr({config:t,reason:a,runId:o}).catch(()=>{}):await i.complete({statusOverride:"failed",steps:[],summary:a}).catch(()=>{}),n&&e!=null&&await(await e.catch(()=>{}))?.close()}async function gc({baseUrl:e,browserPromise:t,preconditionPromise:r,project:n,runId:o,specData:i,streaming:s,webhookSecret:a,workflow:l}){let c=r??Tr({project:hc(n),webhookSecret:a,workflowSlug:l.slug}),[p,m]=await Promise.all([c,t]);await wc({baseUrl:e,browser:m,preconditionResult:p,runId:o,specData:i,streaming:s,webhookSecret:a})}var yc=de.object({preconditions:de.array(de.string()),requiresKeys:de.array(de.object({namespace:de.string(),preconditionName:de.string()})),slug:de.string()});function hc(e){let t=(e.workflows??[]).map(r=>{let n=It(At,r);return yc.parse(n)});return{engineBaseUrl:e.engineBaseUrl,workflows:t}}async function wc({baseUrl:e,browser:t,preconditionResult:r,runId:n,specData:o,streaming:i,webhookSecret:s}){let a=await ue({label:"Spec executed",fn:()=>jr({baseUrl:e,browser:t,cookies:r.cookies,extraVariables:r.data,observerTransport:r.apiUrl==null?void 0:{apiUrl:r.apiUrl,webhookSecret:s},onRecordingChunk:i.enqueueRecordingChunk,onStep:i.enqueueStep,runId:n,spec:o})});await bc({preconditionResult:r,webhookSecret:s}),await i.complete({statusOverride:void 0,steps:a,summary:void 0});let l=a.filter(p=>p.status==="passed").length,c=a.filter(p=>p.status==="failed").length;f.info("Run complete: %d passed, %d failed",l,c)}async function bc({preconditionResult:e,webhookSecret:t}){e.apiUrl!=null&&await $r({apiUrl:e.apiUrl,cookies:e.cookies,data:e.data,executed:e.executed,webhookSecret:t})}async function vc({config:e,workflowId:t}){let r=await g({config:e,document:pc,variables:{projectId:e.projectId}});if(r.project==null)throw new Error("Project not found");let o={...It(io,r.project),...e.engineUrl.length>0?{engineBaseUrl:e.engineUrl}:{}},s=(o.workflows??[]).find(l=>It(At,l).id===t);if(s==null)throw new Error(`Workflow ${t} not found`);let a=It(At,s);return{project:o,workflow:a}}function kc(e){if(e==null)throw new Error("Workflow has no spec");return xr.decode(e)}var Sc=de.coerce.number().int().positive().catch(dc);function Rc(){return Sc.parse(process.env.RIPPLO_RUN_TIMEOUT_MS)}async function Pc(e,t){let r,n=new Promise((o,i)=>{r=setTimeout(()=>{i(new Error(`Run timed out after ${String(t)}ms`))},t)});try{await Promise.race([e,n])}finally{r!=null&&clearTimeout(r)}}import{exec as Ac}from"child_process";import{createAuthClient as xc}from"better-auth/client";import{deviceAuthorizationClient as Cc}from"better-auth/client/plugins";function so({baseURL:e}){return xc({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[Cc()]})}import{z as Ur}from"zod";var Ec="https://ripplo.ai";function Q(){return jt().RIPPLO_SERVER_URL}function lo(){return jt().RIPPLO_PROJECT_ID}var Ic=Ur.object({RIPPLO_PROJECT_ID:Ur.string().min(1).optional(),RIPPLO_SERVER_URL:Ur.string().min(1).default(Ec)}),ao;function jt(){return ao??=Ic.parse(process.env),ao}var jc=5e3,co="ripplo-cli";async function uo({onDeviceCode:e,url:t}){let r=t??jt().RIPPLO_SERVER_URL,n=so({baseURL:r}),o=await n.device.code({client_id:co});if(o.error!=null)throw new Error(`Failed to request device code: ${o.error.error_description}`);let{device_code:i,user_code:s,verification_uri_complete:a}=o.data;e({userCode:s,verificationUrl:a}),Lc(a);let l=await Tc({authClient:n,deviceCode:i});return vr(l),l}async function Tc({authClient:e,deviceCode:t}){for(;;){await Oc(jc);let r=await e.device.token({client_id:co,device_code:t,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(r.data?.access_token!=null)return r.data.access_token;if(r.error==null)continue;if(!Nc(r.error.error))throw new Error(`Authorization failed: ${r.error.error_description}`)}}var $c=new Set(["authorization_pending","slow_down"]);function Nc(e){return $c.has(e)}function Oc(e){return new Promise(t=>{setTimeout(t,e)})}function Dc(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function Lc(e){let t=Dc();Ac(`${t} "${e}"`,()=>{})}function ke({serverUrl:e,token:t}){return{appUrl:"",cwd:process.cwd(),engineUrl:"",projectId:"",ripploServerUrl:e,token:t,webhookSecret:""}}import _c from"fs";import Uc from"path";function x(e){return _c.existsSync(Uc.join(e,".ripplo"))}var Fc=Vc(`
132
+ }
133
+ `);var pb=It(`
134
+ mutation StartAgentReviewWorker($reviewId: String!) {
135
+ startAgentReview(reviewId: $reviewId)
136
+ }
137
+ `);var mb=It(`
138
+ mutation CompleteAgentReviewWorker($reviewId: String!, $markdown: String!) {
139
+ completeAgentReview(reviewId: $reviewId, markdown: $markdown)
140
+ }
141
+ `);var fb=It(`
142
+ mutation FailAgentReviewWorker($reviewId: String!, $reason: String!) {
143
+ failAgentReview(reviewId: $reviewId, reason: $reason)
144
+ }
145
+ `);import{exec as Ec}from"child_process";import{createAuthClient as Rc}from"better-auth/client";import{deviceAuthorizationClient as Pc}from"better-auth/client/plugins";function ro({baseURL:e}){return Rc({baseURL:e,fetchOptions:{headers:{"User-Agent":"Ripplo CLI"}},plugins:[Pc()]})}import{z as Dr}from"zod";var xc="https://ripplo.ai";function Q(){return Tt().RIPPLO_SERVER_URL}function oo(){return Tt().RIPPLO_PROJECT_ID}var Cc=Dr.object({RIPPLO_PROJECT_ID:Dr.string().min(1).optional(),RIPPLO_SERVER_URL:Dr.string().min(1).default(xc)}),no;function Tt(){return no??=Cc.parse(process.env),no}var Ac=5e3,io="ripplo-cli";async function so({onDeviceCode:e,url:t}){let r=t??Tt().RIPPLO_SERVER_URL,n=ro({baseURL:r}),o=await n.device.code({client_id:io});if(o.error!=null)throw new Error(`Failed to request device code: ${o.error.error_description}`);let{device_code:i,user_code:s,verification_uri_complete:a}=o.data;e({userCode:s,verificationUrl:a}),Oc(a);let l=await Ic({authClient:n,deviceCode:i});return vr(l),l}async function Ic({authClient:e,deviceCode:t}){for(;;){await jc(Ac);let r=await e.device.token({client_id:io,device_code:t,grant_type:"urn:ietf:params:oauth:grant-type:device_code"});if(r.data?.access_token!=null)return r.data.access_token;if(r.error==null)continue;if(!$c(r.error.error))throw new Error(`Authorization failed: ${r.error.error_description}`)}}var Tc=new Set(["authorization_pending","slow_down"]);function $c(e){return Tc.has(e)}function jc(e){return new Promise(t=>{setTimeout(t,e)})}function Nc(){return process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open"}function Oc(e){let t=Nc();Ec(`${t} "${e}"`,()=>{})}function be({serverUrl:e,token:t}){return{appUrl:"",cwd:process.cwd(),engineUrl:"",projectId:"",ripploServerUrl:e,token:t,webhookSecret:""}}import Dc from"fs";import Lc from"path";function x(e){return Dc.existsSync(Lc.join(e,".ripplo"))}var Uc=_c(`
118
146
  query AuthViewer {
119
147
  currentUser {
120
148
  name
121
149
  email
122
150
  }
123
151
  }
124
- `);async function po(){let e=Q(),t=Z();if(t!=null){let s=await Vr({serverUrl:e,token:t});if(s!=null){process.stdout.write(`Already signed in as ${s.email}. Run \`ripplo auth logout\` to switch.
152
+ `);async function ao(){let e=Q(),t=Z();if(t!=null){let s=await Lr({serverUrl:e,token:t});if(s!=null){process.stdout.write(`Already signed in as ${s.email}. Run \`ripplo auth logout\` to switch.
125
153
  `);return}process.stdout.write(`Your saved session expired \u2014 let's sign you back in.
126
154
 
127
- `)}let r=await uo({url:e,onDeviceCode:s=>{process.stdout.write(`Opening your browser to finish sign-in.
155
+ `)}let r=await so({url:e,onDeviceCode:s=>{process.stdout.write(`Opening your browser to finish sign-in.
128
156
  `),process.stdout.write(`If it didn't open, visit: ${s.verificationUrl}
129
157
  `),process.stdout.write(`Verification code: ${s.userCode}
130
158
 
131
159
  `),process.stdout.write(`Waiting for you to approve...
132
- `)}}),n=await Vr({serverUrl:e,token:r}),o=x(process.cwd()),i=o?"Welcome back":"Welcome to Ripplo";process.stdout.write(`
160
+ `)}}),n=await Lr({serverUrl:e,token:r}),o=x(process.cwd()),i=o?"Welcome back":"Welcome to Ripplo";process.stdout.write(`
133
161
  `),process.stdout.write(n==null?`${i}.
134
- `:`${i}, ${Hc(n)}.
135
- `),process.stdout.write(`Session saved to ${ye("token")}.
136
- `),o||(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"))}function Hc(e){let t=e.name.trim().split(/\s+/)[0];return t!=null&&t.length>0?t:e.email}async function Vr({serverUrl:e,token:t}){try{let n=(await g({config:ke({serverUrl:e,token:t}),document:Fc,variables:void 0})).currentUser;return n==null?null:{email:n.email,name:n.name}}catch{return null}}async function mo(){let e=Z();e==null&&(process.stdout.write("Not authenticated. Run `ripplo auth login`.\n"),process.exit(1));let t=Q(),r=await Vr({serverUrl:t,token:e});r==null&&(process.stdout.write("Token rejected. Run `ripplo auth login` to re-authenticate.\n"),process.exit(1)),process.stdout.write(`Authenticated as ${r.name} <${r.email}> (${t})
137
- `)}function fo(){if(!kr()){process.stdout.write(`No token to remove.
138
- `);return}process.stdout.write(`Removed ${ye("token")}
139
- `)}import Hr from"fs/promises";import Br from"path";import{z as Tt}from"zod";import{z as go}from"zod";import{z as Oe}from"zod";import{z as d}from"zod";import{z as Se}from"zod";import{z as vo}from"zod";import{z as A}from"zod";import{z as Y}from"zod";import{z as Fr}from"zod";import{z as V}from"zod";var Bc=Tt.object({__codec:Tt.string().min(1),data:Tt.unknown(),version:Tt.number().int().positive()}),Wc=class extends Error{codec;currentVersion;gotVersion;constructor(e){super(`Unsupported ${e.codec} version ${String(e.gotVersion)} (current ${String(e.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`),this.name="CodecVersionError",this.codec=e.codec,this.currentVersion=e.currentVersion,this.gotVersion=e.gotVersion}},Mc=class extends Error{constructor(e){super(`Codec mismatch: expected "${e.expected}", got "${e.got}"`),this.name="CodecMismatchError"}};function qr(e){return ho({legacy:void 0,migrators:[],name:e,schemas:[]})}function qc(e,t){let r=JSON.parse(t);return e.decode(r)}function ho(e){return{initial:t=>Wr(t,{...e,schemas:[t]}),legacy:t=>ho({...e,legacy:t})}}function Wr(e,t){return{build:()=>zc(e,t),legacy:r=>Wr(e,{...t,legacy:r}),upgrade:(r,n)=>{let o=n;return Wr(r,{...t,migrators:[...t.migrators,o],schemas:[...t.schemas,r]})}}}function zc(e,t){let r=t.schemas.length;return{currentVersion:r,name:t.name,decode:n=>Gc(e,t,n),encode:n=>({__codec:t.name,data:n,version:r})}}function Gc(e,t,r){let{data:n,version:o}=Kc(t,r),i=wo(t,n,o);return e.parse(i)}function Kc(e,t){let r=Bc.safeParse(t);if(r.success){if(r.data.__codec!==e.name)throw new Mc({expected:e.name,got:r.data.__codec});return{data:r.data.data,version:r.data.version}}if(e.legacy!=null&&e.legacy.detect(t))return{data:t,version:e.legacy.assumedVersion};throw new Error(`Cannot decode "${e.name}": value is not a codec envelope and no legacy detector matched.`)}function wo(e,t,r){let n=e.schemas.length;if(r>n)throw new Wc({codec:e.name,currentVersion:n,gotVersion:r});if(r===n)return t;let o=e.schemas[r-1];if(o==null)throw new Error(`Codec "${e.name}" missing schema for v${String(r)}; cannot migrate.`);let i=o.parse(t),s=e.migrators[r-1];if(s==null)throw new Error(`Codec "${e.name}" missing migrator v${String(r)} \u2192 v${String(r+1)}.`);return wo(e,s(i),r+1)}var bo=Oe.object({depends:Oe.array(Oe.string().min(1)).optional().describe("Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."),description:Oe.string().min(1).describe("Human-readable description of what this precondition ensures"),returns:Oe.array(Oe.string().min(1)).optional().describe("Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition.")}).describe("A named precondition declared at the graph level. States reference these by name."),Jc=Se.object({by:Se.literal("testId"),value:Se.string().min(1)}),Qc=Se.object({by:Se.literal("role"),name:Se.string().optional(),role:Se.string().min(1)}),P=Se.discriminatedUnion("by",[Jc,Qc]),nt=vo.enum(["equals","notEquals","contains","startsWith","endsWith","matches"]),Yc=vo.enum(["equals","notEquals","greaterThan","greaterThanOrEqual","lessThan","lessThanOrEqual"]),Xc=A.object({type:A.literal("static"),value:A.union([A.string(),A.number(),A.boolean()])}),$t=A.object({name:A.string().min(1),type:A.literal("variable")}),Zc=A.discriminatedUnion("type",[Xc,$t]),ie=A.discriminatedUnion("type",[A.object({type:A.literal("static"),value:A.string()}),$t]),eu=A.discriminatedUnion("type",[A.object({type:A.literal("static"),value:A.number().int().nonnegative()}),$t]),tu=A.discriminatedUnion("type",[A.object({type:A.literal("static"),value:A.union([A.string(),A.number(),A.boolean()])}),$t]),ru=Y.discriminatedUnion("type",[Y.object({default:Y.string().optional(),type:Y.literal("string")}),Y.object({default:Y.number().optional(),type:Y.literal("number")}),Y.object({default:Y.boolean().optional(),type:Y.literal("boolean")}),Y.object({key:Y.string().min(1),type:Y.literal("env")})]),v={id:d.string().min(1).max(200),label:d.string().max(500).optional(),next:d.string().max(200).optional(),uiOnly:d.boolean().optional()},yo=500,nu=d.object({...v,type:d.literal("goto"),url:ie}),ou=d.object({...v,locator:P,type:d.literal("click")}),iu=d.object({...v,locator:P,type:d.literal("fill"),value:ie}),su=d.object({...v,locator:P,type:d.literal("select"),value:ie}),au=d.object({...v,locator:P,type:d.literal("hover")}),lu=d.object({...v,key:d.string().min(1),locator:P.optional(),type:d.literal("press")}),cu=d.object({...v,locator:P,type:d.literal("check")}),uu=d.object({...v,locator:P,type:d.literal("uncheck")}),du=d.object({...v,height:d.number().int().positive(),type:d.literal("setViewport"),width:d.number().int().positive()}),pu=d.object({...v,message:d.string().min(1),type:d.literal("fail")}),mu=d.object({...v,type:d.literal("setVariable"),value:Zc,variable:d.string().min(1)}),fu=d.object({...v,locator:P,type:d.literal("extractText"),variable:d.string().min(1)}),gu=d.object({...v,files:d.array(d.string()).min(1),locator:P,type:d.literal("upload")}),yu=d.object({...v,locator:P,type:d.literal("dblclick")}),hu=d.object({...v,source:P,target:P,type:d.literal("drag")}),wu=d.object({...v,locator:P,type:d.literal("scrollIntoView")}),bu=d.object({...v,locator:P,type:d.literal("type"),value:ie}),vu=d.object({...v,locator:P,type:d.literal("focus")}),ku=d.object({...v,locator:P,type:d.literal("clear")}),Su=d.object({...v,locator:P,type:d.literal("rightClick")}),Ru=d.object({...v,action:d.enum(["accept","dismiss"]),promptText:d.string().optional(),type:d.literal("handleDialog")}),Pu=d.object({...v,action:d.enum(["read","write"]),type:d.literal("clipboard"),value:ie.optional(),variable:d.string().min(1).optional()}),xu=d.object({...v,permission:d.string().min(1),state:d.enum(["granted","prompt"]),type:d.literal("setPermission")}),Cu=d.object({...v,locator:P,type:d.literal("assertVisible")}),Eu=d.object({...v,locator:P,type:d.literal("assertNotVisible")}),Iu=d.object({...v,expected:ie,locator:P,operator:nt,type:d.literal("assertText")}),Au=d.object({...v,expected:ie,operator:nt,type:d.literal("assertUrl")}),ju=d.object({...v,expected:eu,locator:P,operator:Yc,type:d.literal("assertCount")}),Tu=d.object({...v,expected:ie,locator:P,operator:nt,type:d.literal("assertValue")}),$u=d.object({...v,attribute:d.string().min(1),expected:ie,locator:P,operator:nt,type:d.literal("assertAttribute")}),Nu=d.object({...v,locator:P,type:d.literal("assertEnabled")}),Ou=d.object({...v,locator:P,type:d.literal("assertDisabled")}),Du=d.object({...v,expected:ie,operator:nt,type:d.literal("assertTitle")}),Lu=d.object({...v,locator:P,type:d.literal("assertChecked")}),_u=d.object({...v,locator:P,type:d.literal("assertNotChecked")}),Uu=d.object({...v,locator:P,type:d.literal("assertFocused")}),Vu=d.object({...v,locator:P,type:d.literal("assertNotFocused")}),Fu=d.object({...v,budget:d.enum(["fast","slow","async"]),observer:d.string().min(1).max(200),params:d.record(d.string().max(200),tu),type:d.literal("assertObserver")}),Hu=d.discriminatedUnion("type",[nu,ou,iu,su,au,lu,cu,uu,Cu,Eu,Iu,Au,ju,Tu,$u,Nu,Ou,du,pu,mu,fu,gu,yu,hu,wu,bu,vu,ku,Su,Ru,Pu,xu,Du,Lu,_u,Uu,Vu,Fu]),ko=d.object({entryNode:d.string().min(1).max(200),nodes:d.record(d.string().max(200),Hu).refine(e=>Object.keys(e).length<=yo,`Workflow has more than ${String(yo)} nodes`),uiOnly:d.boolean().optional(),variableNamespaces:d.record(d.string().max(200),d.string().max(500)).optional(),variables:d.record(d.string().max(200),ru).optional()}),Bu=go.record(go.string().max(200),bo),Wu={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&"entryNode"in e&&"nodes"in e},Mu={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&!Array.isArray(e)&&!("__codec"in e)},mv=qr("workflow-spec").legacy(Wu).initial(ko).build(),fv=qr("precondition-map").legacy(Mu).initial(Bu).build(),qu=["fast","slow","async"],zu=Fr.object({budget:Fr.enum(qu).describe("Polling budget tier: fast | slow | async"),description:Fr.string().min(1).describe("Human-readable description of what this observer checks")}).describe("A named backend state observer. Tests assert against it with assert.backend(observer, params). Implementation lives on the user's server."),se=".ripplo/ripplo.lock",Gu=5e3,Ku=V.record(V.string().max(200),V.string().max(200)),Ju=V.object({coverage:V.array(V.string().max(500)).max(2e3).default([]),expectedOutcome:V.string().max(2e3),name:V.string().max(200),preconditions:V.array(V.string().max(200)).max(1e3),requiresKeys:Ku,slug:V.string().max(200),spec:ko}),Qu=V.object({observers:V.record(V.string().max(200),zu),preconditions:V.record(V.string().max(200),bo),tests:V.array(Ju).max(Gu)}),So=qr("ripplo-lockfile").initial(Qu).build();function Ro(e){return{observers:e.observers,preconditions:e.preconditions,tests:e.tests.filter(t=>t.implemented).map(t=>({coverage:[...t.coverage],expectedOutcome:t.expectedOutcome,name:t.name,preconditions:[...t.preconditions],requiresKeys:{...t.requiresKeys},slug:t.slug,spec:t.spec}))}}function Mr(e){let t=So.encode(e);return`${JSON.stringify(t,Zu(t),2)}
140
- `}async function Nt({cwd:e}){let t=Br.join(e,se),r=await Yu(t);return r==null?null:qc(So,r)}async function ae({cwd:e,result:t}){let r=Ro(t),n=Br.join(e,se);await Hr.mkdir(Br.dirname(n),{recursive:!0}),await Hr.writeFile(n,Mr(r),"utf8")}function Ot({compiled:e,existing:t}){if(t==null)return"missing";let r=Mr(Ro(e)),n=Mr(t);return r===n?"match":"stale"}async function Yu(e){try{return await Hr.readFile(e,"utf8")}catch(t){if(Xu(t)&&t.code==="ENOENT")return null;throw t}}function Xu(e){return e instanceof Error&&"code"in e}function Zu(e){return function(r,n){return n===e||!ed(n)?n:Object.fromEntries(Object.entries(n).toSorted(([o],[i])=>o.localeCompare(i)))}}function ed(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}import ot from"fs";import _t from"path";var zr=["**/src/**","**/app/**","**/apps/**","**/pages/**","**/routes/**","**/components/**","**/server/**","**/api/**","**/backend/**","**/features/**","**/modules/**","**/views/**","**/ui/**","**/hooks/**","**/contexts/**","**/providers/**","**/controllers/**","**/handlers/**","**/resolvers/**","**/services/**","**/middleware/**","**/lib/**"],Re=["**/*.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/**","**/scripts/**"];function Po(e){return e.data}function xo(e){return{as(t){return{data:{label:t,node:e}}}}}function Dt(e){let t=e.getPreconditions(),r=e.getObservers(),n=e.getTests();td(n);let o={};t.forEach(a=>{o[a.name]={depends:[...a.dependsOn],description:a.description,returns:[...a.returns]}});let i={};r.forEach(a=>{i[a.name]={budget:a.budget,description:a.description}});let s=n.map(a=>rd(a,t));return{observers:i,preconditions:o,tests:s}}function td(e){let t=new Map;e.forEach(r=>{let n=t.get(r.id);if(n!=null)throw new Error(`Duplicate test id "${r.id}" used by "${n}" and "${r.name}"`);t.set(r.id,r.name)})}function rd(e,t){let r=e.id,{accessedKeys:n,vars:o}=od(e.requiresKeys),i=e.startsAtFn==null?void 0:e.startsAtFn(o),s=e.stepsFn==null?[]:e.stepsFn(o),a=i==null?s:[nd(i),...s],l=id(a,n,e.requiresKeys,e.uiOnly),c=[];Object.keys(e.requiresKeys).length>0&&n.size===0&&e.implemented&&c.push("Test requires preconditions but never references their data \u2014 destructure and use precondition data in steps()");let m=ad(e.requires,t);return{additionalChecks:[],coverage:e.coverage,description:e.description,expectedOutcome:e.expectedOutcome,implemented:e.implemented,name:e.name,preconditions:m,requiresKeys:{...e.requiresKeys},slug:r,spec:l,warnings:c}}function nd(e){return xo({type:"goto",url:{type:"static",value:e}}).as(`navigate to ${e}`)}function od(e){let t=new Set,r={};return Object.keys(e).forEach(n=>{r[n]=new Proxy({},{get(o,i){if(typeof i=="string"){let s=`${n}.${i}`;return t.add(s),`{{${s}}}`}}})}),{accessedKeys:t,vars:r}}function id(e,t,r,n){let o={};e.forEach((a,l)=>{let c=`step-${String(l)}`,p=l<e.length-1?`step-${String(l+1)}`:void 0;o[c]=sd(a,c,p)});let i={};t.forEach(a=>{i[a]={default:`test-${a}`,type:"string"}});let s={...r};return{entryNode:"step-0",nodes:o,uiOnly:n,variableNamespaces:s,variables:i}}function sd(e,t,r){let{label:n,node:o}=Po(e);return{...o,id:t,label:n,next:r}}function ad(e,t){let r=new Map(t.map(s=>[s.name,s])),n=[],o=new Set;function i(s){o.has(s)||(o.add(s),r.get(s)?.dependsOn.forEach(a=>{i(a)}),n.push(s))}return e.forEach(s=>{i(s)}),n}import{Webhook as xv,WebhookVerificationError as Cv}from"standardwebhooks";import{z as R}from"zod";var Iv=R.object({preconditions:R.array(R.string().min(1))}),Av=R.object({data:R.record(R.string(),R.record(R.string(),R.string())),preconditions:R.array(R.string().min(1))}),jv=R.object({observer:R.string().min(1).max(200),params:R.record(R.string().max(200),R.union([R.string(),R.number(),R.boolean()]))}),ld=R.discriminatedUnion("kind",[R.object({kind:R.literal("pass")}),R.object({kind:R.literal("retry"),reason:R.string()}),R.object({kind:R.literal("fail"),reason:R.string()})]),Tv=R.object({error:R.string().optional(),outcome:ld.optional(),success:R.boolean()});function De(e){let t=[];return e.tests.forEach(r=>{let n=cd(r),o=i=>{t.push({...i,test:r.slug})};Td.forEach(i=>{i(n,r,o)})}),{diagnostics:t}}function cd(e){let t=[],r=e.spec.entryNode,n=new Set;for(;r!=null&&!n.has(r);){n.add(r);let o=e.spec.nodes[r];if(o==null)break;t.push(o),r=o.next}return t}function ud(e,t,r){e.forEach(n=>{n.type==="assertText"&&"operator"in n&&n.operator!=="equals"&&r({message:`${n.type} uses operator "${n.operator}" \u2014 only "equals" is allowed for determinism`,rule:"exact-text-match",step:n.label??n.id})})}function dd(e,t,r){Object.keys(t.spec.variables??{}).length>0&&e.forEach(o=>{if(o.type==="fill"&&Eo(o.value)){let i=o.value.value;!Io(i)&&wd(i)&&r({message:`fill() uses hardcoded value "${i}" \u2014 consider using precondition data via {{namespace.key}}`,rule:"no-hardcoded-data",step:o.label??o.id})}})}function pd(e,t,r){if(Object.keys(t.spec.variables??{}).length===0)return;e.some(i=>JSON.stringify(i).includes("{{"))||r({message:"Test requires preconditions but steps() never references precondition data \u2014 destructure and use it",rule:"prefer-precondition-data",step:void 0})}function md(e,t,r){e.forEach(n=>{(n.label==null||n.label.length===0)&&r({message:`Step "${n.id}" lacks .as("...") label \u2014 every step must be labeled`,rule:"missing-label",step:n.id})})}function fd(e,t,r){let n=new Map;e.forEach(o=>{if(o.label==null)return;let i=n.get(o.label);i==null?n.set(o.label,o.id):r({message:`Duplicate label "${o.label}" \u2014 also used by ${i}`,rule:"no-duplicate-labels",step:o.label})})}function gd(e,t,r){let n=0;e.forEach((o,i)=>{if(!(i===0&&o.type==="goto")){if(Le(o)){n=0;return}n++,n===3&&r({message:"3+ consecutive actions without an assertion \u2014 add verification between actions",rule:"assert-after-action",step:o.label??o.id})}})}function yd(e,t,r){if(e.length===0)return;let n=e.at(-1);n!=null&&!Le(n)&&r({message:"Last step is an action, not an assertion \u2014 expectedOutcome should be verified by assertions at the end",rule:"assert-matches-outcome",step:n.label??n.id})}function hd(e,t,r){t.implemented&&e.length===0&&r({message:"Test has zero steps",rule:"no-empty-steps",step:void 0})}function Eo(e){return!(typeof e!="object"||e==null||!("type"in e)||e.type!=="static"||!("value"in e)||typeof e.value!="string")}function Io(e){return e.includes("{{")}function wd(e){return e.includes("@")||/\b[a-f0-9]{8,}\b/.test(e)||/^(test|ripplo|example|sample|demo)/i.test(e)}function Le(e){return e.type.startsWith("assert")}var bd=new Set(["assertText","assertValue","assertCount","assertUrl","assertNotVisible","assertChecked","assertNotChecked","assertAttribute","assertDisabled","assertEnabled"]);function vd(e){return bd.has(e.type)}function kd(e,t,r){!t.implemented||e.length===0||e.some(n=>Le(n))||r({message:"Test has zero assertion steps \u2014 cannot verify expectedOutcome. Add assert.* steps.",rule:"no-assertions",step:void 0})}function Sd(e,t,r){if(!t.implemented||e.length<=3)return;let n=e.filter(i=>Le(i)).length;if(n===0)return;let o=n/e.length;if(o<.15){let i=Math.round(o*100);r({message:`Only ${String(n)}/${String(e.length)} steps are assertions (${String(i)}%) \u2014 add more verification between actions`,rule:"low-assertion-ratio",step:void 0})}}function Gr(e){if(!("locator"in e)||e.locator==null)return;let t=e.locator;return t.by==="role"?`role:${t.role}:${t.name??""}`:`testId:${t.value}`}function Rd(e,t,r){e.forEach((n,o)=>{if(n.type!=="click")return;let i=Gr(n);if(i==null)return;let s=e.slice(o+1,o+4);s.find(c=>c.type==="assertVisible"&&Gr(c)===i)==null||s.some(c=>vd(c)||Le(c)&&Gr(c)!==i)||r({message:`click "${n.label??n.id}" is followed only by assert.visible on the same locator \u2014 verifies nothing about the click's effect`,rule:"tautological-post-click-assert",step:n.label??n.id})})}var Pd=new Set(["the","is","a","an","and","or","of","to","in","on","at","for","that","this","with","be","are","was","were","it","its","as","by","from","after","before","should","will","can","still","but","not","no","so","if","then","than","user","users","page","view","shows","show","see","click","clicks","clicked","clickable","visible","appears","appear","displayed","stays","remains"]);function Kr(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(t=>t.length>=3&&!Pd.has(t))}function xd(e){let t=e.label==null?[]:Kr(e.label);if(!("locator"in e)||e.locator==null)return t;let r=e.locator,n=r.by==="role"?r.name??"":r.value;return[...t,...Kr(n)]}function Cd(e,t,r){if(!t.implemented||e.length===0)return;let n=new Set(Kr(t.expectedOutcome));if(n.size===0)return;let o=e.filter(s=>Le(s));if(o.length===0)return;o.some(s=>xd(s).some(a=>n.has(a)))||r({message:`No assertion references any keyword from expectedOutcome (${[...n].join(", ")}) \u2014 the outcome may not actually be verified`,rule:"expected-outcome-keyword-coverage",step:void 0})}var Ed=/save|submit|create|delete|remove|send|invite|update|confirm|publish|apply|pay|subscribe|upgrade|cancel|archive|rename/i;function Co(e){if(e.type==="upload")return!0;if(e.type!=="click")return!1;let t=e.locator,r=t.by==="role"?t.name??"":t.value;return Ed.test(r)}function Id(e,t,r){!t.implemented||t.spec.uiOnly===!0||e.forEach((n,o)=>{if(!Co(n)||"uiOnly"in n&&n.uiOnly===!0)return;let i=e.slice(o+1),s=i.findIndex(p=>Co(p)),a=s===-1?i.length:s;i.slice(0,a).some(p=>p.type==="assertObserver")||r({message:`"${n.label??n.id}" looks like it mutates backend state but no assert.backend(...) observer verifies it \u2014 the test can pass while the server is broken. You need to add an observer that checks the persisted result (see .ripplo/observers/ for examples). Only if this action truly does NOT change any server state (pure client-side interaction, presentation toggle, etc.) should you fall back to marking the step { uiOnly: true } to silence this rule.`,rule:"mutation-without-observer-coverage",step:n.label??n.id})})}function Ad(e,t,r){Object.keys(t.spec.variables??{}).length!==0&&e.forEach(o=>{if(o.type!=="assertObserver")return;let i=Object.entries(o.params);i.length===0||i.some(([,a])=>Eo(a)&&Io(a.value))||r({message:`assert.backend "${o.label??o.id}" passes only hardcoded params while the test defines precondition variables \u2014 observer assertions should reference {{namespace.key}} so they match the actual precondition data`,rule:"observer-params-reference-variables",step:o.label??o.id})})}function jd(e,t,r){let n=new Set(Object.keys(t.spec.variables??{})),o=/\{\{([^{}]+?)\}\}/g;e.forEach(i=>{let a=[...JSON.stringify(i).matchAll(o)],l=[...new Set(a.map(m=>m[1]).filter(m=>m!=null&&!n.has(m)))];if(l.length===0)return;let c=l.map(m=>`{{${m}}}`).join(", "),p=[...new Set(l.map(m=>m.split(".")[0]??m))].join(", ");r({message:`"${i.label??i.id}" contains literal template string(s) ${c} \u2014 destructure the proxy in .steps(({ ${p} }) => \u2026) and pass the proxy value (e.g. \`${l[0]??""}\`) directly. Writing the template as a string bypasses type-checking and silently accepts typos.`,rule:"no-literal-template-strings",step:i.label??i.id})})}var Td=[ud,dd,jd,pd,md,fd,gd,yd,hd,kd,Sd,Rd,Cd,Id,Ad];import rp from"picomatch";import{execFileSync as $d,spawnSync as Nd}from"child_process";function q(e,t){return $d("git",[...e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function Ao(e,t){let r=Nd("git",["show",e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]});if(r.status===0)return r.stdout;if(r.status===128)return null;throw new Error(`git show ${e} failed (${String(r.status)}): ${r.stderr}`)}import h from"typescript";import*as jo from"remeda";var To=[["onClick","click"],["onContextMenu","click"],["onKeyDown","click"],["onKeyPress","click"],["onKeyUp","click"],["onSelect","click"],["onOpenChange","click"],["onPress","click"],["onActivate","click"],["onToggle","click"],["onOpen","click"],["onClose","click"],["onDismiss","click"],["onConfirm","click"],["onCancel","click"],["onApply","click"],["onClear","click"],["onSubmit","submit"],["onReset","submit"],["onDrop","drag"],["onDragEnd","drag"],["onDragEnter","drag"],["onDragLeave","drag"],["onDragOver","drag"],["onDragStart","drag"],["onValueChange","select"],["onSelectionChange","select"],["onChange","input"],["onCheckedChange","input"],["onInput","input"]],Od=new Map([["button","click"],["link","navigate"],["textbox","input"],["searchbox","input"],["combobox","select"],["listbox","select"]]),Dd=[[/(?:^|[a-z])Button$/,"click"],[/(?:^|[a-z])(?:Link|NavLink|Anchor)$/,"navigate"],[/(?:^|[a-z])(?:Textarea|TextArea|Input|Field|SearchBox)$/,"input"],[/(?:^|[a-z])(?:Select|Combobox|ComboBox|Dropdown)$/,"select"],[/(?:^|[a-z])Form$/,"submit"]];function Lt({filePath:e,source:t}){let r=h.createSourceFile(e,t,h.ScriptTarget.Latest,!0,h.ScriptKind.TSX);return r.statements.flatMap(n=>Ld({filePath:e,sf:r,stmt:n}))}function Ld({filePath:e,sf:t,stmt:r}){let n=Ud(r);if(n==null||_d(n))return[];let o=$o(r,t).map(i=>({id:`${e}#${n}.${i.kind}[${i.label}]`,line:i.line}));return jo.uniqueBy(o,i=>i.id)}function _d(e){return e.endsWith("Fragment")||e.endsWith("Skeleton")||/^use[A-Z]/.test(e)||/^[A-Z][A-Z0-9_]*$/.test(e)}function Ud(e){if(h.isExportAssignment(e))return Vd(e.expression);if(!Fd(e))return null;if(h.isFunctionDeclaration(e)||h.isClassDeclaration(e))return e.name?.text??null;if(h.isVariableStatement(e)){let t=e.declarationList.declarations[0];return t!=null&&h.isIdentifier(t.name)?t.name.text:null}return null}function Vd(e){return h.isIdentifier(e)?e.text:(h.isFunctionExpression(e)||h.isClassExpression(e))&&e.name!=null?e.name.text:"default"}function Fd(e){if(!h.canHaveModifiers(e))return!1;let t=h.getModifiers(e);return t!=null&&t.some(r=>r.kind===h.SyntaxKind.ExportKeyword)}function $o(e,t){let r=Hd(e,t),n=e.getChildren(t).flatMap(o=>$o(o,t));return r==null?n:[r,...n]}function Hd(e,t){let r=Bd(e);if(r==null)return null;let n=Wd(r);if(n==null)return null;let o=Kd(r,n);return o==null?null:{kind:n,label:o,line:tp(t,e.getStart(t))}}function Bd(e){return h.isJsxSelfClosingElement(e)&&h.isIdentifier(e.tagName)?{attrs:e.attributes,children:[],tag:e.tagName.text}:h.isJsxElement(e)&&h.isIdentifier(e.openingElement.tagName)?{attrs:e.openingElement.attributes,children:e.children,tag:e.openingElement.tagName.text}:null}function Wd({attrs:e,tag:t}){let r=le(e,"role");return r!=null?Od.get(r)??null:qd(t,e)??zd(t)??Gd(e)}var Md=new Map([["button",()=>"click"],["a",e=>le(e,"href")==null?null:"navigate"],["input",e=>le(e,"type")==="file"?"upload":"input"],["textarea",()=>"input"],["select",()=>"select"],["form",()=>"submit"]]);function qd(e,t){return Md.get(e)?.(t)??null}function zd(e){return/^[A-Z]/.test(e)?Dd.find(([t])=>t.test(e))?.[1]??null:null}function Gd(e){return To.find(([t])=>Yd(e,t))?.[1]??null}function Kd({attrs:e,children:t,tag:r},n){return n==="navigate"?le(e,"href")??le(e,"to"):le(e,"aria-label")??le(e,"label")??le(e,"title")??le(e,"name")??le(e,"placeholder")??(Jd(r)?void 0:ep(t))??Qd(e,n)}function Jd(e){return e==="input"||e==="textarea"||e==="select"}function Qd(e,t){let r=To.find(([,o])=>o===t)?.[0];if(r==null)return;let n=Xd(e,r);if(n!=null){if(h.isIdentifier(n))return n.text;if(h.isArrowFunction(n)||h.isFunctionExpression(n))return No(n.body)}}function No(e){if(h.isCallExpression(e)){let t=e.expression;return h.isIdentifier(t)?t.text:h.isPropertyAccessExpression(t)?t.name.text:void 0}return h.forEachChild(e,No)}function Yd(e,t){return e.properties.some(r=>h.isJsxAttribute(r)&&h.isIdentifier(r.name)&&r.name.text===t)}function Oo(e,t){return e.properties.find(r=>h.isJsxAttribute(r)&&h.isIdentifier(r.name)&&r.name.text===t)}function le(e,t){let r=Oo(e,t);return r==null?void 0:Zd(r.initializer)}function Xd(e,t){let r=Oo(e,t);if(!(r?.initializer==null||!h.isJsxExpression(r.initializer)))return r.initializer.expression}function Zd(e){if(e==null)return"";if(h.isStringLiteral(e))return e.text;if(h.isJsxExpression(e))return Lo(e.expression)}function ep(e){let t=e.flatMap(r=>Do(r)).join(" ").trim();return t.length===0?void 0:t}function Do(e){if(h.isJsxText(e)){let t=e.text.trim();return t.length===0?[]:[t]}if(h.isJsxExpression(e)){let t=Lo(e.expression);return t==null?[]:[t]}return h.isJsxElement(e)?e.children.flatMap(t=>Do(t)):[]}function Lo(e){if(e!=null){if(h.isStringLiteral(e)||h.isNoSubstitutionTemplateLiteral(e))return e.text;if(h.isCallExpression(e)&&e.arguments.length===1){let t=e.arguments[0];if(t!=null&&(h.isStringLiteral(t)||h.isNoSubstitutionTemplateLiteral(t)))return t.text}}}function tp(e,t){return e.getLineAndCharacterOfPosition(t).line+1}var np=_t.join(".ripplo","coverage.d.ts");function _e({cwd:e}){op({cwd:e,ignorePaths:Re})}function op({cwd:e,ignorePaths:t}){let r=Ue({cwd:e,ignorePaths:t});ap({content:sp(r),cwd:e})}function Ue({cwd:e,ignorePaths:t}){let n=ip({cwd:e,ignorePaths:t}).flatMap(o=>Jr({cwd:e,file:o}));return[...new Set(n)].sort((o,i)=>o.localeCompare(i))}function ip({cwd:e,ignorePaths:t}){let r=q(["ls-files","--cached","--others","--exclude-standard","-z"],e),n=rp([...t]);return r.split("\0").filter(o=>o.length>0&&/\.(tsx|jsx)$/.test(o)).filter(o=>!n(o)).filter(o=>ot.existsSync(_t.join(e,o)))}function Jr({cwd:e,file:t}){let r=ot.readFileSync(_t.join(e,t),"utf8");return Lt({filePath:t,source:r}).map(n=>n.id)}function sp(e){let t=`// GENERATED \u2014 do not edit
162
+ `:`${i}, ${Vc(n)}.
163
+ `),process.stdout.write(`Session saved to ${fe("token")}.
164
+ `),o||(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"))}function Vc(e){let t=e.name.trim().split(/\s+/)[0];return t!=null&&t.length>0?t:e.email}async function Lr({serverUrl:e,token:t}){try{let n=(await g({config:be({serverUrl:e,token:t}),document:Uc,variables:void 0})).currentUser;return n==null?null:{email:n.email,name:n.name}}catch{return null}}async function lo(){let e=Z();e==null&&(process.stdout.write("Not authenticated. Run `ripplo auth login`.\n"),process.exit(1));let t=Q(),r=await Lr({serverUrl:t,token:e});r==null&&(process.stdout.write("Token rejected. Run `ripplo auth login` to re-authenticate.\n"),process.exit(1)),process.stdout.write(`Authenticated as ${r.name} <${r.email}> (${t})
165
+ `)}function co(){if(!kr()){process.stdout.write(`No token to remove.
166
+ `);return}process.stdout.write(`Removed ${fe("token")}
167
+ `)}import Ur from"fs/promises";import Vr from"path";import{z as $t}from"zod";import{z as uo}from"zod";import{z as Oe}from"zod";import{z as u}from"zod";import{z as ve}from"zod";import{z as yo}from"zod";import{z as I}from"zod";import{z as Y}from"zod";import{z as _r}from"zod";import{z as U}from"zod";var Fc=$t.object({__codec:$t.string().min(1),data:$t.unknown(),version:$t.number().int().positive()}),Hc=class extends Error{codec;currentVersion;gotVersion;constructor(e){super(`Unsupported ${e.codec} version ${String(e.gotVersion)} (current ${String(e.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`),this.name="CodecVersionError",this.codec=e.codec,this.currentVersion=e.currentVersion,this.gotVersion=e.gotVersion}},Wc=class extends Error{constructor(e){super(`Codec mismatch: expected "${e.expected}", got "${e.got}"`),this.name="CodecMismatchError"}};function Wr(e){return mo({legacy:void 0,migrators:[],name:e,schemas:[]})}function Bc(e,t){let r=JSON.parse(t);return e.decode(r)}function mo(e){return{initial:t=>Fr(t,{...e,schemas:[t]}),legacy:t=>mo({...e,legacy:t})}}function Fr(e,t){return{build:()=>Mc(e,t),legacy:r=>Fr(e,{...t,legacy:r}),upgrade:(r,n)=>{let o=n;return Fr(r,{...t,migrators:[...t.migrators,o],schemas:[...t.schemas,r]})}}}function Mc(e,t){let r=t.schemas.length;return{currentVersion:r,name:t.name,decode:n=>qc(e,t,n),encode:n=>({__codec:t.name,data:n,version:r})}}function qc(e,t,r){let{data:n,version:o}=zc(t,r),i=fo(t,n,o);return e.parse(i)}function zc(e,t){let r=Fc.safeParse(t);if(r.success){if(r.data.__codec!==e.name)throw new Wc({expected:e.name,got:r.data.__codec});return{data:r.data.data,version:r.data.version}}if(e.legacy!=null&&e.legacy.detect(t))return{data:t,version:e.legacy.assumedVersion};throw new Error(`Cannot decode "${e.name}": value is not a codec envelope and no legacy detector matched.`)}function fo(e,t,r){let n=e.schemas.length;if(r>n)throw new Hc({codec:e.name,currentVersion:n,gotVersion:r});if(r===n)return t;let o=e.schemas[r-1];if(o==null)throw new Error(`Codec "${e.name}" missing schema for v${String(r)}; cannot migrate.`);let i=o.parse(t),s=e.migrators[r-1];if(s==null)throw new Error(`Codec "${e.name}" missing migrator v${String(r)} \u2192 v${String(r+1)}.`);return fo(e,s(i),r+1)}var go=Oe.object({depends:Oe.array(Oe.string().min(1)).optional().describe("Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."),description:Oe.string().min(1).describe("Human-readable description of what this precondition ensures"),returns:Oe.array(Oe.string().min(1)).optional().describe("Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition.")}).describe("A named precondition declared at the graph level. States reference these by name."),Gc=ve.object({by:ve.literal("testId"),value:ve.string().min(1)}),Kc=ve.object({by:ve.literal("role"),name:ve.string().optional(),role:ve.string().min(1)}),P=ve.discriminatedUnion("by",[Gc,Kc]),rt=yo.enum(["equals","notEquals","contains","startsWith","endsWith","matches"]),Jc=yo.enum(["equals","notEquals","greaterThan","greaterThanOrEqual","lessThan","lessThanOrEqual"]),Qc=I.object({type:I.literal("static"),value:I.union([I.string(),I.number(),I.boolean()])}),jt=I.object({name:I.string().min(1),type:I.literal("variable")}),Yc=I.discriminatedUnion("type",[Qc,jt]),ie=I.discriminatedUnion("type",[I.object({type:I.literal("static"),value:I.string()}),jt]),Xc=I.discriminatedUnion("type",[I.object({type:I.literal("static"),value:I.number().int().nonnegative()}),jt]),Zc=I.discriminatedUnion("type",[I.object({type:I.literal("static"),value:I.union([I.string(),I.number(),I.boolean()])}),jt]),ed=Y.discriminatedUnion("type",[Y.object({default:Y.string().optional(),type:Y.literal("string")}),Y.object({default:Y.number().optional(),type:Y.literal("number")}),Y.object({default:Y.boolean().optional(),type:Y.literal("boolean")}),Y.object({key:Y.string().min(1),type:Y.literal("env")})]),v={id:u.string().min(1).max(200),label:u.string().max(500).optional(),next:u.string().max(200).optional(),uiOnly:u.boolean().optional()},po=500,td=u.object({...v,type:u.literal("goto"),url:ie}),rd=u.object({...v,locator:P,type:u.literal("click")}),nd=u.object({...v,locator:P,type:u.literal("fill"),value:ie}),od=u.object({...v,locator:P,type:u.literal("select"),value:ie}),id=u.object({...v,locator:P,type:u.literal("hover")}),sd=u.object({...v,key:u.string().min(1),locator:P.optional(),type:u.literal("press")}),ad=u.object({...v,locator:P,type:u.literal("check")}),ld=u.object({...v,locator:P,type:u.literal("uncheck")}),cd=u.object({...v,height:u.number().int().positive(),type:u.literal("setViewport"),width:u.number().int().positive()}),dd=u.object({...v,message:u.string().min(1),type:u.literal("fail")}),ud=u.object({...v,type:u.literal("setVariable"),value:Yc,variable:u.string().min(1)}),pd=u.object({...v,locator:P,type:u.literal("extractText"),variable:u.string().min(1)}),md=u.object({...v,files:u.array(u.string()).min(1),locator:P,type:u.literal("upload")}),fd=u.object({...v,locator:P,type:u.literal("dblclick")}),gd=u.object({...v,source:P,target:P,type:u.literal("drag")}),yd=u.object({...v,locator:P,type:u.literal("scrollIntoView")}),hd=u.object({...v,locator:P,type:u.literal("type"),value:ie}),wd=u.object({...v,locator:P,type:u.literal("focus")}),bd=u.object({...v,locator:P,type:u.literal("clear")}),vd=u.object({...v,locator:P,type:u.literal("rightClick")}),kd=u.object({...v,action:u.enum(["accept","dismiss"]),promptText:u.string().optional(),type:u.literal("handleDialog")}),Sd=u.object({...v,action:u.enum(["read","write"]),type:u.literal("clipboard"),value:ie.optional(),variable:u.string().min(1).optional()}),Rd=u.object({...v,permission:u.string().min(1),state:u.enum(["granted","prompt"]),type:u.literal("setPermission")}),Pd=u.object({...v,locator:P,type:u.literal("assertVisible")}),xd=u.object({...v,locator:P,type:u.literal("assertNotVisible")}),Cd=u.object({...v,expected:ie,locator:P,operator:rt,type:u.literal("assertText")}),Ed=u.object({...v,expected:ie,operator:rt,type:u.literal("assertUrl")}),Ad=u.object({...v,expected:Xc,locator:P,operator:Jc,type:u.literal("assertCount")}),Id=u.object({...v,expected:ie,locator:P,operator:rt,type:u.literal("assertValue")}),Td=u.object({...v,attribute:u.string().min(1),expected:ie,locator:P,operator:rt,type:u.literal("assertAttribute")}),$d=u.object({...v,locator:P,type:u.literal("assertEnabled")}),jd=u.object({...v,locator:P,type:u.literal("assertDisabled")}),Nd=u.object({...v,expected:ie,operator:rt,type:u.literal("assertTitle")}),Od=u.object({...v,locator:P,type:u.literal("assertChecked")}),Dd=u.object({...v,locator:P,type:u.literal("assertNotChecked")}),Ld=u.object({...v,locator:P,type:u.literal("assertFocused")}),_d=u.object({...v,locator:P,type:u.literal("assertNotFocused")}),Ud=u.object({...v,budget:u.enum(["fast","slow","async"]),observer:u.string().min(1).max(200),params:u.record(u.string().max(200),Zc),type:u.literal("assertObserver")}),Vd=u.discriminatedUnion("type",[td,rd,nd,od,id,sd,ad,ld,Pd,xd,Cd,Ed,Ad,Id,Td,$d,jd,cd,dd,ud,pd,md,fd,gd,yd,hd,wd,bd,vd,kd,Sd,Rd,Nd,Od,Dd,Ld,_d,Ud]),ho=u.object({entryNode:u.string().min(1).max(200),nodes:u.record(u.string().max(200),Vd).refine(e=>Object.keys(e).length<=po,`Workflow has more than ${String(po)} nodes`),uiOnly:u.boolean().optional(),variableNamespaces:u.record(u.string().max(200),u.string().max(500)).optional(),variables:u.record(u.string().max(200),ed).optional()}),Fd=uo.record(uo.string().max(200),go),Hd={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&"entryNode"in e&&"nodes"in e},Wd={assumedVersion:1,detect:e=>typeof e=="object"&&e!==null&&!Array.isArray(e)&&!("__codec"in e)},Ev=Wr("workflow-spec").legacy(Hd).initial(ho).build(),Av=Wr("precondition-map").legacy(Wd).initial(Fd).build(),Bd=["fast","slow","async"],Md=_r.object({budget:_r.enum(Bd).describe("Polling budget tier: fast | slow | async"),description:_r.string().min(1).describe("Human-readable description of what this observer checks")}).describe("A named backend state observer. Tests assert against it with assert.backend(observer, params). Implementation lives on the user's server."),se=".ripplo/ripplo.lock",qd=5e3,zd=U.record(U.string().max(200),U.string().max(200)),Gd=U.object({coverage:U.array(U.string().max(500)).max(2e3).default([]),expectedOutcome:U.string().max(2e3),name:U.string().max(200),preconditions:U.array(U.string().max(200)).max(1e3),requiresKeys:zd,slug:U.string().max(200),spec:ho}),Kd=U.object({observers:U.record(U.string().max(200),Md),preconditions:U.record(U.string().max(200),go),tests:U.array(Gd).max(qd)}),wo=Wr("ripplo-lockfile").initial(Kd).build();function bo(e){return{observers:e.observers,preconditions:e.preconditions,tests:e.tests.filter(t=>t.implemented).map(t=>({coverage:[...t.coverage],expectedOutcome:t.expectedOutcome,name:t.name,preconditions:[...t.preconditions],requiresKeys:{...t.requiresKeys},slug:t.slug,spec:t.spec}))}}function Hr(e){let t=wo.encode(e);return`${JSON.stringify(t,Yd(t),2)}
168
+ `}async function Nt({cwd:e}){let t=Vr.join(e,se),r=await Jd(t);return r==null?null:Bc(wo,r)}async function ae({cwd:e,result:t}){let r=bo(t),n=Vr.join(e,se);await Ur.mkdir(Vr.dirname(n),{recursive:!0}),await Ur.writeFile(n,Hr(r),"utf8")}function Ot({compiled:e,existing:t}){if(t==null)return"missing";let r=Hr(bo(e)),n=Hr(t);return r===n?"match":"stale"}async function Jd(e){try{return await Ur.readFile(e,"utf8")}catch(t){if(Qd(t)&&t.code==="ENOENT")return null;throw t}}function Qd(e){return e instanceof Error&&"code"in e}function Yd(e){return function(r,n){return n===e||!Xd(n)?n:Object.fromEntries(Object.entries(n).toSorted(([o],[i])=>o.localeCompare(i)))}}function Xd(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}import nt from"fs";import _t from"path";var Br=["**/src/**","**/app/**","**/apps/**","**/pages/**","**/routes/**","**/components/**","**/server/**","**/api/**","**/backend/**","**/features/**","**/modules/**","**/views/**","**/ui/**","**/hooks/**","**/contexts/**","**/providers/**","**/controllers/**","**/handlers/**","**/resolvers/**","**/services/**","**/middleware/**","**/lib/**"],ke=["**/*.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/**","**/scripts/**"];function vo(e){return e.data}function ko(e){return{as(t){return{data:{label:t,node:e}}}}}function Dt(e){let t=e.getPreconditions(),r=e.getObservers(),n=e.getTests();Zd(n);let o={};t.forEach(a=>{o[a.name]={depends:[...a.dependsOn],description:a.description,returns:[...a.returns]}});let i={};r.forEach(a=>{i[a.name]={budget:a.budget,description:a.description}});let s=n.map(a=>eu(a,t));return{observers:i,preconditions:o,tests:s}}function Zd(e){let t=new Map;e.forEach(r=>{let n=t.get(r.id);if(n!=null)throw new Error(`Duplicate test id "${r.id}" used by "${n}" and "${r.name}"`);t.set(r.id,r.name)})}function eu(e,t){let r=e.id,{accessedKeys:n,vars:o}=ru(e.requiresKeys),i=e.startsAtFn==null?void 0:e.startsAtFn(o),s=e.stepsFn==null?[]:e.stepsFn(o),a=i==null?s:[tu(i),...s],l=nu(a,n,e.requiresKeys,e.uiOnly),c=[];Object.keys(e.requiresKeys).length>0&&n.size===0&&e.implemented&&c.push("Test requires preconditions but never references their data \u2014 destructure and use precondition data in steps()");let m=iu(e.requires,t);return{additionalChecks:[],coverage:e.coverage,description:e.description,expectedOutcome:e.expectedOutcome,implemented:e.implemented,name:e.name,preconditions:m,requiresKeys:{...e.requiresKeys},slug:r,spec:l,warnings:c}}function tu(e){return ko({type:"goto",url:{type:"static",value:e}}).as(`navigate to ${e}`)}function ru(e){let t=new Set,r={};return Object.keys(e).forEach(n=>{r[n]=new Proxy({},{get(o,i){if(typeof i=="string"){let s=`${n}.${i}`;return t.add(s),`{{${s}}}`}}})}),{accessedKeys:t,vars:r}}function nu(e,t,r,n){let o={};e.forEach((a,l)=>{let c=`step-${String(l)}`,p=l<e.length-1?`step-${String(l+1)}`:void 0;o[c]=ou(a,c,p)});let i={};t.forEach(a=>{i[a]={default:`test-${a}`,type:"string"}});let s={...r};return{entryNode:"step-0",nodes:o,uiOnly:n,variableNamespaces:s,variables:i}}function ou(e,t,r){let{label:n,node:o}=vo(e);return{...o,id:t,label:n,next:r}}function iu(e,t){let r=new Map(t.map(s=>[s.name,s])),n=[],o=new Set;function i(s){o.has(s)||(o.add(s),r.get(s)?.dependsOn.forEach(a=>{i(a)}),n.push(s))}return e.forEach(s=>{i(s)}),n}import{Webhook as Vv,WebhookVerificationError as Fv}from"standardwebhooks";import{z as R}from"zod";var Wv=R.object({preconditions:R.array(R.string().min(1))}),Bv=R.object({data:R.record(R.string(),R.record(R.string(),R.string())),preconditions:R.array(R.string().min(1))}),Mv=R.object({observer:R.string().min(1).max(200),params:R.record(R.string().max(200),R.union([R.string(),R.number(),R.boolean()]))}),su=R.discriminatedUnion("kind",[R.object({kind:R.literal("pass")}),R.object({kind:R.literal("retry"),reason:R.string()}),R.object({kind:R.literal("fail"),reason:R.string()})]),qv=R.object({error:R.string().optional(),outcome:su.optional(),success:R.boolean()});function De(e){let t=[];return e.tests.forEach(r=>{let n=au(r),o=i=>{t.push({...i,test:r.slug})};Iu.forEach(i=>{i(n,r,o)})}),{diagnostics:t}}function au(e){let t=[],r=e.spec.entryNode,n=new Set;for(;r!=null&&!n.has(r);){n.add(r);let o=e.spec.nodes[r];if(o==null)break;t.push(o),r=o.next}return t}function lu(e,t,r){e.forEach(n=>{n.type==="assertText"&&"operator"in n&&n.operator!=="equals"&&r({message:`${n.type} uses operator "${n.operator}" \u2014 only "equals" is allowed for determinism`,rule:"exact-text-match",step:n.label??n.id})})}function cu(e,t,r){Object.keys(t.spec.variables??{}).length>0&&e.forEach(o=>{if(o.type==="fill"&&Ro(o.value)){let i=o.value.value;!Po(i)&&yu(i)&&r({message:`fill() uses hardcoded value "${i}" \u2014 consider using precondition data via {{namespace.key}}`,rule:"no-hardcoded-data",step:o.label??o.id})}})}function du(e,t,r){if(Object.keys(t.spec.variables??{}).length===0)return;e.some(i=>JSON.stringify(i).includes("{{"))||r({message:"Test requires preconditions but steps() never references precondition data \u2014 destructure and use it",rule:"prefer-precondition-data",step:void 0})}function uu(e,t,r){e.forEach(n=>{(n.label==null||n.label.length===0)&&r({message:`Step "${n.id}" lacks .as("...") label \u2014 every step must be labeled`,rule:"missing-label",step:n.id})})}function pu(e,t,r){let n=new Map;e.forEach(o=>{if(o.label==null)return;let i=n.get(o.label);i==null?n.set(o.label,o.id):r({message:`Duplicate label "${o.label}" \u2014 also used by ${i}`,rule:"no-duplicate-labels",step:o.label})})}function mu(e,t,r){let n=0;e.forEach((o,i)=>{if(!(i===0&&o.type==="goto")){if(Le(o)){n=0;return}n++,n===3&&r({message:"3+ consecutive actions without an assertion \u2014 add verification between actions",rule:"assert-after-action",step:o.label??o.id})}})}function fu(e,t,r){if(e.length===0)return;let n=e.at(-1);n!=null&&!Le(n)&&r({message:"Last step is an action, not an assertion \u2014 expectedOutcome should be verified by assertions at the end",rule:"assert-matches-outcome",step:n.label??n.id})}function gu(e,t,r){t.implemented&&e.length===0&&r({message:"Test has zero steps",rule:"no-empty-steps",step:void 0})}function Ro(e){return!(typeof e!="object"||e==null||!("type"in e)||e.type!=="static"||!("value"in e)||typeof e.value!="string")}function Po(e){return e.includes("{{")}function yu(e){return e.includes("@")||/\b[a-f0-9]{8,}\b/.test(e)||/^(test|ripplo|example|sample|demo)/i.test(e)}function Le(e){return e.type.startsWith("assert")}var hu=new Set(["assertText","assertValue","assertCount","assertUrl","assertNotVisible","assertChecked","assertNotChecked","assertAttribute","assertDisabled","assertEnabled"]);function wu(e){return hu.has(e.type)}function bu(e,t,r){!t.implemented||e.length===0||e.some(n=>Le(n))||r({message:"Test has zero assertion steps \u2014 cannot verify expectedOutcome. Add assert.* steps.",rule:"no-assertions",step:void 0})}function vu(e,t,r){if(!t.implemented||e.length<=3)return;let n=e.filter(i=>Le(i)).length;if(n===0)return;let o=n/e.length;if(o<.15){let i=Math.round(o*100);r({message:`Only ${String(n)}/${String(e.length)} steps are assertions (${String(i)}%) \u2014 add more verification between actions`,rule:"low-assertion-ratio",step:void 0})}}function Mr(e){if(!("locator"in e)||e.locator==null)return;let t=e.locator;return t.by==="role"?`role:${t.role}:${t.name??""}`:`testId:${t.value}`}function ku(e,t,r){e.forEach((n,o)=>{if(n.type!=="click")return;let i=Mr(n);if(i==null)return;let s=e.slice(o+1,o+4);s.find(c=>c.type==="assertVisible"&&Mr(c)===i)==null||s.some(c=>wu(c)||Le(c)&&Mr(c)!==i)||r({message:`click "${n.label??n.id}" is followed only by assert.visible on the same locator \u2014 verifies nothing about the click's effect`,rule:"tautological-post-click-assert",step:n.label??n.id})})}var Su=new Set(["the","is","a","an","and","or","of","to","in","on","at","for","that","this","with","be","are","was","were","it","its","as","by","from","after","before","should","will","can","still","but","not","no","so","if","then","than","user","users","page","view","shows","show","see","click","clicks","clicked","clickable","visible","appears","appear","displayed","stays","remains"]);function qr(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(t=>t.length>=3&&!Su.has(t))}function Ru(e){let t=e.label==null?[]:qr(e.label);if(!("locator"in e)||e.locator==null)return t;let r=e.locator,n=r.by==="role"?r.name??"":r.value;return[...t,...qr(n)]}function Pu(e,t,r){if(!t.implemented||e.length===0)return;let n=new Set(qr(t.expectedOutcome));if(n.size===0)return;let o=e.filter(s=>Le(s));if(o.length===0)return;o.some(s=>Ru(s).some(a=>n.has(a)))||r({message:`No assertion references any keyword from expectedOutcome (${[...n].join(", ")}) \u2014 the outcome may not actually be verified`,rule:"expected-outcome-keyword-coverage",step:void 0})}var xu=/save|submit|create|delete|remove|send|invite|update|confirm|publish|apply|pay|subscribe|upgrade|cancel|archive|rename/i;function So(e){if(e.type==="upload")return!0;if(e.type!=="click")return!1;let t=e.locator,r=t.by==="role"?t.name??"":t.value;return xu.test(r)}function Cu(e,t,r){!t.implemented||t.spec.uiOnly===!0||e.forEach((n,o)=>{if(!So(n)||"uiOnly"in n&&n.uiOnly===!0)return;let i=e.slice(o+1),s=i.findIndex(p=>So(p)),a=s===-1?i.length:s;i.slice(0,a).some(p=>p.type==="assertObserver")||r({message:`"${n.label??n.id}" looks like it mutates backend state but no assert.backend(...) observer verifies it \u2014 the test can pass while the server is broken. You need to add an observer that checks the persisted result (see .ripplo/observers/ for examples). Only if this action truly does NOT change any server state (pure client-side interaction, presentation toggle, etc.) should you fall back to marking the step { uiOnly: true } to silence this rule.`,rule:"mutation-without-observer-coverage",step:n.label??n.id})})}function Eu(e,t,r){Object.keys(t.spec.variables??{}).length!==0&&e.forEach(o=>{if(o.type!=="assertObserver")return;let i=Object.entries(o.params);i.length===0||i.some(([,a])=>Ro(a)&&Po(a.value))||r({message:`assert.backend "${o.label??o.id}" passes only hardcoded params while the test defines precondition variables \u2014 observer assertions should reference {{namespace.key}} so they match the actual precondition data`,rule:"observer-params-reference-variables",step:o.label??o.id})})}function Au(e,t,r){let n=new Set(Object.keys(t.spec.variables??{})),o=/\{\{([^{}]+?)\}\}/g;e.forEach(i=>{let a=[...JSON.stringify(i).matchAll(o)],l=[...new Set(a.map(m=>m[1]).filter(m=>m!=null&&!n.has(m)))];if(l.length===0)return;let c=l.map(m=>`{{${m}}}`).join(", "),p=[...new Set(l.map(m=>m.split(".")[0]??m))].join(", ");r({message:`"${i.label??i.id}" contains literal template string(s) ${c} \u2014 destructure the proxy in .steps(({ ${p} }) => \u2026) and pass the proxy value (e.g. \`${l[0]??""}\`) directly. Writing the template as a string bypasses type-checking and silently accepts typos.`,rule:"no-literal-template-strings",step:i.label??i.id})})}var Iu=[lu,cu,Au,du,uu,pu,mu,fu,gu,bu,vu,ku,Pu,Cu,Eu];import ep from"picomatch";import{execFileSync as Tu,spawnSync as $u}from"child_process";function q(e,t){return Tu("git",[...e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function xo(e,t){let r=$u("git",["show",e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]});if(r.status===0)return r.stdout;if(r.status===128)return null;throw new Error(`git show ${e} failed (${String(r.status)}): ${r.stderr}`)}import h from"typescript";import*as Co from"remeda";var Eo=[["onClick","click"],["onContextMenu","click"],["onKeyDown","click"],["onKeyPress","click"],["onKeyUp","click"],["onSelect","click"],["onOpenChange","click"],["onPress","click"],["onActivate","click"],["onToggle","click"],["onOpen","click"],["onClose","click"],["onDismiss","click"],["onConfirm","click"],["onCancel","click"],["onApply","click"],["onClear","click"],["onSubmit","submit"],["onReset","submit"],["onDrop","drag"],["onDragEnd","drag"],["onDragEnter","drag"],["onDragLeave","drag"],["onDragOver","drag"],["onDragStart","drag"],["onValueChange","select"],["onSelectionChange","select"],["onChange","input"],["onCheckedChange","input"],["onInput","input"]],ju=new Map([["button","click"],["link","navigate"],["textbox","input"],["searchbox","input"],["combobox","select"],["listbox","select"]]),Nu=[[/(?:^|[a-z])Button$/,"click"],[/(?:^|[a-z])(?:Link|NavLink|Anchor)$/,"navigate"],[/(?:^|[a-z])(?:Textarea|TextArea|Input|Field|SearchBox)$/,"input"],[/(?:^|[a-z])(?:Select|Combobox|ComboBox|Dropdown)$/,"select"],[/(?:^|[a-z])Form$/,"submit"]];function Lt({filePath:e,source:t}){let r=h.createSourceFile(e,t,h.ScriptTarget.Latest,!0,h.ScriptKind.TSX);return r.statements.flatMap(n=>Ou({filePath:e,sf:r,stmt:n}))}function Ou({filePath:e,sf:t,stmt:r}){let n=Lu(r);if(n==null||Du(n))return[];let o=Ao(r,t).map(i=>({id:`${e}#${n}.${i.kind}[${i.label}]`,line:i.line}));return Co.uniqueBy(o,i=>i.id)}function Du(e){return e.endsWith("Fragment")||e.endsWith("Skeleton")||/^use[A-Z]/.test(e)||/^[A-Z][A-Z0-9_]*$/.test(e)}function Lu(e){if(h.isExportAssignment(e))return _u(e.expression);if(!Uu(e))return null;if(h.isFunctionDeclaration(e)||h.isClassDeclaration(e))return e.name?.text??null;if(h.isVariableStatement(e)){let t=e.declarationList.declarations[0];return t!=null&&h.isIdentifier(t.name)?t.name.text:null}return null}function _u(e){return h.isIdentifier(e)?e.text:(h.isFunctionExpression(e)||h.isClassExpression(e))&&e.name!=null?e.name.text:"default"}function Uu(e){if(!h.canHaveModifiers(e))return!1;let t=h.getModifiers(e);return t!=null&&t.some(r=>r.kind===h.SyntaxKind.ExportKeyword)}function Ao(e,t){let r=Vu(e,t),n=e.getChildren(t).flatMap(o=>Ao(o,t));return r==null?n:[r,...n]}function Vu(e,t){let r=Fu(e);if(r==null)return null;let n=Hu(r);if(n==null)return null;let o=zu(r,n);return o==null?null:{kind:n,label:o,line:Zu(t,e.getStart(t))}}function Fu(e){return h.isJsxSelfClosingElement(e)&&h.isIdentifier(e.tagName)?{attrs:e.attributes,children:[],tag:e.tagName.text}:h.isJsxElement(e)&&h.isIdentifier(e.openingElement.tagName)?{attrs:e.openingElement.attributes,children:e.children,tag:e.openingElement.tagName.text}:null}function Hu({attrs:e,tag:t}){let r=le(e,"role");return r!=null?ju.get(r)??null:Bu(t,e)??Mu(t)??qu(e)}var Wu=new Map([["button",()=>"click"],["a",e=>le(e,"href")==null?null:"navigate"],["input",e=>le(e,"type")==="file"?"upload":"input"],["textarea",()=>"input"],["select",()=>"select"],["form",()=>"submit"]]);function Bu(e,t){return Wu.get(e)?.(t)??null}function Mu(e){return/^[A-Z]/.test(e)?Nu.find(([t])=>t.test(e))?.[1]??null:null}function qu(e){return Eo.find(([t])=>Ju(e,t))?.[1]??null}function zu({attrs:e,children:t,tag:r},n){return n==="navigate"?le(e,"href")??le(e,"to"):le(e,"aria-label")??le(e,"label")??le(e,"title")??le(e,"name")??le(e,"placeholder")??(Gu(r)?void 0:Xu(t))??Ku(e,n)}function Gu(e){return e==="input"||e==="textarea"||e==="select"}function Ku(e,t){let r=Eo.find(([,o])=>o===t)?.[0];if(r==null)return;let n=Qu(e,r);if(n!=null){if(h.isIdentifier(n))return n.text;if(h.isArrowFunction(n)||h.isFunctionExpression(n))return Io(n.body)}}function Io(e){if(h.isCallExpression(e)){let t=e.expression;return h.isIdentifier(t)?t.text:h.isPropertyAccessExpression(t)?t.name.text:void 0}return h.forEachChild(e,Io)}function Ju(e,t){return e.properties.some(r=>h.isJsxAttribute(r)&&h.isIdentifier(r.name)&&r.name.text===t)}function To(e,t){return e.properties.find(r=>h.isJsxAttribute(r)&&h.isIdentifier(r.name)&&r.name.text===t)}function le(e,t){let r=To(e,t);return r==null?void 0:Yu(r.initializer)}function Qu(e,t){let r=To(e,t);if(!(r?.initializer==null||!h.isJsxExpression(r.initializer)))return r.initializer.expression}function Yu(e){if(e==null)return"";if(h.isStringLiteral(e))return e.text;if(h.isJsxExpression(e))return jo(e.expression)}function Xu(e){let t=e.flatMap(r=>$o(r)).join(" ").trim();return t.length===0?void 0:t}function $o(e){if(h.isJsxText(e)){let t=e.text.trim();return t.length===0?[]:[t]}if(h.isJsxExpression(e)){let t=jo(e.expression);return t==null?[]:[t]}return h.isJsxElement(e)?e.children.flatMap(t=>$o(t)):[]}function jo(e){if(e!=null){if(h.isStringLiteral(e)||h.isNoSubstitutionTemplateLiteral(e))return e.text;if(h.isCallExpression(e)&&e.arguments.length===1){let t=e.arguments[0];if(t!=null&&(h.isStringLiteral(t)||h.isNoSubstitutionTemplateLiteral(t)))return t.text}}}function Zu(e,t){return e.getLineAndCharacterOfPosition(t).line+1}var tp=_t.join(".ripplo","coverage.d.ts");function _e({cwd:e}){rp({cwd:e,ignorePaths:ke})}function rp({cwd:e,ignorePaths:t}){let r=Ue({cwd:e,ignorePaths:t});ip({content:op(r),cwd:e})}function Ue({cwd:e,ignorePaths:t}){let n=np({cwd:e,ignorePaths:t}).flatMap(o=>zr({cwd:e,file:o}));return[...new Set(n)].sort((o,i)=>o.localeCompare(i))}function np({cwd:e,ignorePaths:t}){let r=q(["ls-files","--cached","--others","--exclude-standard","-z"],e),n=ep([...t]);return r.split("\0").filter(o=>o.length>0&&/\.(tsx|jsx)$/.test(o)).filter(o=>!n(o)).filter(o=>nt.existsSync(_t.join(e,o)))}function zr({cwd:e,file:t}){let r=nt.readFileSync(_t.join(e,t),"utf8");return Lt({filePath:t,source:r}).map(n=>n.id)}function op(e){let t=`// GENERATED \u2014 do not edit
141
169
  import "@ripplo/testing";
142
170
 
143
171
  declare module "@ripplo/testing" {
@@ -148,22 +176,22 @@ declare module "@ripplo/testing" {
148
176
  `:`${t}${r}
149
177
  }
150
178
  }
151
- `}function ap({content:e,cwd:t}){let r=_t.join(t,np);ot.existsSync(r)&&ot.readFileSync(r,"utf8")===e||ot.writeFileSync(r,e)}import _o from"path";import{createJiti as lp}from"jiti";var cp=["getObservers","getPreconditions","getTests","getUnimplemented"];function up(e){return e==null||typeof e!="object"?!1:cp.every(t=>typeof Reflect.get(e,t)=="function")}function dp(e){return e===null?"null":Array.isArray(e)?"array":typeof e}async function C(e){let t=_o.join(e,".ripplo","index.ts"),r=_o.join(e,".ripplo");try{let o=await lp(import.meta.url,{moduleCache:!1,sourceMaps:!0}).import(t),i=o!=null&&typeof o=="object"&&"default"in o?Reflect.get(o,"default"):o;if(!up(i))return{error:`${t} must default-export a RipploBuilder (got ${dp(i)})`,ok:!1};let s=Dt(i);return{builder:i,ok:!0,result:s}}catch(n){return{error:mp(n,r),ok:!1}}}var pp=/\(?([^\s()]{1,500}\.ts):(\d{1,10}):(\d{1,10})\)?$/;function mp(e,t){if(!(e instanceof Error))return String(e);let r=fp(e.stack,t);return r==null?e.message:`${r} \u2014 ${e.message}`}function fp(e,t){if(e==null)return;let n=e.split(`
152
- `).map(a=>pp.exec(a)).find(a=>a!=null&&a[1]!=null&&a[2]!=null&&a[3]!=null&&a[1].startsWith(t));if(n==null)return;let[,o,i,s]=n;return`${o??""}:${i??""}:${s??""}`}function w(e,t){return t==null?`Load \`/ripplo:${e}\` skill for instructions.`:`Load \`/ripplo:${e}\` skill for instructions on ${t}.`}function Uo(e){return e.length===1?w(e[0]):`REQUIRED before proceeding: load ${e.map(r=>`\`/ripplo:${r}\``).join(" AND ")} skills (load every one \u2014 each carries rules the others don't).`}async function Vo(e){let t=process.cwd(),r=await C(t);if(r.ok||(process.stderr.write(`Compilation failed: ${r.error}
179
+ `}function ip({content:e,cwd:t}){let r=_t.join(t,tp);nt.existsSync(r)&&nt.readFileSync(r,"utf8")===e||nt.writeFileSync(r,e)}import No from"path";import{createJiti as sp}from"jiti";var ap=["getObservers","getPreconditions","getTests","getUnimplemented"];function lp(e){return e==null||typeof e!="object"?!1:ap.every(t=>typeof Reflect.get(e,t)=="function")}function cp(e){return e===null?"null":Array.isArray(e)?"array":typeof e}async function C(e){let t=No.join(e,".ripplo","index.ts"),r=No.join(e,".ripplo");try{let o=await sp(import.meta.url,{moduleCache:!1,sourceMaps:!0}).import(t),i=o!=null&&typeof o=="object"&&"default"in o?Reflect.get(o,"default"):o;if(!lp(i))return{error:`${t} must default-export a RipploBuilder (got ${cp(i)})`,ok:!1};let s=Dt(i);return{builder:i,ok:!0,result:s}}catch(n){return{error:up(n,r),ok:!1}}}var dp=/\(?([^\s()]{1,500}\.ts):(\d{1,10}):(\d{1,10})\)?$/;function up(e,t){if(!(e instanceof Error))return String(e);let r=pp(e.stack,t);return r==null?e.message:`${r} \u2014 ${e.message}`}function pp(e,t){if(e==null)return;let n=e.split(`
180
+ `).map(a=>dp.exec(a)).find(a=>a!=null&&a[1]!=null&&a[2]!=null&&a[3]!=null&&a[1].startsWith(t));if(n==null)return;let[,o,i,s]=n;return`${o??""}:${i??""}:${s??""}`}function w(e,t){return t==null?`Load \`/ripplo:${e}\` skill for instructions.`:`Load \`/ripplo:${e}\` skill for instructions on ${t}.`}function Oo(e){return e.length===1?w(e[0]):`REQUIRED before proceeding: load ${e.map(r=>`\`/ripplo:${r}\``).join(" AND ")} skills (load every one \u2014 each carries rules the others don't).`}async function Do(e){let t=process.cwd(),r=await C(t);if(r.ok||(process.stderr.write(`Compilation failed: ${r.error}
153
181
  `),process.stderr.write(`${w("create","DSL authoring + lint rules")}
154
182
  `),process.exit(1)),e.check){let n=await Nt({cwd:t}),o=Ot({compiled:r.result,existing:n});if(o==="match"){process.stdout.write(`${se} is up to date
155
183
  `);return}let i=o==="missing"?"missing":"out of date";process.stderr.write(`${se} is ${i} \u2014 run \`ripplo compile\` and commit the result
156
184
  `),process.stderr.write(`${w("setup")}
157
185
  `),process.exit(1)}await ae({cwd:t,result:r.result}),_e({cwd:t}),process.stdout.write(`wrote ${se}
158
- `)}import pe from"process";import gp from"fs";import yp from"path";import hp from"picomatch";function Fo({compileResult:e,cwd:t,ignorePaths:r}){let n=new Set(Ue({cwd:t,ignorePaths:r})),o=Wo(e),i=wp({cwd:t,ignorePaths:r});return Bo({acknowledged:o,allStatements:n,unacked:i})}function Ho({compileResult:e,cwd:t,ignorePaths:r}){let n=new Set(Ue({cwd:t,ignorePaths:r})),o=Wo(e);return Bo({acknowledged:o,allStatements:n,unacked:n})}function Bo({acknowledged:e,allStatements:t,unacked:r}){let n=[...r].filter(i=>!e.has(i)).map(i=>({id:i,kind:"unacknowledged"})),o=[...e].filter(i=>!t.has(i)).map(i=>({id:i,kind:"stale"}));return[...n,...o]}function Wo(e){return new Set(e.tests.flatMap(t=>t.coverage))}function wp({cwd:e,ignorePaths:t}){let r=bp({cwd:e,ignorePaths:t}),n=new Set(r.flatMap(i=>vp({cwd:e,file:i}))),o=new Set(r.flatMap(i=>kp({cwd:e,file:i})?Jr({cwd:e,file:i}):[]));return new Set([...o].filter(i=>!n.has(i)))}function bp({cwd:e,ignorePaths:t}){let r=q(["diff","--name-only","HEAD","-z"],e),n=q(["ls-files","--others","--exclude-standard","-z"],e),o=hp([...t]);return[...new Set([...r.split("\0"),...n.split("\0")])].filter(i=>i.length>0&&/\.(tsx|jsx)$/.test(i)).filter(i=>!o(i))}function vp({cwd:e,file:t}){let r=Ao(`HEAD:${t}`,e);return r==null?[]:Lt({filePath:t,source:r}).map(n=>n.id)}function kp({cwd:e,file:t}){return gp.existsSync(yp.join(e,t))}async function Mo(){let e=pe.cwd(),t=await C(e);t.ok||(pe.stderr.write(`Compilation failed: ${t.error}
159
- `),pe.exit(1));let r=Ue({cwd:e,ignorePaths:Re}),n=Ho({compileResult:t.result,cwd:e,ignorePaths:Re}),o=n.filter(a=>a.kind==="unacknowledged"),i=n.filter(a=>a.kind==="stale"),s=r.length-o.length;pe.stdout.write(`Coverage: ${String(s)}/${String(r.length)} statements acknowledged
160
- `),o.length>0&&(pe.stdout.write(`
186
+ `)}import de from"process";import mp from"fs";import fp from"path";import gp from"picomatch";function Lo({compileResult:e,cwd:t,ignorePaths:r}){let n=new Set(Ue({cwd:t,ignorePaths:r})),o=Vo(e),i=yp({cwd:t,ignorePaths:r});return Uo({acknowledged:o,allStatements:n,unacked:i})}function _o({compileResult:e,cwd:t,ignorePaths:r}){let n=new Set(Ue({cwd:t,ignorePaths:r})),o=Vo(e);return Uo({acknowledged:o,allStatements:n,unacked:n})}function Uo({acknowledged:e,allStatements:t,unacked:r}){let n=[...r].filter(i=>!e.has(i)).map(i=>({id:i,kind:"unacknowledged"})),o=[...e].filter(i=>!t.has(i)).map(i=>({id:i,kind:"stale"}));return[...n,...o]}function Vo(e){return new Set(e.tests.flatMap(t=>t.coverage))}function yp({cwd:e,ignorePaths:t}){let r=hp({cwd:e,ignorePaths:t}),n=new Set(r.flatMap(i=>wp({cwd:e,file:i}))),o=new Set(r.flatMap(i=>bp({cwd:e,file:i})?zr({cwd:e,file:i}):[]));return new Set([...o].filter(i=>!n.has(i)))}function hp({cwd:e,ignorePaths:t}){let r=q(["diff","--name-only","HEAD","-z"],e),n=q(["ls-files","--others","--exclude-standard","-z"],e),o=gp([...t]);return[...new Set([...r.split("\0"),...n.split("\0")])].filter(i=>i.length>0&&/\.(tsx|jsx)$/.test(i)).filter(i=>!o(i))}function wp({cwd:e,file:t}){let r=xo(`HEAD:${t}`,e);return r==null?[]:Lt({filePath:t,source:r}).map(n=>n.id)}function bp({cwd:e,file:t}){return mp.existsSync(fp.join(e,t))}async function Fo(){let e=de.cwd(),t=await C(e);t.ok||(de.stderr.write(`Compilation failed: ${t.error}
187
+ `),de.exit(1));let r=Ue({cwd:e,ignorePaths:ke}),n=_o({compileResult:t.result,cwd:e,ignorePaths:ke}),o=n.filter(a=>a.kind==="unacknowledged"),i=n.filter(a=>a.kind==="stale"),s=r.length-o.length;de.stdout.write(`Coverage: ${String(s)}/${String(r.length)} statements acknowledged
188
+ `),o.length>0&&(de.stdout.write(`
161
189
  Unacknowledged (${String(o.length)}):
162
- `),o.forEach(a=>pe.stdout.write(` ${a.id}
163
- `))),i.length>0&&(pe.stdout.write(`
190
+ `),o.forEach(a=>de.stdout.write(` ${a.id}
191
+ `))),i.length>0&&(de.stdout.write(`
164
192
  Stale claims (${String(i.length)}):
165
- `),i.forEach(a=>pe.stdout.write(` ${a.id}
166
- `))),n.length>0&&pe.exit(1)}import{graphql as Rp}from"gql.tada";import qo from"fs";import Qr from"path";function zo(e){let t=qo.realpathSync(e),r=t,n=Qr.dirname(r);for(;n!==r;){if(qo.existsSync(Qr.join(r,".git")))return r;r=n,n=Qr.dirname(r)}return t}function _(e){let t=Zt(e),r=Ie(),n=Z();if(n==null)throw new Error("ripplo: not authenticated. Run `ripplo auth login`.");return{appUrl:r.appUrl,cwd:zo(e),engineUrl:r.engineUrl,projectId:lo()??t.projectId,ripploServerUrl:Q(),token:n,webhookSecret:r.webhookSecret}}import it from"fs";var Sp="dev.pid";function Go(e){return et(e,Sp)}function Ve(e){let t=Go(e);if(!it.existsSync(t))return!1;let r=it.readFileSync(t,"utf8").trim(),n=Number.parseInt(r,10);if(!Number.isFinite(n)||n<=0)return!1;try{return process.kill(n,0),!0}catch{return!1}}function Ko(e){Ze(e);let t=Go(e);it.writeFileSync(t,String(process.pid));let r=!1;return()=>{if(!r){r=!0;try{let n=it.readFileSync(t,"utf8").trim();Number.parseInt(n,10)===process.pid&&it.unlinkSync(t)}catch{}}}}var Pp=Rp(`
193
+ `),i.forEach(a=>de.stdout.write(` ${a.id}
194
+ `))),n.length>0&&de.exit(1)}import{graphql as kp}from"gql.tada";import Ho from"fs";import Gr from"path";function Wo(e){let t=Ho.realpathSync(e),r=t,n=Gr.dirname(r);for(;n!==r;){if(Ho.existsSync(Gr.join(r,".git")))return r;r=n,n=Gr.dirname(r)}return t}function L(e){let t=Zt(e),r=Ee(),n=Z();if(n==null)throw new Error("ripplo: not authenticated. Run `ripplo auth login`.");return{appUrl:r.appUrl,cwd:Wo(e),engineUrl:r.engineUrl,projectId:oo()??t.projectId,ripploServerUrl:Q(),token:n,webhookSecret:r.webhookSecret}}import ot from"fs";var vp="dev.pid";function Bo(e){return et(e,vp)}function Ve(e){let t=Bo(e);if(!ot.existsSync(t))return!1;let r=ot.readFileSync(t,"utf8").trim(),n=Number.parseInt(r,10);if(!Number.isFinite(n)||n<=0)return!1;try{return process.kill(n,0),!0}catch{return!1}}function Mo(e){Ze(e);let t=Bo(e);ot.writeFileSync(t,String(process.pid));let r=!1;return()=>{if(!r){r=!0;try{let n=ot.readFileSync(t,"utf8").trim();Number.parseInt(n,10)===process.pid&&ot.unlinkSync(t)}catch{}}}}var Sp=kp(`
167
195
  query DevSessionCheckHealth($projectId: String!, $cwd: String!) {
168
196
  project(id: $projectId) {
169
197
  id
@@ -172,25 +200,25 @@ Stale claims (${String(i.length)}):
172
200
  }
173
201
  }
174
202
  }
175
- `);function Jo(e){switch(e.status){case"active":return"\u2713 Dev session: ripplo watch is live for this directory";case"starting":return"! Dev session: `ripplo watch` 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 watch process output for auth/network errors.";case"missing":return"\u2717 Dev session: no active ripplo watch. Start `npx ripplo watch` as a background process (separate from your app's dev server). Without it, `ripplo run` will refuse to dispatch and scope/coverage hooks won't arm."}}async function Qo(e){let t=Ve(e),r;try{r=_(e)}catch{return{status:t?"starting":"missing",type:"dev-session"}}return(await g({config:r,document:Pp,variables:{cwd:r.cwd,projectId:r.projectId}}).catch(()=>null))?.project?.devSession!=null?{status:"active",type:"dev-session"}:{status:t?"starting":"missing",type:"dev-session"}}function Yo(e){switch(e.type){case"settings":return Ip(e);case"env-files":return Ap(e);case"token":return jp(e);case"dev-server":return Tp(e);case"dev-session":return Jo(e);case"preconditions":return Op(e);case"webhook-verification":return $p(e);case"preconditions-validation":return Dp(e);case"workflows":return Lp(e);case"browser":return Np(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 xp(e);case"lockfile":return Cp(e);case"pre-commit-hook":return Ep(e)}}function xp(e){switch(e.status){case"enabled":return`\u2713 Adapter: enabled at ${e.url}`;case"disabled":return"\u2717 Adapter: disabled (handler returned 404). Set ENABLE_RIPPLO_TESTING=true in your app's env (e.g. apps/<app>/.env.local for Next.js) and restart the dev server.";case"bad-secret":return"\u2717 Adapter: webhook signature rejected. RIPPLO_WEBHOOK_SECRET seen by your dev server does not match the one in your env file.";case"unreachable":return`\u2717 Adapter: ${e.url} could not be reached for signed probe.`;case"no-secret":return"\u2717 Adapter: RIPPLO_WEBHOOK_SECRET is not set in your env file (declared in .ripplo/project.json)."}}function Cp(e){switch(e.status){case"match":return`\u2713 Lockfile: ${se} is up to date`;case"missing":return`\u2717 Lockfile: ${se} is missing \u2014 run \`ripplo compile\` and commit it`;case"stale":return`\u2717 Lockfile: ${se} is out of date \u2014 run \`ripplo compile\` and commit it`}}function Ep(e){return e.installed?"\u2713 Pre-commit hook: .git/hooks/pre-commit runs `ripplo compile --check`":"! Pre-commit hook: .git/hooks/pre-commit does not run `ripplo compile --check` \u2014 see the setup skill for the snippet"}function Ip(e){return e.valid?"\u2713 Settings: Project configured":`\u2717 Settings: Missing fields: ${e.missingFields.join(", ")}`}function Ap(e){return e.missing.length===0?"\u2713 Env files: declared files present":`\u2717 Env files: declared in .ripplo/project.json but missing:
203
+ `);function qo(e){switch(e.status){case"active":return"\u2713 Dev session: ripplo watch is live for this directory";case"starting":return"! Dev session: `ripplo watch` 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 watch process output for auth/network errors.";case"missing":return"\u2717 Dev session: no active ripplo watch. Start `npx ripplo watch` as a background process (separate from your app's dev server). Without it, `ripplo run` will refuse to dispatch and scope/coverage hooks won't arm."}}async function zo(e){let t=Ve(e),r;try{r=L(e)}catch{return{status:t?"starting":"missing",type:"dev-session"}}return(await g({config:r,document:Sp,variables:{cwd:r.cwd,projectId:r.projectId}}).catch(()=>null))?.project?.devSession!=null?{status:"active",type:"dev-session"}:{status:t?"starting":"missing",type:"dev-session"}}function Go(e){switch(e.type){case"settings":return Cp(e);case"env-files":return Ep(e);case"token":return Ap(e);case"dev-server":return Ip(e);case"dev-session":return qo(e);case"preconditions":return jp(e);case"webhook-verification":return Tp(e);case"preconditions-validation":return Np(e);case"workflows":return Op(e);case"browser":return $p(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 Rp(e);case"lockfile":return Pp(e);case"pre-commit-hook":return xp(e)}}function Rp(e){switch(e.status){case"enabled":return`\u2713 Adapter: enabled at ${e.url}`;case"disabled":return"\u2717 Adapter: disabled (handler returned 404). Set ENABLE_RIPPLO_TESTING=true in your app's env (e.g. apps/<app>/.env.local for Next.js) and restart the dev server.";case"bad-secret":return"\u2717 Adapter: webhook signature rejected. RIPPLO_WEBHOOK_SECRET seen by your dev server does not match the one in your env file.";case"unreachable":return`\u2717 Adapter: ${e.url} could not be reached for signed probe.`;case"no-secret":return"\u2717 Adapter: RIPPLO_WEBHOOK_SECRET is not set in your env file (declared in .ripplo/project.json)."}}function Pp(e){switch(e.status){case"match":return`\u2713 Lockfile: ${se} is up to date`;case"missing":return`\u2717 Lockfile: ${se} is missing \u2014 run \`ripplo compile\` and commit it`;case"stale":return`\u2717 Lockfile: ${se} is out of date \u2014 run \`ripplo compile\` and commit it`}}function xp(e){return e.installed?"\u2713 Pre-commit hook: .git/hooks/pre-commit runs `ripplo compile --check`":"! Pre-commit hook: .git/hooks/pre-commit does not run `ripplo compile --check` \u2014 see the setup skill for the snippet"}function Cp(e){return e.valid?"\u2713 Settings: Project configured":`\u2717 Settings: Missing fields: ${e.missingFields.join(", ")}`}function Ep(e){return e.missing.length===0?"\u2713 Env files: declared files present":`\u2717 Env files: declared in .ripplo/project.json but missing:
176
204
  ${e.missing.map(r=>` ${r}`).join(`
177
205
  `)}
178
- If you're in a git worktree, copy the env file from the main checkout (or symlink to a shared file outside the working tree).`}function jp(e){switch(e.status){case"valid":return`\u2713 Auth: Signed in as ${e.email??"unknown"}`;case"missing":return"\u2717 Auth: No token found. Run `ripplo` to authenticate.";case"invalid":return"\u2717 Auth: Saved token rejected by server. Run `ripplo auth login` to re-authenticate.";case"unreachable":return`! Auth: Could not reach ${Q()} to validate token.`}}function Tp(e){return e.reachable?`\u2713 Dev server: ${e.appUrl} is reachable`:`\u2717 Dev server: ${e.appUrl} is not responding. Make sure your dev server is running.`}function $p(e){return e.rejectsUnsigned?"\u2713 Webhook verification: Unsigned requests are correctly rejected":"\u2717 Webhook verification: Endpoint accepted an unsigned request \u2014 webhook signature verification may be missing or misconfigured."}function Np(e){return e.installed?"\u2713 Browser: Chromium installed":"\u2717 Browser: Chromium not installed. Run: npx playwright install chromium"}function Op(e){return e.count===0?"! Preconditions: None defined":e.configured?e.endpointReachable===void 0?`! Preconditions: ${String(e.count)} defined (could not verify endpoint)`:`\u2713 Preconditions: ${String(e.count)} defined, endpoint configured`:`\u2717 Preconditions: ${String(e.count)} defined but RIPPLO_ENGINE_URL is not set in your env file`}function Dp(e){if(!e.found)return"\u2717 State graph: DSL compilation failed";if(e.valid)return"\u2713 State graph: Valid";let t=e.errors.map(r=>` - ${r.path===""?"":r.path+": "}${r.message}`);return`\u2717 State graph: ${String(e.errorCount)} validation error${e.errorCount===1?"":"s"}
206
+ If you're in a git worktree, copy the env file from the main checkout (or symlink to a shared file outside the working tree).`}function Ap(e){switch(e.status){case"valid":return`\u2713 Auth: Signed in as ${e.email??"unknown"}`;case"missing":return"\u2717 Auth: No token found. Run `ripplo` to authenticate.";case"invalid":return"\u2717 Auth: Saved token rejected by server. Run `ripplo auth login` to re-authenticate.";case"unreachable":return`! Auth: Could not reach ${Q()} to validate token.`}}function Ip(e){return e.reachable?`\u2713 Dev server: ${e.appUrl} is reachable`:`\u2717 Dev server: ${e.appUrl} is not responding. Make sure your dev server is running.`}function Tp(e){return e.rejectsUnsigned?"\u2713 Webhook verification: Unsigned requests are correctly rejected":"\u2717 Webhook verification: Endpoint accepted an unsigned request \u2014 webhook signature verification may be missing or misconfigured."}function $p(e){return e.installed?"\u2713 Browser: Chromium installed":"\u2717 Browser: Chromium not installed. Run: npx playwright install chromium"}function jp(e){return e.count===0?"! Preconditions: None defined":e.configured?e.endpointReachable===void 0?`! Preconditions: ${String(e.count)} defined (could not verify endpoint)`:`\u2713 Preconditions: ${String(e.count)} defined, endpoint configured`:`\u2717 Preconditions: ${String(e.count)} defined but RIPPLO_ENGINE_URL is not set in your env file`}function Np(e){if(!e.found)return"\u2717 State graph: DSL compilation failed";if(e.valid)return"\u2713 State graph: Valid";let t=e.errors.map(r=>` - ${r.path===""?"":r.path+": "}${r.message}`);return`\u2717 State graph: ${String(e.errorCount)} validation error${e.errorCount===1?"":"s"}
179
207
  ${t.join(`
180
- `)}`}function Lp(e){if(e.total===0)return"! Tests: No tests defined";if(e.invalidNames.length===0)return`\u2713 Tests: ${String(e.total)} valid`;let t=e.invalidWorkflows.map(r=>_p(r));return`\u2717 Tests: ${String(e.invalidNames.length)} invalid
208
+ `)}`}function Op(e){if(e.total===0)return"! Tests: No tests defined";if(e.invalidNames.length===0)return`\u2713 Tests: ${String(e.total)} valid`;let t=e.invalidWorkflows.map(r=>Dp(r));return`\u2717 Tests: ${String(e.invalidNames.length)} invalid
181
209
  ${t.join(`
182
- `)}`}function _p(e){let t=e.errors.map(r=>" - "+(r.path===""?"":r.path+": ")+r.message);return" "+e.name+`:
210
+ `)}`}function Dp(e){let t=e.errors.map(r=>" - "+(r.path===""?"":r.path+": ")+r.message);return" "+e.name+`:
183
211
  `+t.join(`
184
- `)}import Xr from"fs";import Wp from"path";import{chromium as Mp}from"playwright";import Up from"fs";import Vp from"path";async function Xo(e){let t=Vp.join(e,".ripplo","index.ts");if(!Up.existsSync(t))return{appUrl:void 0,engineUrl:void 0,errors:[{message:".ripplo/index.ts not found",path:""}],valid:!1,warnings:[]};let r=await C(e);if(!r.ok)return{appUrl:void 0,engineUrl:void 0,errors:[{message:r.error,path:".ripplo/"}],valid:!1,warnings:[]};try{let n=Ie();return{appUrl:n.appUrl,engineUrl:n.engineUrl,errors:[],valid:!0,warnings:[]}}catch(n){return{appUrl:void 0,engineUrl:void 0,errors:[{message:n instanceof Error?n.message:String(n),path:""}],valid:!1,warnings:[]}}}async function Yr(e){try{await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(5e3)});return}catch{return`appUrl (${e}) is not responding. Make sure your dev server is running on that port.`}}async function Zo({appUrl:e,engineUrl:t}){let r=t.startsWith("http://")||t.startsWith("https://")?t:`${e}${t}`;try{return(await fetch(`${r}/execute-preconditions`,{body:JSON.stringify({preconditions:[]}),headers:{"Content-Type":"application/json"},method:"PUT",signal:AbortSignal.timeout(5e3)})).ok?"Engine endpoint accepted an unsigned request \u2014 webhook signature verification may be missing.":void 0}catch{return}}import{graphql as Fp}from"gql.tada";var Hp=Fp(`
212
+ `)}import Jr from"fs";import Hp from"path";import{chromium as Wp}from"playwright";import Lp from"fs";import _p from"path";async function Ko(e){let t=_p.join(e,".ripplo","index.ts");if(!Lp.existsSync(t))return{appUrl:void 0,engineUrl:void 0,errors:[{message:".ripplo/index.ts not found",path:""}],valid:!1,warnings:[]};let r=await C(e);if(!r.ok)return{appUrl:void 0,engineUrl:void 0,errors:[{message:r.error,path:".ripplo/"}],valid:!1,warnings:[]};try{let n=Ee();return{appUrl:n.appUrl,engineUrl:n.engineUrl,errors:[],valid:!0,warnings:[]}}catch(n){return{appUrl:void 0,engineUrl:void 0,errors:[{message:n instanceof Error?n.message:String(n),path:""}],valid:!1,warnings:[]}}}async function Kr(e){try{await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(5e3)});return}catch{return`appUrl (${e}) is not responding. Make sure your dev server is running on that port.`}}async function Jo({appUrl:e,engineUrl:t}){let r=t.startsWith("http://")||t.startsWith("https://")?t:`${e}${t}`;try{return(await fetch(`${r}/execute-preconditions`,{body:JSON.stringify({preconditions:[]}),headers:{"Content-Type":"application/json"},method:"PUT",signal:AbortSignal.timeout(5e3)})).ok?"Engine endpoint accepted an unsigned request \u2014 webhook signature verification may be missing.":void 0}catch{return}}import{graphql as Up}from"gql.tada";var Vp=Up(`
185
213
  query DoctorAuthViewer {
186
214
  currentUser {
187
215
  name
188
216
  email
189
217
  }
190
218
  }
191
- `),Bp="Failed to connect to Ripplo server";async function ei({serverUrl:e,token:t}){try{let r=await g({config:ke({serverUrl:e,token:t}),document:Hp,variables:void 0});return r.currentUser==null?{kind:"invalid"}:{email:r.currentUser.email,kind:"valid"}}catch(r){return(r instanceof Error?r.message:String(r)).startsWith(Bp)?{kind:"unreachable"}:{kind:"invalid"}}}function ti(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}}async function ri(e){let t=await Gp(e),r={missing:rr(e),type:"env-files"},n=await Kp(),o=Jp(),i=await C(e),s=Qp(i),a=Zp(i),l=await qp(e,i),c=zp(e),p=await em(t,i);return[t,r,n,...p,s,a,l,c,o]}async function qp(e,t){if(!t.ok)return{status:"missing",type:"lockfile"};let r=await Nt({cwd:e});return{status:Ot({compiled:t.result,existing:r}),type:"lockfile"}}function zp(e){let t=Wp.join(e,".git","hooks","pre-commit");return Xr.existsSync(t)?{installed:Xr.readFileSync(t,"utf8").includes("ripplo compile --check"),type:"pre-commit-hook"}:{installed:!1,type:"pre-commit-hook"}}async function Gp(e){let t=await Xo(e);return t.valid?{missingFields:[],type:"settings",valid:!0}:{missingFields:t.errors.map(n=>n.path).filter(n=>n.length>0),type:"settings",valid:!1}}async function Kp(){let e=Z();if(e==null||e.length===0)return{email:void 0,status:"missing",type:"token"};let t=await ei({serverUrl:Q(),token:e});return t.kind==="valid"?{email:t.email,status:"valid",type:"token"}:{email:void 0,status:t.kind,type:"token"}}function Jp(){let e=Mp.executablePath();return{installed:Xr.existsSync(e),type:"browser"}}function Qp(e){if(!e.ok)return{errorCount:1,errors:[{message:e.error,path:""}],found:!1,type:"preconditions-validation",valid:!1};let t=Yp(e.result.preconditions,e.result.tests);return{errorCount:t.length,errors:t,found:!0,type:"preconditions-validation",valid:t.length===0}}function Yp(e,t){let r=[],n=new Set(Object.keys(e));return Object.entries(e).forEach(([o,i])=>{(i.depends??[]).forEach(s=>{n.has(s)||r.push({message:`Depends on non-existent precondition "${s}"`,path:`preconditions.${o}.depends`})})}),t.forEach(o=>{Object.entries(o.requiresKeys).forEach(([i,s])=>{n.has(s)||r.push({message:`Test "${o.slug}" requires non-existent precondition "${s}" (as ${i})`,path:`tests.${o.slug}.requiresKeys.${i}`})})}),Xp(e).forEach(o=>{r.push({message:o,path:"preconditions"})}),r}function Xp(e){let t=new Set,r=new Set,n=[];function o(i){if(r.has(i)){n.push(`Precondition "${i}" has a circular dependency`);return}t.has(i)||(t.add(i),r.add(i),(e[i]?.depends??[]).forEach(s=>{o(s)}),r.delete(i))}return Object.keys(e).forEach(i=>{o(i)}),n}function Zp(e){if(!e.ok)return{invalidNames:[],invalidWorkflows:[],total:0,type:"workflows",valid:0};let t=e.result.tests.length;return{invalidNames:[],invalidWorkflows:[],total:t,type:"workflows",valid:t}}async function em(e,t){if(!e.valid||!t.ok)return[];let r;try{let a=Ie();r={appUrl:a.appUrl,engineUrl:a.engineUrl,webhookSecret:a.webhookSecret}}catch{return[]}let n=[],o=await Yr(r.appUrl)==null;n.push({appUrl:r.appUrl,reachable:o,type:"dev-server"});let i=await Qo(process.cwd());n.push(i);let s=await tm(r,t);return n.push(...s),n}async function tm(e,t){let r=t.ok?Object.keys(t.result.preconditions).length:0,n=e.engineUrl.length>0,o={configured:n,count:r,endpointReachable:void 0,type:"preconditions"};if(r===0||!n)return[o];let i=nm(e.appUrl,e.engineUrl);if(i==null)return[o];let s=await Yr(i)==null,a=[];if(a.push({configured:!0,count:r,endpointReachable:s,type:"preconditions"}),ni(e.engineUrl)&&a.push({reachable:s,type:"engine-endpoint",url:i}),!s)return a;let l=await Zo({appUrl:e.appUrl,engineUrl:e.engineUrl});a.push({rejectsUnsigned:l==null,type:"webhook-verification"});let c=await rm({engineUrl:i,webhookSecret:e.webhookSecret});return a.push({status:c,type:"adapter-enabled",url:i}),a}async function rm({engineUrl:e,webhookSecret:t}){if(t.length===0)return"no-secret";let r=JSON.stringify({preconditions:[]});try{let n=await fetch(`${e}/execute-preconditions`,{body:r,headers:{"Content-Type":"application/json",...ve({body:r,secret:t})},method:"PUT",signal:AbortSignal.timeout(5e3)});return n.status===404?"disabled":n.status===401||n.status===403?"bad-secret":"enabled"}catch{return"unreachable"}}function ni(e){return e.startsWith("http://")||e.startsWith("https://")}function nm(e,t){return ni(t)?t:`${e}${t}`}async function oi(){let e=process.cwd(),t=await ri(e),r=t.map(o=>Yo(o));process.stdout.write(r.join(`
219
+ `),Fp="Failed to connect to Ripplo server";async function Qo({serverUrl:e,token:t}){try{let r=await g({config:be({serverUrl:e,token:t}),document:Vp,variables:void 0});return r.currentUser==null?{kind:"invalid"}:{email:r.currentUser.email,kind:"valid"}}catch(r){return(r instanceof Error?r.message:String(r)).startsWith(Fp)?{kind:"unreachable"}:{kind:"invalid"}}}function Yo(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}}async function Xo(e){let t=await qp(e),r={missing:rr(e),type:"env-files"},n=await zp(),o=Gp(),i=await C(e),s=Kp(i),a=Yp(i),l=await Bp(e,i),c=Mp(e),p=await Xp(t,i);return[t,r,n,...p,s,a,l,c,o]}async function Bp(e,t){if(!t.ok)return{status:"missing",type:"lockfile"};let r=await Nt({cwd:e});return{status:Ot({compiled:t.result,existing:r}),type:"lockfile"}}function Mp(e){let t=Hp.join(e,".git","hooks","pre-commit");return Jr.existsSync(t)?{installed:Jr.readFileSync(t,"utf8").includes("ripplo compile --check"),type:"pre-commit-hook"}:{installed:!1,type:"pre-commit-hook"}}async function qp(e){let t=await Ko(e);return t.valid?{missingFields:[],type:"settings",valid:!0}:{missingFields:t.errors.map(n=>n.path).filter(n=>n.length>0),type:"settings",valid:!1}}async function zp(){let e=Z();if(e==null||e.length===0)return{email:void 0,status:"missing",type:"token"};let t=await Qo({serverUrl:Q(),token:e});return t.kind==="valid"?{email:t.email,status:"valid",type:"token"}:{email:void 0,status:t.kind,type:"token"}}function Gp(){let e=Wp.executablePath();return{installed:Jr.existsSync(e),type:"browser"}}function Kp(e){if(!e.ok)return{errorCount:1,errors:[{message:e.error,path:""}],found:!1,type:"preconditions-validation",valid:!1};let t=Jp(e.result.preconditions,e.result.tests);return{errorCount:t.length,errors:t,found:!0,type:"preconditions-validation",valid:t.length===0}}function Jp(e,t){let r=[],n=new Set(Object.keys(e));return Object.entries(e).forEach(([o,i])=>{(i.depends??[]).forEach(s=>{n.has(s)||r.push({message:`Depends on non-existent precondition "${s}"`,path:`preconditions.${o}.depends`})})}),t.forEach(o=>{Object.entries(o.requiresKeys).forEach(([i,s])=>{n.has(s)||r.push({message:`Test "${o.slug}" requires non-existent precondition "${s}" (as ${i})`,path:`tests.${o.slug}.requiresKeys.${i}`})})}),Qp(e).forEach(o=>{r.push({message:o,path:"preconditions"})}),r}function Qp(e){let t=new Set,r=new Set,n=[];function o(i){if(r.has(i)){n.push(`Precondition "${i}" has a circular dependency`);return}t.has(i)||(t.add(i),r.add(i),(e[i]?.depends??[]).forEach(s=>{o(s)}),r.delete(i))}return Object.keys(e).forEach(i=>{o(i)}),n}function Yp(e){if(!e.ok)return{invalidNames:[],invalidWorkflows:[],total:0,type:"workflows",valid:0};let t=e.result.tests.length;return{invalidNames:[],invalidWorkflows:[],total:t,type:"workflows",valid:t}}async function Xp(e,t){if(!e.valid||!t.ok)return[];let r;try{let a=Ee();r={appUrl:a.appUrl,engineUrl:a.engineUrl,webhookSecret:a.webhookSecret}}catch{return[]}let n=[],o=await Kr(r.appUrl)==null;n.push({appUrl:r.appUrl,reachable:o,type:"dev-server"});let i=await zo(process.cwd());n.push(i);let s=await Zp(r,t);return n.push(...s),n}async function Zp(e,t){let r=t.ok?Object.keys(t.result.preconditions).length:0,n=e.engineUrl.length>0,o={configured:n,count:r,endpointReachable:void 0,type:"preconditions"};if(r===0||!n)return[o];let i=tm(e.appUrl,e.engineUrl);if(i==null)return[o];let s=await Kr(i)==null,a=[];if(a.push({configured:!0,count:r,endpointReachable:s,type:"preconditions"}),Zo(e.engineUrl)&&a.push({reachable:s,type:"engine-endpoint",url:i}),!s)return a;let l=await Jo({appUrl:e.appUrl,engineUrl:e.engineUrl});a.push({rejectsUnsigned:l==null,type:"webhook-verification"});let c=await em({engineUrl:i,webhookSecret:e.webhookSecret});return a.push({status:c,type:"adapter-enabled",url:i}),a}async function em({engineUrl:e,webhookSecret:t}){if(t.length===0)return"no-secret";let r=JSON.stringify({preconditions:[]});try{let n=await fetch(`${e}/execute-preconditions`,{body:r,headers:{"Content-Type":"application/json",...we({body:r,secret:t})},method:"PUT",signal:AbortSignal.timeout(5e3)});return n.status===404?"disabled":n.status===401||n.status===403?"bad-secret":"enabled"}catch{return"unreachable"}}function Zo(e){return e.startsWith("http://")||e.startsWith("https://")}function tm(e,t){return Zo(t)?t:`${e}${t}`}async function ei(){let e=process.cwd(),t=await Xo(e),r=t.map(o=>Go(o));process.stdout.write(r.join(`
192
220
  `)+`
193
- `);let n=t.some(o=>ti(o));process.exit(n?1:0)}import{graphql as Vt,readFragment as dm}from"gql.tada";import im from"crypto";import{graphql as sm}from"gql.tada";import Zr from"fs";import en from"path";function Ut(e){try{let t=en.join(om(e),"HEAD"),r=Zr.readFileSync(t,"utf8").trim(),n="ref: refs/heads/";return r.startsWith(n)?r.slice(n.length):r.length===0?void 0:r.slice(0,7)}catch{return}}function om(e){let t=en.join(e,".git");if(Zr.statSync(t).isDirectory())return t;let n=Zr.readFileSync(t,"utf8").trim(),o="gitdir:";if(!n.startsWith(o))throw new Error(`unrecognized .git file at ${t}`);let i=n.slice(o.length).trim();if(i.length===0)throw new Error(`empty gitdir pointer at ${t}`);return en.resolve(e,i)}var am=sm(`
221
+ `);let n=t.some(o=>Yo(o));process.exit(n?1:0)}import{graphql as at,readFragment as cm}from"gql.tada";import nm from"crypto";import{graphql as om}from"gql.tada";import Qr from"fs";import Yr from"path";function Ut(e){try{let t=Yr.join(rm(e),"HEAD"),r=Qr.readFileSync(t,"utf8").trim(),n="ref: refs/heads/";return r.startsWith(n)?r.slice(n.length):r.length===0?void 0:r.slice(0,7)}catch{return}}function rm(e){let t=Yr.join(e,".git");if(Qr.statSync(t).isDirectory())return t;let n=Qr.readFileSync(t,"utf8").trim(),o="gitdir:";if(!n.startsWith(o))throw new Error(`unrecognized .git file at ${t}`);let i=n.slice(o.length).trim();if(i.length===0)throw new Error(`empty gitdir pointer at ${t}`);return Yr.resolve(e,i)}var im=om(`
194
222
  mutation SyncDevSession(
195
223
  $projectId: String!
196
224
  $cwd: String!
@@ -206,61 +234,64 @@ ${t.join(`
206
234
  workflows: $workflows
207
235
  ) {
208
236
  id
237
+ hooksPaused
209
238
  }
210
239
  }
211
- `);async function st(e){let t=lm(e.compiled);return um({config:e.config,cwd:e.cwd,payload:t})}async function Ce(e,t){let r=await C(e);if(!r.ok)throw new Error(`DSL compilation failed: ${r.error}`);let n=await st({compiled:r.result,config:t,cwd:e});return{compiled:r.result,devSessionId:n.devSessionId}}function lm(e){return{preconditions:Object.entries(e.preconditions).map(([t,r])=>({depends:[...r.depends??[]],description:r.description,name:t,returns:[...r.returns??[]]})),workflows:e.tests.map(t=>({expectedOutcome:t.expectedOutcome,name:t.name,preconditions:[...t.preconditions],requiresKeys:Object.entries(t.requiresKeys).map(([r,n])=>({namespace:r,preconditionName:n})),slug:t.slug,spec:t.implemented?JSON.stringify(t.spec):null}))}}function cm(e){return im.createHash("sha256").update(JSON.stringify(e)).digest("hex")}async function um({config:e,cwd:t,payload:r}){let n=cm(r),o=Ut(t),i=await g({config:e,document:am,variables:{branch:o??null,cwd:t,preconditions:r.preconditions.map(s=>({depends:[...s.depends],description:s.description,name:s.name,returns:[...s.returns]})),projectId:e.projectId,workflows:r.workflows.map(s=>({expectedOutcome:s.expectedOutcome,name:s.name,preconditions:[...s.preconditions],requiresKeys:s.requiresKeys.map(a=>({namespace:a.namespace,preconditionName:a.preconditionName})),slug:s.slug,spec:s.spec??null}))}});if(i.syncDevSession==null)throw new Error("syncDevSession returned null");return{devSessionId:i.syncDevSession.id,hash:n}}import ii from"figures";var at={cross:ii.cross,tick:ii.tick};var ai=Vt(`
240
+ `);async function it(e){let t=sm(e.compiled);return lm({config:e.config,cwd:e.cwd,payload:t})}async function xe(e,t){let r=await C(e);if(!r.ok)throw new Error(`DSL compilation failed: ${r.error}`);let n=await it({compiled:r.result,config:t,cwd:e});return{compiled:r.result,devSessionId:n.devSessionId}}function sm(e){return{preconditions:Object.entries(e.preconditions).map(([t,r])=>({depends:[...r.depends??[]],description:r.description,name:t,returns:[...r.returns??[]]})),workflows:e.tests.map(t=>({expectedOutcome:t.expectedOutcome,name:t.name,preconditions:[...t.preconditions],requiresKeys:Object.entries(t.requiresKeys).map(([r,n])=>({namespace:r,preconditionName:n})),slug:t.slug,spec:t.implemented?JSON.stringify(t.spec):null}))}}function am(e){return nm.createHash("sha256").update(JSON.stringify(e)).digest("hex")}async function lm({config:e,cwd:t,payload:r}){let n=am(r),o=Ut(t),i=await g({config:e,document:im,variables:{branch:o??null,cwd:t,preconditions:r.preconditions.map(s=>({depends:[...s.depends],description:s.description,name:s.name,returns:[...s.returns]})),projectId:e.projectId,workflows:r.workflows.map(s=>({expectedOutcome:s.expectedOutcome,name:s.name,preconditions:[...s.preconditions],requiresKeys:s.requiresKeys.map(a=>({namespace:a.namespace,preconditionName:a.preconditionName})),slug:s.slug,spec:s.spec??null}))}});if(i.syncDevSession==null)throw new Error("syncDevSession returned null");return{devSessionId:i.syncDevSession.id,hash:n,hooksPaused:i.syncDevSession.hooksPaused}}import ti from"figures";var st={cross:ti.cross,tick:ti.tick};var ni=at(`
212
241
  fragment WorkflowIdMCP on Workflow {
213
242
  id
214
243
  slug
215
244
  }
216
- `),pm=Vt(`
245
+ `),dm=at(`
217
246
  query ProjectWorkflowIdsMCP($projectId: String!, $cwd: String!) {
218
247
  project(id: $projectId) {
219
248
  id
220
249
  devSession(cwd: $cwd) {
221
250
  id
222
- }
223
- workflows {
224
- ...WorkflowIdMCP
251
+ workflows {
252
+ ...WorkflowIdMCP
253
+ }
225
254
  }
226
255
  }
227
256
  }
228
- `,[ai]),mm=Vt(`
229
- mutation CreateRunMCP($workflowId: String!, $platforms: [String!]!, $concurrency: Int!) {
230
- createRun(workflowId: $workflowId, platforms: $platforms, concurrency: $concurrency) {
257
+ `,[ni]),um=at(`
258
+ mutation SetProjectViewCLI($projectId: String!, $devSessionId: String!) {
259
+ setProjectView(projectId: $projectId, devSessionId: $devSessionId) {
260
+ id
261
+ }
262
+ }
263
+ `),pm=at(`
264
+ mutation CreateRunMCP($workflowId: String!) {
265
+ createRun(workflowId: $workflowId) {
231
266
  id
232
267
  status
233
268
  }
234
269
  }
235
- `),fm=Vt(`
270
+ `),mm=at(`
236
271
  query RunStatusMCP($id: String!) {
237
272
  run(id: $id) {
238
273
  id
239
274
  status
240
- results {
241
- id
275
+ duration
276
+ passCount
277
+ failCount
278
+ warnCount
279
+ summary
280
+ traceEntries {
281
+ title
242
282
  status
283
+ detail
243
284
  duration
244
- passCount
245
- failCount
246
- warnCount
247
- summary
248
- traceEntries {
249
- title
285
+ assertions {
286
+ description
250
287
  status
251
288
  detail
252
- duration
253
- assertions {
254
- description
255
- status
256
- detail
257
- }
258
289
  }
259
290
  }
260
291
  }
261
292
  }
262
- `);async function Ft({config:e,ids:t}){let r=await gm({config:e,ids:t}),n=await Promise.all(r.map(async s=>{try{let a=await vm({config:e,workflowId:s.workflowId});return{entry:s,ok:!0,runId:a}}catch(a){let l=a instanceof Error?a.message:String(a);return{entry:s,message:l,ok:!1}}})),o=await Promise.all(n.map(async s=>{if(!s.ok)return{id:s.entry.id,runId:"",summary:`ERROR creating run: ${s.message}
263
- ${w("setup","check auth + config")}`};let{entry:a,runId:l}=s;try{let c=await Sm({config:e,runId:l}),p=bm({config:e,id:a.id,runId:l}),m=`.ripplo/debug/${l}/`;return{id:a.id,runId:l,summary:`View run: ${p}
293
+ `);async function Vt({config:e,ids:t}){let r=await fm({config:e,ids:t}),n=await Promise.all(r.map(async s=>{try{let a=await bm({config:e,workflowId:s.workflowId});return{entry:s,ok:!0,runId:a}}catch(a){let l=a instanceof Error?a.message:String(a);return{entry:s,message:l,ok:!1}}})),o=await Promise.all(n.map(async s=>{if(!s.ok)return{id:s.entry.id,runId:"",summary:`ERROR creating run: ${s.message}
294
+ ${w("setup","check auth + config")}`};let{entry:a,runId:l}=s;try{let c=await km({config:e,runId:l}),p=wm({config:e,id:a.id,runId:l}),m=`.ripplo/debug/${l}/`;return{id:a.id,runId:l,summary:`View run: ${p}
264
295
  Debug artifacts: ${m}
265
296
  ${w("debug","read summary.txt, accessibility-tree, network.jsonl before guessing")}
266
297
 
@@ -270,10 +301,10 @@ ${s.summary}`).join(`
270
301
 
271
302
  ---
272
303
 
273
- `);return{results:o,summary:i}}async function gm({config:e,ids:t}){let r=await Ce(e.cwd,e),n=await g({config:e,document:pm,variables:{cwd:e.cwd,projectId:e.projectId}});if(n.project==null)throw new Error("Project not found");if(n.project.devSession==null)throw new Error("No active dev session. Start one by running `ripplo` (or `npx ripplo`) in your terminal, then try again.");let o=(n.project.workflows??[]).map(s=>dm(ai,s));if(t.length===0)return o.map(s=>({id:s.slug,workflowId:s.id}));let i=r.compiled.tests.map(s=>s.slug);return t.map(s=>{let a=o.find(l=>l.slug===s);if(a==null)throw new Error(ym({compiledSlugs:i,slug:s}));return{id:s,workflowId:a.id}})}function ym({compiledSlugs:e,slug:t}){if(e.includes(t))return`Test "${t}" was synced from .ripplo/ but the server didn't return it. This is likely a server-state bug \u2014 check the server logs for the SyncDevSession mutation.`;let r=hm({compiledSlugs:e,limit:3,slug:t}),n=r.length>0?`
304
+ `);return{results:o,summary:i}}async function fm({config:e,ids:t}){let r=await xe(e.cwd,e),n=await g({config:e,document:dm,variables:{cwd:e.cwd,projectId:e.projectId}});if(n.project==null)throw new Error("Project not found");let o=n.project.devSession;if(o==null)throw new Error("No active dev session. Start one by running `ripplo` (or `npx ripplo`) in your terminal, then try again.");await g({config:e,document:um,variables:{devSessionId:o.id,projectId:e.projectId}});let i=(o.workflows??[]).map(a=>cm(ni,a));if(t.length===0)return i.map(a=>({id:a.slug,workflowId:a.id}));let s=r.compiled.tests.map(a=>a.slug);return t.map(a=>{let l=i.find(c=>c.slug===a);if(l==null)throw new Error(gm({compiledSlugs:s,slug:a}));return{id:a,workflowId:l.id}})}function gm({compiledSlugs:e,slug:t}){if(e.includes(t))return`Test "${t}" was synced from .ripplo/ but the server didn't return it. This is likely a server-state bug \u2014 check the server logs for the SyncDevSession mutation.`;let r=ym({compiledSlugs:e,limit:3,slug:t}),n=r.length>0?`
274
305
  Did you mean: ${r.join(", ")}?`:"";return`Test "${t}" is not registered in this dev session.${n}
275
- Tests must be exported from .ripplo/tests/index.ts. (ripplo run already syncs on demand \u2014 restarting the dev server will not help.)`}function hm({compiledSlugs:e,limit:t,slug:r}){let n=new Set(r.split("-"));return e.map(i=>({candidate:i,score:wm(n,i)})).filter(i=>i.score>0).toSorted((i,s)=>s.score-i.score).slice(0,t).map(i=>i.candidate)}function wm(e,t){return t.split("-").filter(r=>e.has(r)).length}function bm({config:e,id:t,runId:r}){let n=e.ripploServerUrl.replace(/:3000\b/,":3001"),o=new URL(n.endsWith("/")?n:`${n}/`);return new URL(`projects/${e.projectId}/workflows/${t}/runs/${r}`,o).toString()}async function vm({config:e,workflowId:t}){let r=await g({config:e,document:mm,variables:{concurrency:1,platforms:["chromium"],workflowId:t}});if(r.createRun==null)throw new Error("Failed to create run");return r.createRun.id}var km=2e3,si=150;async function Sm({config:e,runId:t}){let r=0;for(;r<si;){await Pm(km),r+=1;let o=(await g({config:e,document:fm,variables:{id:t}})).run;if(o==null)throw new Error(`Run ${t} not found`);if(!(o.status==="pending"||o.status==="running"))return Rm({results:o.results??[],runId:t,status:o.status})}return`Run ${t} timed out after polling ${String(si)} times.`}function Rm({results:e,runId:t,status:r}){let n=[`Run ${t}: ${r.toUpperCase()}`];return e.forEach(o=>{n.push(` ${o.status}: ${String(o.passCount)} passed, ${String(o.failCount)} failed (${String(o.duration??0)}ms)`),o.summary!=null&&n.push(` Summary: ${o.summary}`),o.traceEntries.forEach(i=>{let s=i.status==="passed"?at.tick:at.cross,a=i.detail==null?"":` \u2014 ${i.detail}`;n.push(` ${s} ${i.title} (${String(i.duration)}ms)${a}`),i.assertions.forEach(l=>{let c=l.status==="passed"?at.tick:at.cross,p=l.detail==null?"":` \u2014 ${l.detail}`;n.push(` ${c} ${l.description}${p}`)})})}),n.join(`
276
- `)}function Pm(e){return new Promise(t=>{setTimeout(t,e)})}import{graphql as xm}from"gql.tada";var Cm=xm(`
306
+ Tests must be exported from .ripplo/tests/index.ts. (ripplo run already syncs on demand \u2014 restarting the dev server will not help.)`}function ym({compiledSlugs:e,limit:t,slug:r}){let n=new Set(r.split("-"));return e.map(i=>({candidate:i,score:hm(n,i)})).filter(i=>i.score>0).toSorted((i,s)=>s.score-i.score).slice(0,t).map(i=>i.candidate)}function hm(e,t){return t.split("-").filter(r=>e.has(r)).length}function wm({config:e,id:t,runId:r}){let n=e.ripploServerUrl.replace(/:3000\b/,":3001"),o=new URL(n.endsWith("/")?n:`${n}/`);return new URL(`projects/${e.projectId}/workflows/${t}/runs/${r}`,o).toString()}async function bm({config:e,workflowId:t}){let r=await g({config:e,document:pm,variables:{concurrency:1,platforms:["chromium"],workflowId:t}});if(r.createRun==null)throw new Error("Failed to create run");return r.createRun.id}var vm=2e3,ri=150;async function km({config:e,runId:t}){let r=0;for(;r<ri;){await Rm(vm),r+=1;let o=(await g({config:e,document:mm,variables:{id:t}})).run;if(o==null)throw new Error(`Run ${t} not found`);if(!(o.status==="pending"||o.status==="running"))return Sm({run:o,runId:t})}return`Run ${t} timed out after polling ${String(ri)} times.`}function Sm({run:e,runId:t}){let r=[`Run ${t}: ${e.status.toUpperCase()}`];return r.push(` ${e.status}: ${String(e.passCount)} passed, ${String(e.failCount)} failed (${String(e.duration??0)}ms)`),e.summary!=null&&r.push(` Summary: ${e.summary}`),e.traceEntries.forEach(n=>{let o=n.status==="passed"?st.tick:st.cross,i=n.detail==null?"":` \u2014 ${n.detail}`;r.push(` ${o} ${n.title} (${String(n.duration)}ms)${i}`),n.assertions.forEach(s=>{let a=s.status==="passed"?st.tick:st.cross,l=s.detail==null?"":` \u2014 ${s.detail}`;r.push(` ${a} ${s.description}${l}`)})}),r.join(`
307
+ `)}function Rm(e){return new Promise(t=>{setTimeout(t,e)})}import{graphql as Pm}from"gql.tada";var xm=Pm(`
277
308
  query DevSessionCheckPreflight($projectId: String!, $cwd: String!) {
278
309
  project(id: $projectId) {
279
310
  id
@@ -282,36 +313,38 @@ Tests must be exported from .ripplo/tests/index.ts. (ripplo run already syncs on
282
313
  }
283
314
  }
284
315
  }
285
- `);function me(){try{return _(process.cwd())}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`${t}
316
+ `);function ue(){try{return L(process.cwd())}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`${t}
286
317
  `),process.stderr.write(`${w("setup")}
287
- `),process.exit(1)}}async function fe(e){(await g({config:e,document:Cm,variables:{cwd:e.cwd,projectId:e.projectId}})).project?.devSession==null&&(process.stderr.write("No active dev session. Start `npx ripplo watch` 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(`${w("setup")}
288
- `),process.exit(1))}async function li({id:e,runs:t}){let r=me();await fe(r),process.stdout.write(`flake-detect "${e}" x${String(t)}...
289
- `);let n=await Promise.allSettled(Array.from({length:t},()=>Ft({config:r,ids:[e]}))),o=0,i=0,s=new Map;n.forEach(l=>{if(l.status==="rejected"){i++;let p=l.reason instanceof Error?l.reason.message:"unknown error";s.set(p,(s.get(p)??0)+1);return}let c=l.value.results[0];if(c==null){i++;return}if(c.summary.includes("FAILED")||c.summary.includes("ERROR")){i++;let p=c.summary.split(`
318
+ `),process.exit(1)}}async function pe(e){(await g({config:e,document:xm,variables:{cwd:e.cwd,projectId:e.projectId}})).project?.devSession==null&&(process.stderr.write("No active dev session. Start `npx ripplo watch` 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(`${w("setup")}
319
+ `),process.exit(1))}async function oi({id:e,runs:t}){let r=ue();await pe(r),process.stdout.write(`flake-detect "${e}" x${String(t)}...
320
+ `);let n=await Promise.allSettled(Array.from({length:t},()=>Vt({config:r,ids:[e]}))),o=0,i=0,s=new Map;n.forEach(l=>{if(l.status==="rejected"){i++;let p=l.reason instanceof Error?l.reason.message:"unknown error";s.set(p,(s.get(p)??0)+1);return}let c=l.value.results[0];if(c==null){i++;return}if(c.summary.includes("FAILED")||c.summary.includes("ERROR")){i++;let p=c.summary.split(`
290
321
  `).find(m=>m.includes("FAILED")||m.includes("ERROR"))??"unknown failure";s.set(p,(s.get(p)??0)+1)}else o++});let a=t>0?i/t*100:0;process.stdout.write(`${String(o)} passed / ${String(i)} failed (${a.toFixed(1)}% flake)
291
322
  `),s.size>0&&s.forEach((l,c)=>{process.stdout.write(` ${String(l)}x: ${c}
292
323
  `)}),i>0&&process.stderr.write(`${w("flake-detect","parallel safety + race conditions")}
293
- `),process.exit(i>0?1:0)}import Ht from"fs";import{graphql as Im}from"gql.tada";import Em from"fs";function ce(e){return et(e,"hooks-paused")}function $(e){return Em.existsSync(ce(e))?!1:Ve(e)}var Am=Im(`
324
+ `),process.exit(i>0?1:0)}import Ft from"fs";import{graphql as Em}from"gql.tada";import Cm from"fs";function Se(e){return et(e,"hooks-paused")}function T(e){return Cm.existsSync(Se(e))?!1:Ve(e)}var Am=Em(`
294
325
  mutation CliSetHooksPaused($projectId: String!, $paused: Boolean!) {
295
- setHooksPaused(projectId: $projectId, paused: $paused)
326
+ setHooksPaused(projectId: $projectId, paused: $paused) {
327
+ id
328
+ }
296
329
  }
297
- `);async function ci(){process.stdin.isTTY||(process.stderr.write("`ripplo hooks pause` must be invoked interactively from a terminal \u2014 refusing non-TTY invocation. Bypassing the gate is a human decision, not an agent decision.\n"),process.exit(1));let e=process.cwd();Ze(e);let t=ce(e);if(Ht.existsSync(t)){process.stdout.write("Hooks already paused. Run `ripplo hooks resume` to re-enable.\n");return}Ht.writeFileSync(t,""),await di(e,!0),process.stdout.write("Hooks paused. Pre-edit gates and stop enforcement are off until you run `ripplo hooks resume`.\n")}async function ui(){let e=process.cwd(),t=ce(e);if(!Ht.existsSync(t)){process.stdout.write(`Hooks already active.
298
- `);return}Ht.unlinkSync(t),await di(e,!1),process.stdout.write(`Hooks resumed.
299
- `)}async function di(e,t){let r;try{r=_(e)}catch{return}await g({config:r,document:Am,variables:{paused:t,projectId:r.projectId}}).catch(()=>null)}import Fe from"fs";import He from"path";import{input as fi,select as gi}from"@inquirer/prompts";import{graphql as Jm}from"gql.tada";import{exec as jm}from"child_process";import D from"fs";import U from"path";import{promisify as Tm}from"util";var $m=["@ripplo/testing"],pi=".ripplo/ripplo.lock linguist-generated=true",Nm=[".ripplo/debug/",".ripplo/.local/"],Om=Tm(jm);async function mi({cwd:e,onStep:t}){t("Scaffolding project files..."),Bm({cwd:e}),t("Updating .gitignore..."),Wm(e),t("Marking ripplo.lock as generated..."),_m(e),t("Installing dependencies...");let r=await Dm(e),n=[];if(r.ok||n.push({manualCommand:r.cmd,message:`Couldn't auto-install dev dependencies (${r.reason}). Run the command below, then run \`ripplo lint\` to compile the lockfile.`}),r.ok){t("Compiling initial lockfile...");let o=await Lm(e);o!=null&&n.push({manualCommand:void 0,message:o})}return t("Setting up browser..."),await fr(),n}async function Dm(e){let t=Um({cwd:e,pm:Hm(e)});f.info("Installing dependencies: %s",t);try{return await Om(t,{cwd:e}),{ok:!0}}catch(r){let n=r instanceof Error?r.message.split(`
300
- `)[0]??r.message:String(r);return f.warn("Install failed (%s): %s",t,n),{cmd:t,ok:!1,reason:n}}}async function Lm(e){try{let t=await C(e);if(!t.ok)return;await ae({cwd:e,result:t.result});return}catch(t){return`Couldn't compile initial lockfile: ${t instanceof Error?t.message:String(t)}.`}}function _m(e){let t=U.join(e,".gitattributes"),r=D.existsSync(t)?D.readFileSync(t,"utf8"):"";if(r.includes(pi))return;let n=r.length===0||r.endsWith(`
330
+ `);async function ii(){process.stdin.isTTY||(process.stderr.write("`ripplo hooks pause` must be invoked interactively from a terminal \u2014 refusing non-TTY invocation. Bypassing the gate is a human decision, not an agent decision.\n"),process.exit(1));let e=process.cwd();Ze(e);let t=Se(e);if(Ft.existsSync(t)){process.stdout.write("Hooks already paused. Run `ripplo hooks resume` to re-enable.\n");return}Ft.writeFileSync(t,""),await ai(e,!0),process.stdout.write("Hooks paused. Pre-edit gates and stop enforcement are off until you run `ripplo hooks resume`.\n")}async function si(){let e=process.cwd(),t=Se(e);if(!Ft.existsSync(t)){process.stdout.write(`Hooks already active.
331
+ `);return}Ft.unlinkSync(t),await ai(e,!1),process.stdout.write(`Hooks resumed.
332
+ `)}async function ai(e,t){let r;try{r=L(e)}catch{return}await g({config:r,document:Am,variables:{paused:t,projectId:r.projectId}}).catch(()=>null)}import Fe from"fs";import He from"path";import{input as di,select as ui}from"@inquirer/prompts";import{graphql as Km}from"gql.tada";import{exec as Im}from"child_process";import D from"fs";import _ from"path";import{promisify as Tm}from"util";var $m=["@ripplo/testing"],li=".ripplo/ripplo.lock linguist-generated=true",jm=[".ripplo/debug/",".ripplo/.local/"],Nm=Tm(Im);async function ci({cwd:e,onStep:t}){t("Scaffolding project files..."),Hm({cwd:e}),t("Updating .gitignore..."),Wm(e),t("Marking ripplo.lock as generated..."),Lm(e),t("Installing dependencies...");let r=await Om(e),n=[];if(r.ok||n.push({manualCommand:r.cmd,message:`Couldn't auto-install dev dependencies (${r.reason}). Run the command below, then run \`ripplo lint\` to compile the lockfile.`}),r.ok){t("Compiling initial lockfile...");let o=await Dm(e);o!=null&&n.push({manualCommand:void 0,message:o})}return t("Setting up browser..."),await fr(),n}async function Om(e){let t=_m({cwd:e,pm:Fm(e)});y.info("Installing dependencies: %s",t);try{return await Nm(t,{cwd:e}),{ok:!0}}catch(r){let n=r instanceof Error?r.message.split(`
333
+ `)[0]??r.message:String(r);return y.warn("Install failed (%s): %s",t,n),{cmd:t,ok:!1,reason:n}}}async function Dm(e){try{let t=await C(e);if(!t.ok)return;await ae({cwd:e,result:t.result});return}catch(t){return`Couldn't compile initial lockfile: ${t instanceof Error?t.message:String(t)}.`}}function Lm(e){let t=_.join(e,".gitattributes"),r=D.existsSync(t)?D.readFileSync(t,"utf8"):"";if(r.includes(li))return;let n=r.length===0||r.endsWith(`
301
334
  `)?"":`
302
- `;D.writeFileSync(t,`${r}${n}${pi}
303
- `)}function Um({cwd:e,pm:t}){let r=$m.join(" ");return t==="pnpm"?Vm(e)?`pnpm add -wD ${r}`:`pnpm add -D ${r}`:t==="yarn"?Fm(e)?`yarn add -WD ${r}`:`yarn add -D ${r}`:t==="bun"?`bun add -d ${r}`:`npm install -D ${r}`}function Vm(e){return D.existsSync(U.join(e,"pnpm-workspace.yaml"))||D.existsSync(U.join(e,"pnpm-workspace.yml"))}function Fm(e){let t=U.join(e,"package.json");if(!D.existsSync(t))return!1;try{let r=JSON.parse(D.readFileSync(t,"utf8"));if(r==null||typeof r!="object"||!("workspaces"in r))return!1;let n=r.workspaces;return Array.isArray(n)||n!=null&&typeof n=="object"}catch{return!1}}function Hm(e){return D.existsSync(U.join(e,"pnpm-lock.yaml"))?"pnpm":D.existsSync(U.join(e,"yarn.lock"))?"yarn":D.existsSync(U.join(e,"bun.lockb"))||D.existsSync(U.join(e,"bun.lock"))?"bun":"npm"}function Bm({cwd:e}){let t=U.join(e,".ripplo"),r=U.join(t,"tests"),n=U.join(t,"preconditions"),o=U.join(t,"observers");D.mkdirSync(r,{recursive:!0}),D.mkdirSync(n,{recursive:!0}),D.mkdirSync(o,{recursive:!0}),lt(U.join(t,"index.ts"),Mm),lt(U.join(n,"index.ts"),qm),lt(U.join(o,"index.ts"),zm),lt(U.join(r,"index.ts"),Gm),lt(U.join(t,"tsconfig.json"),Km)}function lt(e,t){D.existsSync(e)||D.writeFileSync(e,t)}function Wm(e){let t=U.join(e,".gitignore");if(!D.existsSync(t))return;let r=D.readFileSync(t,"utf8"),n=Nm.filter(i=>!r.includes(i));if(n.length===0)return;let o=r.endsWith(`
335
+ `;D.writeFileSync(t,`${r}${n}${li}
336
+ `)}function _m({cwd:e,pm:t}){let r=$m.join(" ");return t==="pnpm"?Um(e)?`pnpm add -wD ${r}`:`pnpm add -D ${r}`:t==="yarn"?Vm(e)?`yarn add -WD ${r}`:`yarn add -D ${r}`:t==="bun"?`bun add -d ${r}`:`npm install -D ${r}`}function Um(e){return D.existsSync(_.join(e,"pnpm-workspace.yaml"))||D.existsSync(_.join(e,"pnpm-workspace.yml"))}function Vm(e){let t=_.join(e,"package.json");if(!D.existsSync(t))return!1;try{let r=JSON.parse(D.readFileSync(t,"utf8"));if(r==null||typeof r!="object"||!("workspaces"in r))return!1;let n=r.workspaces;return Array.isArray(n)||n!=null&&typeof n=="object"}catch{return!1}}function Fm(e){return D.existsSync(_.join(e,"pnpm-lock.yaml"))?"pnpm":D.existsSync(_.join(e,"yarn.lock"))?"yarn":D.existsSync(_.join(e,"bun.lockb"))||D.existsSync(_.join(e,"bun.lock"))?"bun":"npm"}function Hm({cwd:e}){let t=_.join(e,".ripplo"),r=_.join(t,"tests"),n=_.join(t,"preconditions"),o=_.join(t,"observers");D.mkdirSync(r,{recursive:!0}),D.mkdirSync(n,{recursive:!0}),D.mkdirSync(o,{recursive:!0}),lt(_.join(t,"index.ts"),Bm),lt(_.join(n,"index.ts"),Mm),lt(_.join(o,"index.ts"),qm),lt(_.join(r,"index.ts"),zm),lt(_.join(t,"tsconfig.json"),Gm)}function lt(e,t){D.existsSync(e)||D.writeFileSync(e,t)}function Wm(e){let t=_.join(e,".gitignore");if(!D.existsSync(t))return;let r=D.readFileSync(t,"utf8"),n=jm.filter(i=>!r.includes(i));if(n.length===0)return;let o=r.endsWith(`
304
337
  `)?"":`
305
338
  `;D.writeFileSync(t,r+o+n.join(`
306
339
  `)+`
307
- `)}var Mm=`import { createRipplo } from "@ripplo/testing";
340
+ `)}var Bm=`import { createRipplo } from "@ripplo/testing";
308
341
  import { preconditions } from "./preconditions/index";
309
342
  import { observers } from "./observers/index";
310
343
  import { tests } from "./tests/index";
311
344
 
312
345
  export { preconditions, observers, tests };
313
346
  export default createRipplo({ preconditions, observers, tests });
314
- `,qm=`// Declare preconditions with \`precondition(name)\` from @ripplo/testing and collect them
347
+ `,Mm=`// Declare preconditions with \`precondition(name)\` from @ripplo/testing and collect them
315
348
  // in the \`preconditions\` registry below. Every handle in this registry must be implemented
316
349
  // in your app server's \`createEngine(ripplo, { preconditions: {...}, observers: {...} })\` call.
317
350
  //
@@ -325,7 +358,7 @@ export default createRipplo({ preconditions, observers, tests });
325
358
  // export const preconditions = { authLoggedIn };
326
359
 
327
360
  export const preconditions = {};
328
- `,zm=`// Declare observers with \`observer(name)\` from @ripplo/testing and collect them in
361
+ `,qm=`// Declare observers with \`observer(name)\` from @ripplo/testing and collect them in
329
362
  // the \`observers\` registry below. Use them in tests via \`assert.backend(handle, params)\`.
330
363
  //
331
364
  // Example:
@@ -338,7 +371,7 @@ export const preconditions = {};
338
371
  // export const observers = { orgNameIs };
339
372
 
340
373
  export const observers = {};
341
- `,Gm=`// Each test file under ./tests exports a TestDefinition. Import them here and add them
374
+ `,zm=`// Each test file under ./tests exports a TestDefinition. Import them here and add them
342
375
  // to the \`tests\` array \u2014 that's what \`createRipplo({ ..., tests })\` receives.
343
376
  //
344
377
  // Example:
@@ -346,7 +379,7 @@ export const observers = {};
346
379
  // export const tests = [myTest] as const;
347
380
 
348
381
  export const tests = [] as const;
349
- `,Km=`{
382
+ `,Gm=`{
350
383
  "compilerOptions": {
351
384
  "strict": true,
352
385
  "noUncheckedIndexedAccess": true,
@@ -362,68 +395,68 @@ export const tests = [] as const;
362
395
  "include": ["*.ts", "coverage.d.ts", "observers/**/*.ts", "preconditions/**/*.ts", "tests/**/*.ts"],
363
396
  "exclude": ["node_modules"]
364
397
  }
365
- `;var Qm=Jm(`
398
+ `;var Jm=Km(`
366
399
  query InitProjects {
367
400
  projects {
368
401
  id
369
402
  name
370
403
  }
371
404
  }
372
- `),tn=["../.env.local","../.env"];async function yi(e=Zm()){let t=process.cwd(),r=Z();r==null&&(process.stdout.write("Not authenticated. Run `ripplo auth login` first.\n"),process.exit(1));let n=Q();Ym(t)&&(process.stdout.write(`\`.ripplo/index.ts\` already exists at ${t}. Delete it first if you want to re-init.
373
- `),process.exit(1));let o=await Xm({serverUrl:n,token:r});o.length===0&&(process.stdout.write("No projects found. Create one in the dashboard first, then re-run `ripplo init`.\n"),process.exit(1));let i=await tf(o,e.projectId),s=await rf(t,e.envFile),a=await nf(e.appUrl),l=ef(a,e.engineUrl),c=He.resolve(He.join(t,".ripplo"),s);pn({cwd:t,envFiles:[s],projectId:i}),af({appUrl:a,engineUrl:l,filePath:c});let p=await mi({cwd:t,onStep:m=>{process.stdout.write(` ${m}
405
+ `),Xr=["../.env.local","../.env"];async function pi(e=Xm()){let t=process.cwd(),r=Z();r==null&&(process.stdout.write("Not authenticated. Run `ripplo auth login` first.\n"),process.exit(1));let n=Q();Qm(t)&&(process.stdout.write(`\`.ripplo/index.ts\` already exists at ${t}. Delete it first if you want to re-init.
406
+ `),process.exit(1));let o=await Ym({serverUrl:n,token:r});o.length===0&&(process.stdout.write("No projects found. Create one in the dashboard first, then re-run `ripplo init`.\n"),process.exit(1));let i=await ef(o,e.projectId),s=await tf(t,e.envFile),a=await rf(e.appUrl),l=Zm(a,e.engineUrl),c=He.resolve(He.join(t,".ripplo"),s);ln({cwd:t,envFiles:[s],projectId:i}),sf({appUrl:a,engineUrl:l,filePath:c});let p=await ci({cwd:t,onStep:m=>{process.stdout.write(` ${m}
374
407
  `)}});if(p.length>0){process.stdout.write(`Done with warnings:
375
408
  `),p.forEach(m=>{process.stdout.write(` - ${m.message}
376
409
  `),m.manualCommand!=null&&process.stdout.write(` run: ${m.manualCommand}
377
- `)});return}process.stdout.write("Ready. Start `npx ripplo watch` as a background process (or run `/ripplo:start` in Claude Code), then write tests in `.ripplo/tests/`.\n")}function Ym(e){return Fe.existsSync(He.join(e,".ripplo","index.ts"))}async function Xm({serverUrl:e,token:t}){return((await g({config:ke({serverUrl:e,token:t}),document:Qm,variables:void 0})).projects??[]).map(n=>({id:n.id,name:n.name}))}function Zm(){return{appUrl:void 0,engineUrl:void 0,envFile:void 0,projectId:void 0}}function ef(e,t){if(t!=null){try{new URL(t)}catch{process.stdout.write(`--engine-url is not a valid URL: ${t}
378
- `),process.exit(1)}return t}return`${e.replace(/\/$/,"")}/ripplo`}async function tf(e,t){if(t!=null){let r=e.find(n=>n.id===t);return r==null&&(process.stdout.write(`Unknown project id: ${t}
410
+ `)});return}process.stdout.write("Ready. Start `npx ripplo watch` as a background process (or run `/ripplo:start` in Claude Code), then write tests in `.ripplo/tests/`.\n")}function Qm(e){return Fe.existsSync(He.join(e,".ripplo","index.ts"))}async function Ym({serverUrl:e,token:t}){return((await g({config:be({serverUrl:e,token:t}),document:Jm,variables:void 0})).projects??[]).map(n=>({id:n.id,name:n.name}))}function Xm(){return{appUrl:void 0,engineUrl:void 0,envFile:void 0,projectId:void 0}}function Zm(e,t){if(t!=null){try{new URL(t)}catch{process.stdout.write(`--engine-url is not a valid URL: ${t}
411
+ `),process.exit(1)}return t}return`${e.replace(/\/$/,"")}/ripplo`}async function ef(e,t){if(t!=null){let r=e.find(n=>n.id===t);return r==null&&(process.stdout.write(`Unknown project id: ${t}
379
412
  `),process.exit(1)),process.stdout.write(`Using project: ${r.name} (${r.id})
380
413
  `),r.id}if(e.length===1){let r=e[0];if(r==null)throw new Error("unreachable");return process.stdout.write(`Using project: ${r.name} (${r.id})
381
- `),r.id}return gi({choices:e.map(r=>({name:r.name,value:r.id})),message:"Select a project"})}async function rf(e,t){return t!=null?(t.trim().length===0&&(process.stdout.write(`--env must not be empty
382
- `),process.exit(1)),t):of(e)}async function nf(e){if(e!=null){try{new URL(e)}catch{process.stdout.write(`--app-url is not a valid URL: ${e}
383
- `),process.exit(1)}return e}return sf()}async function of(e){let t=He.join(e,".ripplo"),r=tn.find(o=>Fe.existsSync(He.resolve(t,o))),n=await gi({choices:[...tn.map(o=>({name:r===o?`${o} (detected)`:o,value:o})),{name:"custom path",value:"__custom__"}],default:r??tn[0],message:"Which env file should ripplo write RIPPLO_APP_URL, RIPPLO_ENGINE_URL, RIPPLO_WEBHOOK_SECRET, and ENABLE_RIPPLO_TESTING to?"});return n!=="__custom__"?n:fi({message:"Path to env file (relative to .ripplo/, e.g. ../apps/server/.env)",validate:o=>o.trim().length>0?!0:"required"})}async function sf(){return fi({default:"http://localhost:3000",message:"Where does your dev server run? (RIPPLO_APP_URL)",validate:e=>{try{return new URL(e),!0}catch{return"must be a valid URL"}}})}function af({appUrl:e,engineUrl:t,filePath:r}){Fe.mkdirSync(He.dirname(r),{recursive:!0});let n=Fe.existsSync(r)?Fe.readFileSync(r,"utf8"):"",o=[];if(/^RIPPLO_APP_URL=/m.test(n)||o.push(`RIPPLO_APP_URL=${e}`),/^RIPPLO_ENGINE_URL=/m.test(n)||o.push(`RIPPLO_ENGINE_URL=${t}`),/^RIPPLO_WEBHOOK_SECRET=/m.test(n)||o.push(`RIPPLO_WEBHOOK_SECRET=${Ir()}`),/^ENABLE_RIPPLO_TESTING=/m.test(n)||o.push("ENABLE_RIPPLO_TESTING=true"),o.length===0)return;let i=n.length===0||n.endsWith(`
414
+ `),r.id}return ui({choices:e.map(r=>({name:r.name,value:r.id})),message:"Select a project"})}async function tf(e,t){return t!=null?(t.trim().length===0&&(process.stdout.write(`--env must not be empty
415
+ `),process.exit(1)),t):nf(e)}async function rf(e){if(e!=null){try{new URL(e)}catch{process.stdout.write(`--app-url is not a valid URL: ${e}
416
+ `),process.exit(1)}return e}return of()}async function nf(e){let t=He.join(e,".ripplo"),r=Xr.find(o=>Fe.existsSync(He.resolve(t,o))),n=await ui({choices:[...Xr.map(o=>({name:r===o?`${o} (detected)`:o,value:o})),{name:"custom path",value:"__custom__"}],default:r??Xr[0],message:"Which env file should ripplo write RIPPLO_APP_URL, RIPPLO_ENGINE_URL, RIPPLO_WEBHOOK_SECRET, and ENABLE_RIPPLO_TESTING to?"});return n!=="__custom__"?n:di({message:"Path to env file (relative to .ripplo/, e.g. ../apps/server/.env)",validate:o=>o.trim().length>0?!0:"required"})}async function of(){return di({default:"http://localhost:3000",message:"Where does your dev server run? (RIPPLO_APP_URL)",validate:e=>{try{return new URL(e),!0}catch{return"must be a valid URL"}}})}function sf({appUrl:e,engineUrl:t,filePath:r}){Fe.mkdirSync(He.dirname(r),{recursive:!0});let n=Fe.existsSync(r)?Fe.readFileSync(r,"utf8"):"",o=[];if(/^RIPPLO_APP_URL=/m.test(n)||o.push(`RIPPLO_APP_URL=${e}`),/^RIPPLO_ENGINE_URL=/m.test(n)||o.push(`RIPPLO_ENGINE_URL=${t}`),/^RIPPLO_WEBHOOK_SECRET=/m.test(n)||o.push(`RIPPLO_WEBHOOK_SECRET=${Ar()}`),/^ENABLE_RIPPLO_TESTING=/m.test(n)||o.push("ENABLE_RIPPLO_TESTING=true"),o.length===0)return;let i=n.length===0||n.endsWith(`
384
417
  `)?"":`
385
418
  `;Fe.writeFileSync(r,`${n}${i}${o.join(`
386
419
  `)}
387
- `)}import rn from"fs";import hi from"path";import*as wi from"remeda";import lf from"path";import{graphql as cf}from"gql.tada";var uf=cf(`
420
+ `)}import Zr from"fs";import mi from"path";import*as fi from"remeda";import af from"path";import{graphql as lf}from"gql.tada";var cf=lf(`
388
421
  mutation AutoScopeAddDirty($projectId: String!, $cwd: String!, $workflowSlugs: [String!]!) {
389
422
  addDirtyTestsToScope(projectId: $projectId, cwd: $cwd, workflowSlugs: $workflowSlugs) {
390
423
  id
391
424
  }
392
425
  }
393
- `);async function Bt({compileResult:e,cwd:t}){if(!$(t))return{addedSlugs:[]};let r=df(t);if(r.length===0)return{addedSlugs:[]};let n=new Set(e.result.tests.map(c=>c.slug)),o=r.filter(c=>n.has(c));if(o.length===0)return{addedSlugs:[]};let i;try{i=_(t)}catch{return{addedSlugs:[]}}if(await st({compiled:e.result,config:i,cwd:t}).catch(c=>(f.warn("auto-sync failed: %s",c instanceof Error?c.message:String(c)),null))==null)return{addedSlugs:[]};let a=await g({config:i,document:uf,variables:{cwd:i.cwd,projectId:i.projectId,workflowSlugs:o}}).catch(c=>(f.warn("auto-scope failed: %s",c instanceof Error?c.message:String(c)),null));return a==null?{addedSlugs:[]}:(a.addDirtyTestsToScope??[]).length===0?{addedSlugs:[]}:{addedSlugs:o}}function df(e){let t;try{t=q(["status","--porcelain","--",".ripplo/tests"],e)}catch{return[]}return t.split(`
394
- `).map(r=>r.slice(3).trim()).filter(r=>/^\.ripplo\/tests\/[^/]+\.ts$/.test(r)).map(r=>lf.basename(r,".ts")).filter(r=>r!=="index")}async function bi(e){let{ids:t,requireImplemented:r}=e,n=process.cwd(),o=await C(n);o.ok||(process.stderr.write(`Compilation failed: ${o.error}
426
+ `);async function Ht({compileResult:e,cwd:t}){if(!T(t))return{addedSlugs:[]};let r=df(t);if(r.length===0)return{addedSlugs:[]};let n=new Set(e.result.tests.map(c=>c.slug)),o=r.filter(c=>n.has(c));if(o.length===0)return{addedSlugs:[]};let i;try{i=L(t)}catch{return{addedSlugs:[]}}if(await it({compiled:e.result,config:i,cwd:t}).catch(c=>(y.warn("auto-sync failed: %s",c instanceof Error?c.message:String(c)),null))==null)return{addedSlugs:[]};let a=await g({config:i,document:cf,variables:{cwd:i.cwd,projectId:i.projectId,workflowSlugs:o}}).catch(c=>(y.warn("auto-scope failed: %s",c instanceof Error?c.message:String(c)),null));return a==null?{addedSlugs:[]}:(a.addDirtyTestsToScope??[]).length===0?{addedSlugs:[]}:{addedSlugs:o}}function df(e){let t;try{t=q(["status","--porcelain","--",".ripplo/tests"],e)}catch{return[]}return t.split(`
427
+ `).map(r=>r.slice(3).trim()).filter(r=>/^\.ripplo\/tests\/[^/]+\.ts$/.test(r)).map(r=>af.basename(r,".ts")).filter(r=>r!=="index")}async function gi(e){let{ids:t,requireImplemented:r}=e,n=process.cwd(),o=await C(n);o.ok||(process.stderr.write(`Compilation failed: ${o.error}
395
428
  `),process.stderr.write(`${w("create","DSL authoring + lint rules")}
396
- `),process.exit(1)),await ae({cwd:n,result:o.result});let i=De(o.result),s=t.length===0?i.diagnostics:i.diagnostics.filter(y=>t.includes(y.test)),a=wi.groupBy(s,y=>y.test);Object.entries(a).forEach(([y,N])=>{process.stderr.write(`
397
- ${y}
398
- `),N.forEach(T=>{let F=T.step==null?"":` at "${T.step}"`;process.stderr.write(` [${T.rule}]${F}
399
- `),process.stderr.write(` ${T.message}
400
- `)})});let l=new Set(o.builder.getUnimplemented().tests),c=r.filter(y=>l.has(y));c.length>0&&(process.stderr.write(`
429
+ `),process.exit(1)),await ae({cwd:n,result:o.result});let i=De(o.result),s=t.length===0?i.diagnostics:i.diagnostics.filter(f=>t.includes(f.test)),a=fi.groupBy(s,f=>f.test);Object.entries(a).forEach(([f,W])=>{process.stderr.write(`
430
+ ${f}
431
+ `),W.forEach($=>{let V=$.step==null?"":` at "${$.step}"`;process.stderr.write(` [${$.rule}]${V}
432
+ `),process.stderr.write(` ${$.message}
433
+ `)})});let l=new Set(o.builder.getUnimplemented().tests),c=r.filter(f=>l.has(f));c.length>0&&(process.stderr.write(`
401
434
  not-implemented
402
- `),c.forEach(y=>{process.stderr.write(` ${y} is still marked .notImplemented()
403
- `)}));let p=gf(n);p.length>0&&(process.stderr.write(`
435
+ `),c.forEach(f=>{process.stderr.write(` ${f} is still marked .notImplemented()
436
+ `)}));let p=ff(n);p.length>0&&(process.stderr.write(`
404
437
  preconditions
405
- `),p.forEach(y=>{process.stderr.write(` [hardcoded-identifier] ${y.file}
406
- `),process.stderr.write(` ${y.message}
438
+ `),p.forEach(f=>{process.stderr.write(` [hardcoded-identifier] ${f.file}
439
+ `),process.stderr.write(` ${f.message}
407
440
  `)}));let m=s.length+c.length+p.length;m>0&&(process.stderr.write(`
408
441
  ${String(m)} error(s)
409
- `),pf({diagnostics:s,hardcodeWarnings:p,stillStubbed:c}),process.exit(1));let k=o.result.tests.length,L=t.length===0?"all tests":`${String(t.length)} test(s)`;process.stdout.write(`\u2713 ripplo lint: ${L} passed (${String(k)} compiled, lockfile written)
410
- `);let{addedSlugs:j}=await Bt({compileResult:o,cwd:n});j.length>0&&process.stdout.write(`\u2713 Auto-scoped ${j.join(", ")} (dirty tests).
411
- `)}function pf({diagnostics:e,hardcodeWarnings:t,stillStubbed:r}){let n=new Set;(e.length>0||r.length>0)&&n.add("create"),t.length>0&&n.add("explore"),n.forEach(o=>process.stderr.write(`${w(o)}
412
- `))}var mf=/['"`][\w.+-]+@[\w.-]+\.[a-z]{2,}['"`]/i,ff=/['"`][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"`]/i;function gf(e){let t=hi.join(e,".ripplo","preconditions");return rn.existsSync(t)?rn.readdirSync(t).filter(n=>n.endsWith(".ts")).flatMap(n=>yf(hi.join(t,n))):[]}function yf(e){let t=rn.readFileSync(e,"utf8"),r=t.includes("ctx.uniqueEmail")||t.includes("ctx.uniqueId"),n=[],o=mf.exec(t);o!=null&&!r&&n.push({file:e,message:`hardcoded email literal ${o[0]} \u2014 use ctx.uniqueEmail() so parallel runs don't collide`});let i=ff.exec(t);return i!=null&&!r&&n.push({file:e,message:`hardcoded UUID-like literal ${i[0]} \u2014 use ctx.uniqueId() so parallel runs don't collide`}),n}import{graphql as hf}from"gql.tada";var wf=hf(`
442
+ `),uf({diagnostics:s,hardcodeWarnings:p,stillStubbed:c}),process.exit(1));let k=o.result.tests.length,j=t.length===0?"all tests":`${String(t.length)} test(s)`;process.stdout.write(`\u2713 ripplo lint: ${j} passed (${String(k)} compiled, lockfile written)
443
+ `);let{addedSlugs:N}=await Ht({compileResult:o,cwd:n});N.length>0&&process.stdout.write(`\u2713 Auto-scoped ${N.join(", ")} (dirty tests).
444
+ `)}function uf({diagnostics:e,hardcodeWarnings:t,stillStubbed:r}){let n=new Set;(e.length>0||r.length>0)&&n.add("create"),t.length>0&&n.add("explore"),n.forEach(o=>process.stderr.write(`${w(o)}
445
+ `))}var pf=/['"`][\w.+-]+@[\w.-]+\.[a-z]{2,}['"`]/i,mf=/['"`][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"`]/i;function ff(e){let t=mi.join(e,".ripplo","preconditions");return Zr.existsSync(t)?Zr.readdirSync(t).filter(n=>n.endsWith(".ts")).flatMap(n=>gf(mi.join(t,n))):[]}function gf(e){let t=Zr.readFileSync(e,"utf8"),r=t.includes("ctx.uniqueEmail")||t.includes("ctx.uniqueId"),n=[],o=pf.exec(t);o!=null&&!r&&n.push({file:e,message:`hardcoded email literal ${o[0]} \u2014 use ctx.uniqueEmail() so parallel runs don't collide`});let i=mf.exec(t);return i!=null&&!r&&n.push({file:e,message:`hardcoded UUID-like literal ${i[0]} \u2014 use ctx.uniqueId() so parallel runs don't collide`}),n}import{graphql as yf}from"gql.tada";var hf=yf(`
413
446
  query ProjectsList {
414
447
  projects {
415
448
  id
416
449
  name
417
450
  }
418
451
  }
419
- `);async function vi(){let e=Z();e==null&&(process.stderr.write("Not authenticated. Run `ripplo auth login` first.\n"),process.exit(1));let t=Q(),n=((await g({config:ke({serverUrl:t,token:e}),document:wf,variables:void 0})).projects??[]).map(o=>({id:o.id,name:o.name}));process.stdout.write(`${JSON.stringify({projects:n},null,2)}
420
- `)}async function ki(e){let t=me();await fe(t);let{results:r,summary:n}=await Ft({config:t,ids:e});process.stdout.write(`${n}
452
+ `);async function yi(){let e=Z();e==null&&(process.stderr.write("Not authenticated. Run `ripplo auth login` first.\n"),process.exit(1));let t=Q(),n=((await g({config:be({serverUrl:t,token:e}),document:hf,variables:void 0})).projects??[]).map(o=>({id:o.id,name:o.name}));process.stdout.write(`${JSON.stringify({projects:n},null,2)}
453
+ `)}async function hi(e){let t=ue();await pe(t);let{results:r,summary:n}=await Vt({config:t,ids:e});process.stdout.write(`${n}
421
454
  `);let o=r.some(i=>i.summary.includes("FAILED")||i.summary.includes("ERROR"));o&&(process.stderr.write(`Artifacts in .ripplo/debug/<runId>/ \u2014 read before re-running.
422
455
  `),process.stderr.write(`${w("debug")}
423
- `)),process.exit(o?1:0)}async function Si(){let e=process.cwd(),t=_(e);try{let r=await Ce(t.cwd,t),n=r.compiled.tests.length,o=Object.keys(r.compiled.preconditions).length;process.stdout.write(`synced ${String(n)} tests, ${String(o)} preconditions to dev session ${r.devSessionId}
456
+ `)),process.exit(o?1:0)}async function wi(){let e=process.cwd(),t=L(e);try{let r=await xe(t.cwd,t),n=r.compiled.tests.length,o=Object.keys(r.compiled.preconditions).length;process.stdout.write(`synced ${String(n)} tests, ${String(o)} preconditions to dev session ${r.devSessionId}
424
457
  `)}catch(r){let n=r instanceof Error?r.message:String(r);process.stderr.write(`ripplo sync failed: ${n}
425
458
  `),process.stderr.write(`${w("setup","verify auth + server reachability")}
426
- `),process.exit(1)}}import on from"fs";import{createClient as xf}from"graphql-sse";import{graphql as Wt}from"gql.tada";import{print as Pi}from"graphql";import{availableParallelism as bf}from"os";import vf from"p-limit";var kf=Math.max(1,Math.min(10,Math.floor(bf()/2))),Sf=vf(kf),nn=new Set;async function Ri(e){if(!nn.has(e.runId)){nn.add(e.runId);try{await Sf(()=>Rf(e))}finally{nn.delete(e.runId)}}}async function Rf({config:e,cwd:t,runId:r,workflowId:n,workflowSlug:o}){let i=await Ye({headed:!1});try{await gr({maxRuns:100});let s=t!=null&&o!=null?await Pf({cwd:t,workflowSlug:o}):void 0;await _r({baseUrl:e.appUrl,browser:i,config:e,headed:!1,preconditionNames:s,runId:r,webhookSecret:e.webhookSecret,workflowId:n})}catch(s){f.error(s,"Runner error")}finally{await i.close()}}async function Pf({cwd:e,workflowSlug:t}){let r=await C(e);if(!r.ok)return;let n=r.result.tests.find(o=>o.slug===t);if(n!=null)return n.preconditions}var Cf=15e3,Ef=Wt(`
459
+ `),process.exit(1)}}import Wt from"fs";import{createClient as Pf}from"graphql-sse";import{graphql as Bt}from"gql.tada";import{print as vi}from"graphql";import{availableParallelism as wf}from"os";import bf from"p-limit";var vf=Math.max(1,Math.min(10,Math.floor(wf()/2))),kf=bf(vf),en=new Set;async function bi(e){if(!en.has(e.runId)){en.add(e.runId);try{await kf(()=>Sf(e))}finally{en.delete(e.runId)}}}async function Sf({config:e,cwd:t,runId:r,workflowSlug:n}){let o=await Ye({headed:!1});try{await gr({maxRuns:100});let i=t!=null&&n!=null?await Rf({cwd:t,workflowSlug:n}):void 0;await Or({baseUrl:e.appUrl,browser:o,config:e,headed:!1,preconditionNames:i,runId:r,webhookSecret:e.webhookSecret})}catch(i){y.error(i,"Runner error")}finally{await o.close()}}async function Rf({cwd:e,workflowSlug:t}){let r=await C(e);if(!r.ok)return;let n=r.result.tests.find(o=>o.slug===t);if(n!=null)return n.preconditions}var xf=15e3,Cf=Bt(`
427
460
  subscription RunRequestedWatch($devSessionId: String!) {
428
461
  runRequested(devSessionId: $devSessionId) {
429
462
  runId
@@ -431,30 +464,30 @@ ${String(m)} error(s)
431
464
  workflowSlug
432
465
  }
433
466
  }
434
- `),If=Wt(`
467
+ `),Ef=Bt(`
435
468
  subscription HooksPausedWatch($projectId: String!) {
436
469
  hooksPausedRequested(projectId: $projectId) {
437
470
  paused
438
471
  projectId
439
472
  }
440
473
  }
441
- `),Af=Wt(`
474
+ `),Af=Bt(`
442
475
  mutation HeartbeatDevSessionWatch($id: String!) {
443
476
  heartbeatDevSession(id: $id) {
444
477
  id
445
478
  }
446
479
  }
447
- `),jf=Wt(`
480
+ `),If=Bt(`
448
481
  mutation EndDevSessionWatch($projectId: String!, $cwd: String!) {
449
482
  endDevSession(projectId: $projectId, cwd: $cwd) {
450
483
  id
451
484
  }
452
485
  }
453
- `);async function xi(){Hf();let e=Of(process.cwd()),t=e.cwd,r=await C(t);r.ok||(process.stderr.write(`ripplo: DSL compilation failed: ${r.error}
454
- `),process.exit(1));let n=await _f(()=>st({compiled:r.result,config:e,cwd:t})),o=Ut(t);o!=null&&f.info("watching branch %s in %s",o,t);let i=Ko(t),s=xf({headers:{Authorization:`Bearer ${e.token}`},retryAttempts:1/0,url:`${e.ripploServerUrl}/graphql`}),a=Tf({config:e,cwd:t,devSessionId:n.devSessionId,sseClient:s}),l=$f({cwd:t,projectId:e.projectId,sseClient:s}),c=Nf({config:e,devSessionId:n.devSessionId});process.stdout.write(`ripplo watch: ready as local executor for session ${n.devSessionId} (sync happens on each \`ripplo run\`; Ctrl-C to stop)
455
- `);let p=async()=>{c(),a(),l(),await Promise.race([g({config:e,document:jf,variables:{cwd:e.cwd,projectId:e.projectId}}).catch(()=>{}),new Promise(k=>setTimeout(k,3e3))]),i()},m=()=>{p().finally(()=>process.exit(0))};process.on("SIGINT",m),process.on("SIGTERM",m),process.on("SIGHUP",m),process.on("SIGBREAK",m),await new Promise(()=>{})}function Tf({config:e,cwd:t,devSessionId:r,sseClient:n}){return n.subscribe({query:Pi(Ef),variables:{devSessionId:r}},{complete:()=>{},error:o=>{f.error(o,"runRequested subscription error")},next:o=>{let i=o.data?.runRequested;i!=null&&(process.stdout.write(`ripplo: run ${i.runId} (${i.workflowSlug})
456
- `),Ri({config:e,cwd:t,runId:i.runId,workflowId:i.workflowId,workflowSlug:i.workflowSlug}).catch(s=>{f.error({err:s,runId:i.runId,workflowSlug:i.workflowSlug},"run execution failed; watch continuing")}))}})}function $f({cwd:e,projectId:t,sseClient:r}){return r.subscribe({query:Pi(If),variables:{projectId:t}},{complete:()=>{},error:n=>{f.error(n,"hooksPaused subscription error")},next:n=>{let o=n.data?.hooksPausedRequested?.paused;if(o===!0){on.writeFileSync(ce(e),"");return}o===!1&&on.existsSync(ce(e))&&on.unlinkSync(ce(e))}})}function Nf({config:e,devSessionId:t}){let r=setInterval(()=>{g({config:e,document:Af,variables:{id:t}}).catch(n=>{f.warn("heartbeat failed: %s",n instanceof Error?n.message:String(n))})},Cf);return()=>{clearInterval(r)}}function Of(e){try{return _(e)}catch(t){let r=t instanceof Error?t.message:String(t);process.stderr.write(`${r}
457
- `),process.exit(1)}}var Df=1e3,Lf=6e4;async function _f(e){let t=Date.now()+Lf,r;for(;Date.now()<t;){let n=await Uf(e);if(n.ok)return n.value;if(!Vf(n.message))throw n.error;r=n.error,await Ff(Df)}throw r instanceof Error?r:new Error("server never became ready")}async function Uf(e){try{return{ok:!0,value:await e()}}catch(t){let r=t instanceof Error?t.message:String(t);return{error:t,message:r,ok:!1}}}function Vf(e){return e.includes("status 502")||e.includes("ECONNREFUSED")||e.includes("Failed to connect")||e.includes("fetch failed")}function Ff(e){return new Promise(t=>setTimeout(t,e))}function Hf(){process.on("unhandledRejection",e=>{f.error({err:e},"unhandledRejection in watch; continuing")}),process.on("uncaughtException",e=>{f.error({err:e},"uncaughtException in watch; continuing")})}import{graphql as ct}from"gql.tada";var Bf=ct(`
486
+ `);async function ki(){Ff();let e=Nf(process.cwd()),t=e.cwd,r=await C(t);r.ok||(process.stderr.write(`ripplo: DSL compilation failed: ${r.error}
487
+ `),process.exit(1));let n=await Lf(()=>it({compiled:r.result,config:e,cwd:t}));Si({cwd:t,paused:n.hooksPaused});let o=Ut(t);o!=null&&y.info("watching branch %s in %s",o,t);let i=Mo(t),s=Pf({headers:{Authorization:`Bearer ${e.token}`},retryAttempts:1/0,url:`${e.ripploServerUrl}/graphql`}),a=Tf({config:e,cwd:t,devSessionId:n.devSessionId,sseClient:s}),l=$f({cwd:t,projectId:e.projectId,sseClient:s}),c=jf({config:e,devSessionId:n.devSessionId});process.stdout.write(`ripplo watch: ready as local executor for session ${n.devSessionId} (sync happens on each \`ripplo run\`; Ctrl-C to stop)
488
+ `);let p=async()=>{c(),a(),l(),await Promise.race([g({config:e,document:If,variables:{cwd:e.cwd,projectId:e.projectId}}).catch(()=>{}),new Promise(k=>setTimeout(k,3e3))]),i()},m=()=>{p().finally(()=>process.exit(0))};process.on("SIGINT",m),process.on("SIGTERM",m),process.on("SIGHUP",m),process.on("SIGBREAK",m),await new Promise(()=>{})}function Tf({config:e,cwd:t,devSessionId:r,sseClient:n}){return n.subscribe({query:vi(Cf),variables:{devSessionId:r}},{complete:()=>{},error:o=>{y.error(o,"runRequested subscription error")},next:o=>{let i=o.data?.runRequested;i!=null&&(process.stdout.write(`ripplo: run ${i.runId} (${i.workflowSlug})
489
+ `),bi({config:e,cwd:t,runId:i.runId,workflowId:i.workflowId,workflowSlug:i.workflowSlug}).catch(s=>{y.error({err:s,runId:i.runId,workflowSlug:i.workflowSlug},"run execution failed; watch continuing")}))}})}function Si({cwd:e,paused:t}){let r=Se(e);if(t&&!Wt.existsSync(r)){Wt.writeFileSync(r,"");return}!t&&Wt.existsSync(r)&&Wt.unlinkSync(r)}function $f({cwd:e,projectId:t,sseClient:r}){return r.subscribe({query:vi(Ef),variables:{projectId:t}},{complete:()=>{},error:n=>{y.error(n,"hooksPaused subscription error")},next:n=>{let o=n.data?.hooksPausedRequested?.paused;o!=null&&Si({cwd:e,paused:o})}})}function jf({config:e,devSessionId:t}){let r=setInterval(()=>{g({config:e,document:Af,variables:{id:t}}).catch(n=>{y.warn("heartbeat failed: %s",n instanceof Error?n.message:String(n))})},xf);return()=>{clearInterval(r)}}function Nf(e){try{return L(e)}catch(t){let r=t instanceof Error?t.message:String(t);process.stderr.write(`${r}
490
+ `),process.exit(1)}}var Of=1e3,Df=6e4;async function Lf(e){let t=Date.now()+Df,r;for(;Date.now()<t;){let n=await _f(e);if(n.ok)return n.value;if(!Uf(n.message))throw n.error;r=n.error,await Vf(Of)}throw r instanceof Error?r:new Error("server never became ready")}async function _f(e){try{return{ok:!0,value:await e()}}catch(t){let r=t instanceof Error?t.message:String(t);return{error:t,message:r,ok:!1}}}function Uf(e){return e.includes("status 502")||e.includes("ECONNREFUSED")||e.includes("Failed to connect")||e.includes("fetch failed")}function Vf(e){return new Promise(t=>setTimeout(t,e))}function Ff(){process.on("unhandledRejection",e=>{y.error({err:e},"unhandledRejection in watch; continuing")}),process.on("uncaughtException",e=>{y.error({err:e},"uncaughtException in watch; continuing")})}import{graphql as ct}from"gql.tada";var Hf=ct(`
458
491
  query ScopeStatus($projectId: String!, $cwd: String!) {
459
492
  project(id: $projectId) {
460
493
  id
@@ -487,7 +520,7 @@ ${String(m)} error(s)
487
520
  }
488
521
  }
489
522
  }
490
- `),Mf=ct(`
523
+ `),Bf=ct(`
491
524
  mutation ScopeAddDirtyTests($projectId: String!, $cwd: String!, $workflowSlugs: [String!]!) {
492
525
  addDirtyTestsToScope(projectId: $projectId, cwd: $cwd, workflowSlugs: $workflowSlugs) {
493
526
  id
@@ -497,36 +530,36 @@ ${String(m)} error(s)
497
530
  }
498
531
  }
499
532
  }
500
- `),qf=ct(`
533
+ `),Mf=ct(`
501
534
  mutation ScopeLink($id: ID!, $workflowId: String!) {
502
535
  linkScopeItem(id: $id, workflowId: $workflowId) {
503
536
  id
504
537
  }
505
538
  }
506
- `),zf=ct(`
539
+ `),qf=ct(`
507
540
  mutation ScopeRemoveMany($ids: [ID!]!) {
508
541
  removeScopeItems(ids: $ids)
509
542
  }
510
- `);async function Ci(e){let t=me();await fe(t);let n=(await g({config:t,document:Bf,variables:{cwd:t.cwd,projectId:t.projectId}})).project?.devSession?.scopeItems??[];if(e.format==="json"){process.stdout.write(`${JSON.stringify(n,null,2)}
543
+ `);async function Ri(e){let t=ue();await pe(t);let n=(await g({config:t,document:Hf,variables:{cwd:t.cwd,projectId:t.projectId}})).project?.devSession?.scopeItems??[];if(e.format==="json"){process.stdout.write(`${JSON.stringify(n,null,2)}
511
544
  `);return}if(n.length===0){process.stdout.write("No scope items. Add via `ripplo scope add <test-ids..>` (variadic) or from the dashboard.\n");return}n.forEach(o=>{let i=o.workflow;if(i==null){process.stdout.write(` [intent] (${o.id}) ${o.label??""}
512
545
  `);return}let s=i.spec==null?"stub":"implemented";process.stdout.write(` [${s}] (${o.id}) ${i.slug} \u2014 ${i.name}
513
- `)})}async function Ei({testIds:e}){let t=me();await fe(t),await Ce(t.cwd,t);let n=(await g({config:t,document:Mf,variables:{cwd:t.cwd,projectId:t.projectId,workflowSlugs:[...e]}})).addDirtyTestsToScope??[];if(n.length===0){process.stdout.write(`No scope items added.
546
+ `)})}async function Pi({testIds:e}){let t=ue();await pe(t),await xe(t.cwd,t);let n=(await g({config:t,document:Bf,variables:{cwd:t.cwd,projectId:t.projectId,workflowSlugs:[...e]}})).addDirtyTestsToScope??[];if(n.length===0){process.stdout.write(`No scope items added.
514
547
  `);return}let o=n.map(i=>i.workflow?.slug??"?").join(", ");process.stdout.write(`Added ${String(n.length)} scope item(s): ${o}
515
- `)}async function Ii({id:e,testId:t}){let r=me();await fe(r),await Ce(r.cwd,r);let n=await Gf({cfg:r,slug:t});await g({config:r,document:qf,variables:{id:e,workflowId:n}}),process.stdout.write(`Linked scope item ${e} to ${t}
516
- `)}async function Ai({ids:e}){let t=me();await fe(t);let n=(await g({config:t,document:zf,variables:{ids:[...e]}})).removeScopeItems??0;process.stdout.write(`Removed ${String(n)} scope item(s)
517
- `)}async function Gf({cfg:e,slug:t}){let n=(await g({config:e,document:Wf,variables:{cwd:e.cwd,projectId:e.projectId,slug:t}})).project?.devSession?.workflows?.[0];return n==null&&(process.stderr.write(`No workflow found with id "${t}". Create a stub first via the testing DSL.
548
+ `)}async function xi({id:e,testId:t}){let r=ue();await pe(r),await xe(r.cwd,r);let n=await zf({cfg:r,slug:t});await g({config:r,document:Mf,variables:{id:e,workflowId:n}}),process.stdout.write(`Linked scope item ${e} to ${t}
549
+ `)}async function Ci({ids:e}){let t=ue();await pe(t);let n=(await g({config:t,document:qf,variables:{ids:[...e]}})).removeScopeItems??0;process.stdout.write(`Removed ${String(n)} scope item(s)
550
+ `)}async function zf({cfg:e,slug:t}){let n=(await g({config:e,document:Wf,variables:{cwd:e.cwd,projectId:e.projectId,slug:t}})).project?.devSession?.workflows?.[0];return n==null&&(process.stderr.write(`No workflow found with id "${t}". Create a stub first via the testing DSL.
518
551
  `),process.stderr.write(`${w("create")}
519
- `),process.exit(1)),n.id}async function ji(e){let t=process.cwd(),r=await C(t);r.ok||(process.stderr.write(`Compilation failed: ${r.error}
552
+ `),process.exit(1)),n.id}async function Ei(e){let t=process.cwd(),r=await C(t);r.ok||(process.stderr.write(`Compilation failed: ${r.error}
520
553
  `),process.exit(1));let{tests:n}=r.builder.getUnimplemented();if(e.format==="summary"){n.length>0&&process.stdout.write(`tests: ${n.join(", ")}
521
554
  `);return}let o={tests:n.map(i=>({id:i,implemented:!1}))};process.stdout.write(`${JSON.stringify(o,null,2)}
522
- `)}import{z as Kf}from"zod";function E(e,t){let r=Kf.custom(n=>typeof n=="object"&&n!==null&&"hook_event_name"in n&&n.hook_event_name===e);return{event:e,run:async n=>await t(r.parse(n))??void 0}}import{createHash as TP}from"crypto";import Ti from"picomatch";function Jf(e){return q(["diff","--name-only","HEAD"],e).split(`
523
- `).filter(t=>t.length>0)}function $i({cwd:e,ignoreGlobs:t,watchGlobs:r}){let n=Ti([...r]),o=Ti([...t]);return Jf(e).filter(i=>n(i)&&!o(i))}function Mt(e,...t){return q(["diff","--name-only","HEAD","--",...t],e).split(`
524
- `).filter(r=>r.length>0)}import{createHash as Qf}from"crypto";import{mkdirSync as Yf,readFileSync as Ni,writeFileSync as Xf}from"fs";import sn from"path";var Zf=[".ts",".tsx",".js",".jsx"];function Be(e){let t=Qf("sha256");return t.update(q(["rev-parse","HEAD"],e)),t.update("\0"),t.update(q(["diff","HEAD"],e)),t.update("\0"),q(["ls-files","--others","--exclude-standard"],e).split(`
525
- `).filter(n=>n.length>0).filter(n=>Zf.some(o=>n.endsWith(o))).toSorted((n,o)=>n.localeCompare(o)).forEach(n=>{t.update(n),t.update("\0"),t.update(eg(sn.join(e,n))),t.update("\0")}),t.digest("hex")}function We(e,t){try{return Ni(Oi(e,t),"utf8").trim()}catch{return null}}function Me(e,t,r){let n=Oi(e,t);Yf(sn.dirname(n),{recursive:!0}),Xf(n,r)}function Oi(e,t){return sn.join(e,".ripplo",".local",`${t}.hash`)}function eg(e){try{return Ni(e)}catch{return Buffer.alloc(0)}}function Pe(){return{ignorePaths:Re,watchPaths:zr}}var Di=E("UserPromptSubmit",e=>{let{cwd:t}=e;if(!x(t)||!$(t))return;let r=Be(t);if(We(t,"coverage-nudge")===r)return;let{ignorePaths:n,watchPaths:o}=Pe(),i=$i({cwd:t,ignoreGlobs:n,watchGlobs:o});if(Me(t,"coverage-nudge",r),!(i.length===0||Mt(t,".ripplo/tests",".ripplo/preconditions",".ripplo/observers").length>0))return{hookSpecificOutput:{additionalContext:`SCOPE DRIFT: ${String(i.length)} app file(s) changed without .ripplo/tests updates. ${w("scope")}`,hookEventName:"UserPromptSubmit"}}});import qt from"fs";import tg from"os";import Li from"path";var _i=E("PreToolUse",e=>{if(e.tool_name!=="ExitPlanMode"||!$(e.cwd))return;let t=rg();if(t==null)return;let r=qt.readFileSync(t,"utf8");if(!/\.ripplo\/tests|Tests to implement|No e2e coverage needed/.test(r))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Plan must cite ripplo test stubs. Add a "Tests to implement" section listing .ripplo/tests/<id>.ts per affected flow, OR add "No e2e coverage needed: <reason>" if this plan touches no user-facing behavior. ${w("explore")}`}}});function rg(){let e=Li.join(tg.homedir(),".claude","plans");return qt.existsSync(e)?qt.readdirSync(e).filter(r=>r.endsWith(".md")).map(r=>Li.join(e,r)).map(r=>({full:r,mtime:qt.statSync(r).mtimeMs})).sort((r,n)=>n.mtime-r.mtime)[0]?.full??null:null}var Ui=E("UserPromptSubmit",async e=>{if(e.permission_mode!=="plan"||!x(e.cwd)||!$(e.cwd))return;let t=await C(e.cwd);if(!t.ok)return;let{tests:r}=t.builder.getUnimplemented(),n=['Plan must include "Tests to implement" with .ripplo/tests/<id>.ts per affected flow (ExitPlanMode blocks otherwise).'];return r.length>0&&n.push(`Existing stubs: ${r.join(", ")}`),{hookSpecificOutput:{additionalContext:n.join(`
526
- `),hookEventName:"UserPromptSubmit"}}});import ng from"path";import Vi from"picomatch";import{z as Fi}from"zod";var og=Fi.looseObject({file_path:Fi.string()}),Hi=E("PostToolUse",async e=>{let t=og.safeParse(e.tool_input);if(!t.success)return;let r=t.data.file_path,{cwd:n}=e;if(!x(n)||!$(n))return;let o=ng.relative(n,r);if(o.startsWith(".."))return;let{ignorePaths:i,watchPaths:s}=Pe(),a=Vi([...s]),l=Vi([...i]);if(!a(o)||l(o))return;let c=await C(n);if(!c.ok)return;let{tests:p}=c.builder.getUnimplemented();if(p.length!==0)return{hookSpecificOutput:{additionalContext:`Reminder: .notImplemented() stubs still present \u2014 tests: ${p.join(", ")}`,hookEventName:"PostToolUse"}}});import{z as Bi}from"zod";var ig=Bi.looseObject({file_path:Bi.string()}),Wi=E("PostToolUse",async e=>{let t=ig.safeParse(e.tool_input);if(!t.success)return;let r=t.data.file_path;if(!/\/\.ripplo\/.*\.ts$/.test(r))return;let{cwd:n}=e;if(!x(n)||!$(n))return;let o=await C(n);if(!o.ok)return{decision:"block",reason:`Compilation failed: ${o.error}
527
- ${w("create","DSL authoring + lint rules")}`};await ae({cwd:n,result:o.result}),_e({cwd:n});let{diagnostics:i}=De(o.result);if(i.length===0){let{addedSlugs:a}=await Bt({compileResult:o,cwd:n});return a.length===0?void 0:{hookSpecificOutput:{additionalContext:`Auto-scoped ${a.join(", ")} (dirty tests).`,hookEventName:"PostToolUse"}}}return{decision:"block",reason:`${i.map(a=>{let l=a.step==null?"":` at "${a.step}"`;return` [${a.rule}]${l} ${a.test}: ${a.message}`}).join(`
555
+ `)}import{z as Gf}from"zod";function E(e,t){let r=Gf.custom(n=>typeof n=="object"&&n!==null&&"hook_event_name"in n&&n.hook_event_name===e);return{event:e,run:async n=>await t(r.parse(n))??void 0}}import{createHash as qP}from"crypto";import Ai from"picomatch";function Kf(e){return q(["diff","--name-only","HEAD"],e).split(`
556
+ `).filter(t=>t.length>0)}function Ii({cwd:e,ignoreGlobs:t,watchGlobs:r}){let n=Ai([...r]),o=Ai([...t]);return Kf(e).filter(i=>n(i)&&!o(i))}function Mt(e,...t){return q(["diff","--name-only","HEAD","--",...t],e).split(`
557
+ `).filter(r=>r.length>0)}import{createHash as Jf}from"crypto";import{mkdirSync as Qf,readFileSync as Ti,writeFileSync as Yf}from"fs";import tn from"path";var Xf=[".ts",".tsx",".js",".jsx"];function We(e){let t=Jf("sha256");return t.update(q(["rev-parse","HEAD"],e)),t.update("\0"),t.update(q(["diff","HEAD"],e)),t.update("\0"),q(["ls-files","--others","--exclude-standard"],e).split(`
558
+ `).filter(n=>n.length>0).filter(n=>Xf.some(o=>n.endsWith(o))).toSorted((n,o)=>n.localeCompare(o)).forEach(n=>{t.update(n),t.update("\0"),t.update(Zf(tn.join(e,n))),t.update("\0")}),t.digest("hex")}function Be(e,t){try{return Ti($i(e,t),"utf8").trim()}catch{return null}}function Me(e,t,r){let n=$i(e,t);Qf(tn.dirname(n),{recursive:!0}),Yf(n,r)}function $i(e,t){return tn.join(e,".ripplo",".local",`${t}.hash`)}function Zf(e){try{return Ti(e)}catch{return Buffer.alloc(0)}}function Re(){return{ignorePaths:ke,watchPaths:Br}}var ji=E("UserPromptSubmit",e=>{let{cwd:t}=e;if(!x(t)||!T(t))return;let r=We(t);if(Be(t,"coverage-nudge")===r)return;let{ignorePaths:n,watchPaths:o}=Re(),i=Ii({cwd:t,ignoreGlobs:n,watchGlobs:o});if(Me(t,"coverage-nudge",r),!(i.length===0||Mt(t,".ripplo/tests",".ripplo/preconditions",".ripplo/observers").length>0))return{hookSpecificOutput:{additionalContext:`SCOPE DRIFT: ${String(i.length)} app file(s) changed without .ripplo/tests updates. ${w("scope")}`,hookEventName:"UserPromptSubmit"}}});import qt from"fs";import eg from"os";import Ni from"path";var Oi=E("PreToolUse",e=>{if(e.tool_name!=="ExitPlanMode"||!T(e.cwd))return;let t=tg();if(t==null)return;let r=qt.readFileSync(t,"utf8");if(!/\.ripplo\/tests|Tests to implement|No e2e coverage needed/.test(r))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Plan must cite ripplo test stubs. Add a "Tests to implement" section listing .ripplo/tests/<id>.ts per affected flow, OR add "No e2e coverage needed: <reason>" if this plan touches no user-facing behavior. ${w("explore")}`}}});function tg(){let e=Ni.join(eg.homedir(),".claude","plans");return qt.existsSync(e)?qt.readdirSync(e).filter(r=>r.endsWith(".md")).map(r=>Ni.join(e,r)).map(r=>({full:r,mtime:qt.statSync(r).mtimeMs})).sort((r,n)=>n.mtime-r.mtime)[0]?.full??null:null}var Di=E("UserPromptSubmit",async e=>{if(e.permission_mode!=="plan"||!x(e.cwd)||!T(e.cwd))return;let t=await C(e.cwd);if(!t.ok)return;let{tests:r}=t.builder.getUnimplemented(),n=['Plan must include "Tests to implement" with .ripplo/tests/<id>.ts per affected flow (ExitPlanMode blocks otherwise).'];return r.length>0&&n.push(`Existing stubs: ${r.join(", ")}`),{hookSpecificOutput:{additionalContext:n.join(`
559
+ `),hookEventName:"UserPromptSubmit"}}});import rg from"path";import Li from"picomatch";import{z as _i}from"zod";var ng=_i.looseObject({file_path:_i.string()}),Ui=E("PostToolUse",async e=>{let t=ng.safeParse(e.tool_input);if(!t.success)return;let r=t.data.file_path,{cwd:n}=e;if(!x(n)||!T(n))return;let o=rg.relative(n,r);if(o.startsWith(".."))return;let{ignorePaths:i,watchPaths:s}=Re(),a=Li([...s]),l=Li([...i]);if(!a(o)||l(o))return;let c=await C(n);if(!c.ok)return;let{tests:p}=c.builder.getUnimplemented();if(p.length!==0)return{hookSpecificOutput:{additionalContext:`Reminder: .notImplemented() stubs still present \u2014 tests: ${p.join(", ")}`,hookEventName:"PostToolUse"}}});import{z as Vi}from"zod";var og=Vi.looseObject({file_path:Vi.string()}),Fi=E("PostToolUse",async e=>{let t=og.safeParse(e.tool_input);if(!t.success)return;let r=t.data.file_path;if(!/\/\.ripplo\/.*\.ts$/.test(r))return;let{cwd:n}=e;if(!x(n)||!T(n))return;let o=await C(n);if(!o.ok)return{decision:"block",reason:`Compilation failed: ${o.error}
560
+ ${w("create","DSL authoring + lint rules")}`};await ae({cwd:n,result:o.result}),_e({cwd:n});let{diagnostics:i}=De(o.result);if(i.length===0){let{addedSlugs:a}=await Ht({compileResult:o,cwd:n});return a.length===0?void 0:{hookSpecificOutput:{additionalContext:`Auto-scoped ${a.join(", ")} (dirty tests).`,hookEventName:"PostToolUse"}}}return{decision:"block",reason:`${i.map(a=>{let l=a.step==null?"":` at "${a.step}"`;return` [${a.rule}]${l} ${a.test}: ${a.message}`}).join(`
528
561
  `)}
529
- ${w("create")}`}});import{z as Mi}from"zod";var sg=Mi.looseObject({command:Mi.string()}),ag=/\bripplo\s+hooks\s+pause\b/,qi=E("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let t=sg.safeParse(e.tool_input);if(!t.success||!ag.test(t.data.command))return;let{cwd:r}=e;if(x(r))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 watch 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 pg}from"shell-quote";import{z as Ki}from"zod";import{existsSync as lg,mkdirSync as cg,rmSync as Nx,writeFileSync as ug}from"fs";import an from"path";function zi(e,t,r){let n=Gi(e,t,r);cg(an.dirname(n),{recursive:!0}),ug(n,"")}function zt(e,t,r){return lg(Gi(e,t,r))}function dg(e,t){return an.join(e,".ripplo",".local","skills-loaded",t)}function Gi(e,t,r){return an.join(dg(e,t),r)}var mg=Ki.looseObject({command:Ki.string()}),fg="debug",gg=new Set(["tail","head","less","more","wc","sort","uniq","awk","sed","grep"]),Qi=E("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let t=mg.safeParse(e.tool_input);if(!t.success)return;let r=yg(t.data.command);if(!hg(r))return;let{cwd:n}=e;if(!x(n)||!$(n))return;let o=bg(r);if(o!=null)return Ji(`Don't pipe \`ripplo run\` through \`${o}\` \u2014 buffering filters hold all stdout until EOF, hiding live progress; if you kill the pipeline mid-run the run is orphaned on the server. Redirect to a file instead: \`ripplo run <id> > /tmp/run.log 2>&1\` and Read the file, or use \`run_in_background: true\`.`);if(!zt(n,e.session_id,fg))return Ji("Running `ripplo run` requires the `/ripplo:debug` skill loaded first. Load `/ripplo:debug` then retry \u2014 it carries the artifact-read order, the 3-strike rule, and the no-grep-piping guidance for run failures.")});function yg(e){try{return pg(e)}catch{return[]}}function hg(e){return e.some((t,r)=>t==="ripplo"&&e[r+1]==="run")}function wg(e){return typeof e=="object"&&"op"in e&&e.op==="|"}function bg(e){let t=e.find((r,n)=>{let o=e[n-1];return o!=null&&wg(o)&&typeof r=="string"&&gg.has(r)});return typeof t=="string"?t:null}function Ji(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}import vg from"path";import{z as Yi}from"zod";var kg=new Set(["Edit","Write","NotebookEdit"]),Sg=Yi.looseObject({file_path:Yi.string()}),Rg="create",Xi=E("PreToolUse",e=>{if(!kg.has(e.tool_name))return;let t=Sg.safeParse(e.tool_input);if(!t.success)return;let{cwd:r}=e;if(!x(r)||!$(r))return;let n=vg.relative(r,t.data.file_path);if(!(n.startsWith("..")||n!==".ripplo"&&!n.startsWith(".ripplo/"))&&!zt(r,e.session_id,Rg))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 Pg from"path";import Zi from"picomatch";import{graphql as xg}from"gql.tada";import{z as es}from"zod";var Cg=new Set(["Edit","Write","NotebookEdit"]),Eg=es.looseObject({file_path:es.string()}),Ig=xg(`
562
+ ${w("create")}`}});import{z as Hi}from"zod";var ig=Hi.looseObject({command:Hi.string()}),sg=/\bripplo\s+hooks\s+pause\b/,Wi=E("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let t=ig.safeParse(e.tool_input);if(!t.success||!sg.test(t.data.command))return;let{cwd:r}=e;if(x(r))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 watch 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 ug}from"shell-quote";import{z as qi}from"zod";import{existsSync as ag,mkdirSync as lg,rmSync as Gx,writeFileSync as cg}from"fs";import rn from"path";function Bi(e,t,r){let n=Mi(e,t,r);lg(rn.dirname(n),{recursive:!0}),cg(n,"")}function zt(e,t,r){return ag(Mi(e,t,r))}function dg(e,t){return rn.join(e,".ripplo",".local","skills-loaded",t)}function Mi(e,t,r){return rn.join(dg(e,t),r)}var pg=qi.looseObject({command:qi.string()}),mg="debug",fg=new Set(["tail","head","less","more","wc","sort","uniq","awk","sed","grep"]),Gi=E("PreToolUse",e=>{if(e.tool_name!=="Bash")return;let t=pg.safeParse(e.tool_input);if(!t.success)return;let r=gg(t.data.command);if(!yg(r))return;let{cwd:n}=e;if(!x(n)||!T(n))return;let o=wg(r);if(o!=null)return zi(`Don't pipe \`ripplo run\` through \`${o}\` \u2014 buffering filters hold all stdout until EOF, hiding live progress; if you kill the pipeline mid-run the run is orphaned on the server. Redirect to a file instead: \`ripplo run <id> > /tmp/run.log 2>&1\` and Read the file, or use \`run_in_background: true\`.`);if(!zt(n,e.session_id,mg))return zi("Running `ripplo run` requires the `/ripplo:debug` skill loaded first. Load `/ripplo:debug` then retry \u2014 it carries the artifact-read order, the 3-strike rule, and the no-grep-piping guidance for run failures.")});function gg(e){try{return ug(e)}catch{return[]}}function yg(e){return e.some((t,r)=>t==="ripplo"&&e[r+1]==="run")}function hg(e){return typeof e=="object"&&"op"in e&&e.op==="|"}function wg(e){let t=e.find((r,n)=>{let o=e[n-1];return o!=null&&hg(o)&&typeof r=="string"&&fg.has(r)});return typeof t=="string"?t:null}function zi(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}import bg from"path";import{z as Ki}from"zod";var vg=new Set(["Edit","Write","NotebookEdit"]),kg=Ki.looseObject({file_path:Ki.string()}),Sg="create",Ji=E("PreToolUse",e=>{if(!vg.has(e.tool_name))return;let t=kg.safeParse(e.tool_input);if(!t.success)return;let{cwd:r}=e;if(!x(r)||!T(r))return;let n=bg.relative(r,t.data.file_path);if(!(n.startsWith("..")||n!==".ripplo"&&!n.startsWith(".ripplo/"))&&!zt(r,e.session_id,Sg))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 Rg from"path";import Qi from"picomatch";import{graphql as Pg}from"gql.tada";import{z as Yi}from"zod";var xg=new Set(["Edit","Write","NotebookEdit"]),Cg=Yi.looseObject({file_path:Yi.string()}),Eg=Pg(`
530
563
  query PreEditScopeGate($projectId: String!, $cwd: String!) {
531
564
  project(id: $projectId) {
532
565
  id
@@ -538,7 +571,7 @@ ${w("create")}`}});import{z as Mi}from"zod";var sg=Mi.looseObject({command:Mi.st
538
571
  }
539
572
  }
540
573
  }
541
- `),ts=E("PreToolUse",async e=>{if(!Cg.has(e.tool_name))return;let t=Eg.safeParse(e.tool_input);if(!t.success)return;let{cwd:r}=e;if(!x(r)||!$(r))return;let n=Pg.relative(r,t.data.file_path);if(!(n.startsWith("..")||n===".ripplo"||n.startsWith(".ripplo/")||!Ag(n)||await jg(r)))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Scope empty; edit touches app code (${n}). Stub a test or \`scope add\` an existing one before proceeding \u2014 or acknowledge "no user-facing behavior" if pure refactor. Hook re-fires until scope is populated (or hooks paused via the web UI). ${Uo(["scope","create"])}`}}});function Ag(e){let{ignorePaths:t,watchPaths:r}=Pe(),n=Zi([...r]),o=Zi([...t]);return n(e)&&!o(e)}async function jg(e){let t;try{t=_(e)}catch{return!0}let r=await g({config:t,document:Ig,variables:{cwd:t.cwd,projectId:t.projectId}}).catch(()=>null);return r==null?!0:(r.project?.devSession?.scopeItems??[]).length>0}import Tg from"fs";import $g from"path";import{z as rs}from"zod";var Ng=new Set(["Edit","Write","NotebookEdit"]),Og=rs.looseObject({file_path:rs.string()}),ns=E("PreToolUse",e=>{if(!Ng.has(e.tool_name))return;let t=Og.safeParse(e.tool_input);if(!t.success)return;let{cwd:r}=e;if(!x(r))return;let n=$g.relative(r,t.data.file_path);if(!(n.startsWith("..")||n!==".ripplo"&&!n.startsWith(".ripplo/"))&&!Tg.existsSync(ce(r))&&!Ve(r))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`\`ripplo watch\` is not running, so this edit to \`${n}\` won't sync to the server and dev-mode guardrails won't fire. Run \`/ripplo:start\` (or spawn \`npx ripplo watch\` directly via Bash with run_in_background), then retry. The app's dev server also needs to be running \u2014 \`npx ripplo doctor\` reports both. If watch can't start (auth, server down, intentional offline work), the user can run \`npx ripplo hooks pause\` to bypass this gate; \`npx ripplo hooks resume\` re-enables it. ${w("start")}`}}});import{graphql as Dg}from"gql.tada";var Lg=Dg(`
574
+ `),Xi=E("PreToolUse",async e=>{if(!xg.has(e.tool_name))return;let t=Cg.safeParse(e.tool_input);if(!t.success)return;let{cwd:r}=e;if(!x(r)||!T(r))return;let n=Rg.relative(r,t.data.file_path);if(!(n.startsWith("..")||n===".ripplo"||n.startsWith(".ripplo/")||!Ag(n)||await Ig(r)))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Scope empty; edit touches app code (${n}). Stub a test or \`scope add\` an existing one before proceeding \u2014 or acknowledge "no user-facing behavior" if pure refactor. Hook re-fires until scope is populated (or hooks paused via the web UI). ${Oo(["scope","create"])}`}}});function Ag(e){let{ignorePaths:t,watchPaths:r}=Re(),n=Qi([...r]),o=Qi([...t]);return n(e)&&!o(e)}async function Ig(e){let t;try{t=L(e)}catch{return!0}let r=await g({config:t,document:Eg,variables:{cwd:t.cwd,projectId:t.projectId}}).catch(()=>null);return r==null?!0:(r.project?.devSession?.scopeItems??[]).length>0}import Tg from"fs";import $g from"path";import{z as Zi}from"zod";var jg=new Set(["Edit","Write","NotebookEdit"]),Ng=Zi.looseObject({file_path:Zi.string()}),es=E("PreToolUse",e=>{if(!jg.has(e.tool_name))return;let t=Ng.safeParse(e.tool_input);if(!t.success)return;let{cwd:r}=e;if(!x(r))return;let n=$g.relative(r,t.data.file_path);if(!(n.startsWith("..")||n!==".ripplo"&&!n.startsWith(".ripplo/"))&&!Tg.existsSync(Se(r))&&!Ve(r))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`\`ripplo watch\` is not running, so this edit to \`${n}\` won't sync to the server and dev-mode guardrails won't fire. Run \`/ripplo:start\` (or spawn \`npx ripplo watch\` directly via Bash with run_in_background), then retry. The app's dev server also needs to be running \u2014 \`npx ripplo doctor\` reports both. If watch can't start (auth, server down, intentional offline work), the user can run \`npx ripplo hooks pause\` to bypass this gate; \`npx ripplo hooks resume\` re-enables it. ${w("start")}`}}});import{graphql as Og}from"gql.tada";var Dg=Og(`
542
575
  query ScopeReminder($projectId: String!, $cwd: String!) {
543
576
  project(id: $projectId) {
544
577
  id
@@ -555,9 +588,9 @@ ${w("create")}`}});import{z as Mi}from"zod";var sg=Mi.looseObject({command:Mi.st
555
588
  }
556
589
  }
557
590
  }
558
- `),os=E("UserPromptSubmit",async e=>{let{cwd:t}=e;if(!x(t)||!$(t))return;let r=Be(t);if(We(t,"scope-reminder")===r)return;let n;try{n=_(t)}catch{return}let o=await g({config:n,document:Lg,variables:{cwd:n.cwd,projectId:n.projectId}}).catch(()=>null);if(o==null)return;Me(t,"scope-reminder",r);let i=o.project?.devSession?.scopeItems??[];return{hookSpecificOutput:{additionalContext:i.length===0?`RIPPLO SCOPE: empty. ${w("scope")}`:`RIPPLO SCOPE (${String(i.length)}):
591
+ `),ts=E("UserPromptSubmit",async e=>{let{cwd:t}=e;if(!x(t)||!T(t))return;let r=We(t);if(Be(t,"scope-reminder")===r)return;let n;try{n=L(t)}catch{return}let o=await g({config:n,document:Dg,variables:{cwd:n.cwd,projectId:n.projectId}}).catch(()=>null);if(o==null)return;Me(t,"scope-reminder",r);let i=o.project?.devSession?.scopeItems??[];return{hookSpecificOutput:{additionalContext:i.length===0?`RIPPLO SCOPE: empty. ${w("scope")}`:`RIPPLO SCOPE (${String(i.length)}):
559
592
  ${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(`
560
- `)}`,hookEventName:"UserPromptSubmit"}}});var _g="# 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: precondition isolation, observer budget tiers, coverage IDs, 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, cleared coverage gates without proof, etc.).\n\nMatch the task to a skill from the triage table; if unsure between two, load both. If completely unsure, load `/ripplo:explore` 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; `ripplo doctor` reports the engine endpoint missing.\n- Load `/ripplo:explore` skill for instructions on planning/stubbing test coverage for a new project or new feature area.\n- Load `/ripplo:scope` skill for instructions on managing the working set of tests this session is responsible for.\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 existing tests.\n- Load `/ripplo:debug` skill for instructions on a failed run; read artifacts in `.ripplo/debug/<runId>/` before re-running.\n- Load `/ripplo:flake-detect` skill for instructions on intermittent failures; parallelizes N runs (only use with evidence of flake).\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 watch` (run `/ripplo:start` to spawn it, or invoke it directly via Bash with `run_in_background`), so the dev session is live. Without watch, scope/coverage hooks don't arm and `ripplo run` refuses to dispatch. Without the dev server, runs fail when they try to hit the app. `npx ripplo doctor` reports both.\n- **Two funnels.** Definitions \u2192 `createRipplo({ preconditions, observers, tests })` in `.ripplo/index.ts`. Implementations \u2192 `createEngine(ripplo, { preconditions, observers })` in your app server's `test/engine.ts`. 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 `.notImplemented()` tests. 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 `.notImplemented()` 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 (precondition, observer, engine impl) is in-scope work, not follow-up.\n- **`uiOnly: true` is not a stub.** Observer wiring is in-scope for mutation flows; don't use it to silence observer lint.\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 produce DOM, a11y tree, console, network, screenshots under `.ripplo/debug/<runId>/`. Read them. Never pipe `ripplo run` through `grep`/`tail`/`head`. Form a hypothesis citing an artifact line, make ONE change, re-run once.\n- **3-strike rule.** Same failure after 3 targeted fixes \u2192 stop and report. Repeated failure on the same step almost always means the diagnosis is wrong.\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 watch` (whether started via `/ripplo:start` or manually) talks to the wrong server, runs silently fail or hit the sibling worktree's app.\n- **DSL locators are semantic.** No `css` / `placeholder` / `waitFor` / `group`.\n- **New backend assertion?** Declare in `.ripplo/observers/index.ts`, implement in your app server's `engine.ts` (TS flags the missing impl).\n\n## Key files\n\n- `.ripplo/index.ts` \u2014 `createRipplo` call.\n- `.ripplo/{preconditions,observers,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- `packages/testing/README.md` (or the npm page for `@ripplo/testing`) \u2014 DSL reference.\n",is=E("SessionStart",e=>{if(x(e.cwd))return{hookSpecificOutput:{additionalContext:_g,hookEventName:"SessionStart"}}});import{execFileSync as Ug}from"child_process";import Vg from"path";import ss from"process";import{graphql as Fg}from"gql.tada";import{z as Gt}from"zod";var Hg=Fg(`
593
+ `)}`,hookEventName:"UserPromptSubmit"}}});var Lg="# 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: precondition isolation, observer budget tiers, coverage IDs, 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, cleared coverage gates without proof, etc.).\n\nMatch the task to a skill from the triage table; if unsure between two, load both. If completely unsure, load `/ripplo:explore` 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; `ripplo doctor` reports the engine endpoint missing.\n- Load `/ripplo:explore` skill for instructions on planning/stubbing test coverage for a new project or new feature area.\n- Load `/ripplo:scope` skill for instructions on managing the working set of tests this session is responsible for.\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 existing tests.\n- Load `/ripplo:debug` skill for instructions on a failed run; read artifacts in `.ripplo/debug/<runId>/` before re-running.\n- Load `/ripplo:flake-detect` skill for instructions on intermittent failures; parallelizes N runs (only use with evidence of flake).\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 watch` (run `/ripplo:start` to spawn it, or invoke it directly via Bash with `run_in_background`), so the dev session is live. Without watch, scope/coverage hooks don't arm and `ripplo run` refuses to dispatch. Without the dev server, runs fail when they try to hit the app. `npx ripplo doctor` reports both.\n- **Two funnels.** Definitions \u2192 `createRipplo({ preconditions, observers, tests })` in `.ripplo/index.ts`. Implementations \u2192 `createEngine(ripplo, { preconditions, observers })` in your app server's `test/engine.ts`. 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 `.notImplemented()` tests. 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 `.notImplemented()` 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 (precondition, observer, engine impl) is in-scope work, not follow-up.\n- **`uiOnly: true` is not a stub.** Observer wiring is in-scope for mutation flows; don't use it to silence observer lint.\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 produce DOM, a11y tree, console, network, screenshots under `.ripplo/debug/<runId>/`. Read them. Never pipe `ripplo run` through `grep`/`tail`/`head`. Form a hypothesis citing an artifact line, make ONE change, re-run once.\n- **3-strike rule.** Same failure after 3 targeted fixes \u2192 stop and report. Repeated failure on the same step almost always means the diagnosis is wrong.\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 watch` (whether started via `/ripplo:start` or manually) talks to the wrong server, runs silently fail or hit the sibling worktree's app.\n- **DSL locators are semantic.** No `css` / `placeholder` / `waitFor` / `group`.\n- **New backend assertion?** Declare in `.ripplo/observers/index.ts`, implement in your app server's `engine.ts` (TS flags the missing impl).\n\n## Key files\n\n- `.ripplo/index.ts` \u2014 `createRipplo` call.\n- `.ripplo/{preconditions,observers,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- `packages/testing/README.md` (or the npm page for `@ripplo/testing`) \u2014 DSL reference.\n",rs=E("SessionStart",e=>{if(x(e.cwd))return{hookSpecificOutput:{additionalContext:Lg,hookEventName:"SessionStart"}}});import{execFileSync as _g}from"child_process";import Ug from"path";import ns from"process";import{graphql as Vg}from"gql.tada";import{z as Gt}from"zod";var Fg=Vg(`
561
594
  query ScopeEnforce($projectId: String!, $cwd: String!) {
562
595
  project(id: $projectId) {
563
596
  id
@@ -576,14 +609,14 @@ ${i.map(a=>{let l=a.workflow;return l==null?` [intent] (${a.id}) ${a.label??""}
576
609
  }
577
610
  }
578
611
  }
579
- `),ls=E("Stop",async e=>{let{cwd:t}=e;if(!x(t)||!$(t))return;let r=Be(t),n=We(t,"stop-enforce")===r;if(n&&!e.stop_hook_active)return;let o=Bg(t),i=await Wg(t,o);if(Me(t,"stop-enforce",r),i.length!==0)return n&&e.stop_hook_active?{continue:!1,stopReason:`Stop-enforce: same repo state across consecutive stop attempts; agent appears stuck. Errors:
612
+ `),is=E("Stop",async e=>{let{cwd:t}=e;if(!x(t)||!T(t))return;let r=We(t),n=Be(t,"stop-enforce")===r;if(n&&!e.stop_hook_active)return;let o=Hg(t),i=await Wg(t,o);if(Me(t,"stop-enforce",r),i.length!==0)return n&&e.stop_hook_active?{continue:!1,stopReason:`Stop-enforce: same repo state across consecutive stop attempts; agent appears stuck. Errors:
580
613
  ${i.join(`
581
614
 
582
615
  `)}`}:{decision:"block",reason:i.join(`
583
616
 
584
- `)}});function Bg(e){return Mt(e,".ripplo/tests").filter(t=>t.endsWith(".ts")).map(t=>Vg.basename(t,".ts")).filter(t=>t!=="index")}async function Wg(e,t){let r=await C(e);if(!r.ok)return[`--- Compilation failed ---
617
+ `)}});function Hg(e){return Mt(e,".ripplo/tests").filter(t=>t.endsWith(".ts")).map(t=>Ug.basename(t,".ts")).filter(t=>t!=="index")}async function Wg(e,t){let r=await C(e);if(!r.ok)return[`--- Compilation failed ---
585
618
  ${r.error}
586
- ${w("create")}`];await ae({cwd:e,result:r.result}),_e({cwd:e});let n=await qg(e),o=[...new Set([...t,...n.scopedSlugsToRun])],i=zg(r,t),s=Gg(r),a=Mg(e,r),l=Kg(e,o);return[i,s,a,l,n.error].filter(c=>c!=null)}function Mg(e,t){let{ignorePaths:r}=Pe(),n=Fo({compileResult:t.result,cwd:e,ignorePaths:r});if(n.length===0)return null;let o=n.filter(a=>a.kind==="unacknowledged"),i=n.filter(a=>a.kind==="stale"),s=[];if(o.length>0){let a=o.map(l=>` ${l.id}`).join(`
619
+ ${w("create")}`];await ae({cwd:e,result:r.result}),_e({cwd:e});let n=await Mg(e),o=[...new Set([...t,...n.scopedSlugsToRun])],i=qg(r,t),s=zg(r),a=Bg(e,r),l=Gg(e,o);return[i,s,a,l,n.error].filter(c=>c!=null)}function Bg(e,t){let{ignorePaths:r}=Re(),n=Lo({compileResult:t.result,cwd:e,ignorePaths:r});if(n.length===0)return null;let o=n.filter(a=>a.kind==="unacknowledged"),i=n.filter(a=>a.kind==="stale"),s=[];if(o.length>0){let a=o.map(l=>` ${l.id}`).join(`
587
620
  `);s.push(`Unacknowledged interactions (add to a test's .coverage(...)):
588
621
  ${a}`)}if(i.length>0){let a=i.map(l=>` ${l.id}`).join(`
589
622
  `);s.push(`Stale .coverage(...) claims (id no longer exists in source):
@@ -591,23 +624,23 @@ ${a}`)}return`--- Exhaustiveness ---
591
624
  ${s.join(`
592
625
 
593
626
  `)}
594
- ${w("create")}`}async function qg(e){let t;try{t=_(e)}catch{return{error:null,scopedSlugsToRun:[]}}let n=(await g({config:t,document:Hg,variables:{cwd:t.cwd,projectId:t.projectId}}).catch(()=>null))?.project?.devSession?.scopeItems??[],o=n.flatMap(a=>{let l=a.workflow;return l==null?[` [intent] ${a.label??"(no label)"} \u2014 stub a test for this flow`]:l.spec==null?[` [stub] ${l.slug} \u2014 implement \`${l.name}\``]:[]}),i=n.flatMap(a=>a.workflow?.spec==null?[]:[a.workflow.slug]);return{error:o.length===0?null:`--- Testing Scope ---
627
+ ${w("create")}`}async function Mg(e){let t;try{t=L(e)}catch{return{error:null,scopedSlugsToRun:[]}}let n=(await g({config:t,document:Fg,variables:{cwd:t.cwd,projectId:t.projectId}}).catch(()=>null))?.project?.devSession?.scopeItems??[],o=n.flatMap(a=>{let l=a.workflow;return l==null?[` [intent] ${a.label??"(no label)"} \u2014 stub a test for this flow`]:l.spec==null?[` [stub] ${l.slug} \u2014 implement \`${l.name}\``]:[]}),i=n.flatMap(a=>a.workflow?.spec==null?[]:[a.workflow.slug]);return{error:o.length===0?null:`--- Testing Scope ---
595
628
  ${o.join(`
596
629
  `)}
597
- ${w("create")}`,scopedSlugsToRun:i}}function zg(e,t){let{diagnostics:r}=De(e.result),n=new Set(e.builder.getUnimplemented().tests),o=t.filter(a=>n.has(a));if(r.length===0&&o.length===0)return null;let i=r.map(a=>{let l=a.step==null?"":` at "${a.step}"`;return` [${a.rule}]${l} ${a.test}: ${a.message}`}),s=o.map(a=>` [not-implemented] ${a} is still marked .notImplemented()`);return`--- Ripplo Lint ---
630
+ ${w("create")}`,scopedSlugsToRun:i}}function qg(e,t){let{diagnostics:r}=De(e.result),n=new Set(e.builder.getUnimplemented().tests),o=t.filter(a=>n.has(a));if(r.length===0&&o.length===0)return null;let i=r.map(a=>{let l=a.step==null?"":` at "${a.step}"`;return` [${a.rule}]${l} ${a.test}: ${a.message}`}),s=o.map(a=>` [not-implemented] ${a} is still marked .notImplemented()`);return`--- Ripplo Lint ---
598
631
  ${[...i,...s].join(`
599
632
  `)}
600
- ${w("create")}`}function Gg(e){let{tests:t}=e.builder.getUnimplemented();return t.length===0?null:`--- Unimplemented stubs ---
633
+ ${w("create")}`}function zg(e){let{tests:t}=e.builder.getUnimplemented();return t.length===0?null:`--- Unimplemented stubs ---
601
634
  ${t.join(", ")}
602
635
  Implement the stub now. Do not ask the user "implement or defer?" \u2014 that framing is forbidden by /ripplo:create. New scaffolding (precondition, observer, engine impl) is in-scope, not follow-up.
603
- ${w("create")}`}function Kg(e,t){if(t.length===0)return null;let r=Qg(["run",...t],e);if(r.code===0)return null;let n=r.output.split(`
636
+ ${w("create")}`}function Gg(e,t){if(t.length===0)return null;let r=Jg(["run",...t],e);if(r.code===0)return null;let n=r.output.split(`
604
637
  `).filter(o=>/FAILED/.test(o)).join(`
605
638
  `);return n.length===0?null:`--- Ripplo Run Failures (${t.join(" ")}) ---
606
639
  ${n}
607
- Artifacts: .ripplo/debug/<runId>/. ${w("debug")}`}var Jg=Gt.object({status:Gt.number().nullish(),stderr:Gt.unknown().optional(),stdout:Gt.unknown().optional()});function as(e){return e instanceof Buffer?e.toString("utf8"):typeof e=="string"?e:""}function Qg(e,t){let r=ss.argv[1];if(r==null)return{code:1,output:""};try{return{code:0,output:Ug(ss.execPath,[r,...e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}}catch(n){let o=Jg.safeParse(n);if(!o.success)return{code:1,output:""};let i=`${as(o.data.stdout)}${as(o.data.stderr)}`;return{code:o.data.status??1,output:i}}}import{z as cs}from"zod";var Yg=cs.looseObject({skill:cs.string()}),us=E("PostToolUse",e=>{if(e.tool_name!=="Skill")return;let t=Yg.safeParse(e.tool_input);if(!t.success)return;let r=/^ripplo:(.+)$/.exec(t.data.skill);if(r==null)return;let n=r[1];n!=null&&x(e.cwd)&&zi(e.cwd,e.session_id,n)});ay();fn(process.cwd());var ds={"coverage-nudge":Di,"exit-plan-gate":_i,"plan-reminder":Ui,"post-edit-flag-stubs":Hi,"post-edit-lint":Wi,"pre-bash-hooks-pause-gate":qi,"pre-bash-run-gate":Qi,"pre-edit-ripplo-skill-gate":Xi,"pre-edit-scope-gate":ts,"pre-edit-watch-gate":ns,"scope-reminder":os,"session-preamble":is,"stop-enforce":ls,"track-skill-load":us};async function ey(){await Xg(Zg(process.argv)).scriptName("ripplo").version("0.4.12").command("watch","Watch for run requests and execute locally",()=>{},()=>xi()).command("auth <subcommand>","Manage authentication",sy).command("projects <subcommand>","Inspect Ripplo projects",iy).command("hooks <subcommand>","Pause or resume Ripplo hooks",oy).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=>yi({appUrl:e["app-url"],engineUrl:e["engine-url"],envFile:e.env,projectId:e.project})).command("run [ids..]","Run tests in parallel",e=>{let t=[];return e.positional("ids",{array:!0,default:t,describe:"Test ids to run (all if omitted)",type:"string"})},e=>ki(e.ids)).command("lint [ids..]","Compile and lint tests (all or specific ids)",e=>{let t=[];return e.positional("ids",{array:!0,default:t,describe:"Test ids to lint (all if omitted)",type:"string"}).option("require-implemented",{array:!0,default:t,describe:"Test ids that must not be .notImplemented() \u2014 fails if any still are",type:"string"})},e=>bi({ids:e.ids,requireImplemented:e["require-implemented"]})).command("flake-detect <id>","Run N times to detect flakiness",e=>e.positional("id",{demandOption:!0,type:"string"}).option("runs",{default:10,type:"number"}),e=>li({id:e.id,runs:e.runs})).command("sync","Push the compiled .ripplo/ resources to the server (no run)",()=>{},()=>Si()).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=>Vo({check:e.check})).command("cover","Audit all coverage statements",()=>{},()=>Mo()).command("doctor","Check project health",()=>{},()=>oi()).command("status","Report unimplemented tests and preconditions",e=>e.option("format",{choices:["json","summary"],default:"json",describe:"Output format"}),e=>ji({format:e.format})).command("scope <subcommand>","Manage testing scope",ny).command("hook <name>","Internal: run a Claude Code plugin hook",e=>e.positional("name",{choices:Object.keys(ds),demandOption:!0,type:"string"}),e=>ty(e.name)).strict().help().parse()}async function ty(e){let t=ds[e];t==null&&(process.stderr.write(`Unknown hook: ${e}
608
- `),process.exit(1));let r=await ry(),n=r.trim()===""?{}:JSON.parse(r),o=await t.run(n);o!=null&&process.stdout.write(JSON.stringify(o))}function ry(){return new Promise((e,t)=>{if(process.stdin.isTTY){e("");return}let r=[];process.stdin.on("data",n=>r.push(n)),process.stdin.on("end",()=>{e(Buffer.concat(r).toString("utf8"))}),process.stdin.on("error",t)})}ey().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
609
- `),process.exit(1)});function ny(e){return e.command("status","Print the current scope",t=>t.option("format",{choices:["json","text"],default:"text",describe:"Output format"}),t=>Ci({format:t.format})).command("add <test-ids..>","Bind one or more existing tests (stubs or implemented) to scope as agent intent",t=>{let r=[];return t.positional("test-ids",{array:!0,default:r,demandOption:!0,describe:"Slugs of existing workflows",type:"string"})},t=>Ei({testIds:t["test-ids"]})).command("link <id> <test-id>","Link an existing scope item to a test",t=>t.positional("id",{demandOption:!0,describe:"Scope item id",type:"string"}).positional("test-id",{demandOption:!0,describe:"Slug of the workflow to link",type:"string"}),t=>Ii({id:t.id,testId:t["test-id"]})).command("remove <ids..>","Remove one or more scope items by id",t=>{let r=[];return t.positional("ids",{array:!0,default:r,demandOption:!0,describe:"Scope item ids",type:"string"})},t=>Ai({ids:t.ids})).demandCommand(1)}function oy(e){return e.command("pause","Disable all Ripplo pre-edit gates and stop enforcement until resumed",()=>{},()=>ci()).command("resume","Re-enable Ripplo hooks paused via `ripplo hooks pause`",()=>{},()=>ui()).demandCommand(1)}function iy(e){return e.command("list","List projects you have access to (JSON)",()=>{},()=>vi()).demandCommand(1)}function sy(e){return e.command("login","Authenticate via device flow",()=>{},()=>po()).command("status","Show authentication status",()=>{},()=>mo()).command("logout","Remove the saved token",()=>{},()=>{fo()}).demandCommand(1)}function ay(){let e=process.cwd(),t=Jt(e);t!=null&&t!==e&&(process.chdir(t),process.stderr.write(`ripplo: resolved .ripplo/ at ${t}
610
- `))}export{ey as main};
640
+ Artifacts: .ripplo/debug/<runId>/. ${w("debug")}`}var Kg=Gt.object({status:Gt.number().nullish(),stderr:Gt.unknown().optional(),stdout:Gt.unknown().optional()});function os(e){return e instanceof Buffer?e.toString("utf8"):typeof e=="string"?e:""}function Jg(e,t){let r=ns.argv[1];if(r==null)return{code:1,output:""};try{return{code:0,output:_g(ns.execPath,[r,...e],{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}}catch(n){let o=Kg.safeParse(n);if(!o.success)return{code:1,output:""};let i=`${os(o.data.stdout)}${os(o.data.stderr)}`;return{code:o.data.status??1,output:i}}}import{z as ss}from"zod";var Qg=ss.looseObject({skill:ss.string()}),as=E("PostToolUse",e=>{if(e.tool_name!=="Skill")return;let t=Qg.safeParse(e.tool_input);if(!t.success)return;let r=/^ripplo:(.+)$/.exec(t.data.skill);if(r==null)return;let n=r[1];n!=null&&x(e.cwd)&&Bi(e.cwd,e.session_id,n)});sy();dn(process.cwd());var ls={"coverage-nudge":ji,"exit-plan-gate":Oi,"plan-reminder":Di,"post-edit-flag-stubs":Ui,"post-edit-lint":Fi,"pre-bash-hooks-pause-gate":Wi,"pre-bash-run-gate":Gi,"pre-edit-ripplo-skill-gate":Ji,"pre-edit-scope-gate":Xi,"pre-edit-watch-gate":es,"scope-reminder":ts,"session-preamble":rs,"stop-enforce":is,"track-skill-load":as};async function Zg(){await Yg(Xg(process.argv)).scriptName("ripplo").version("0.4.14").command("watch","Watch for run requests and execute locally",()=>{},()=>ki()).command("auth <subcommand>","Manage authentication",iy).command("projects <subcommand>","Inspect Ripplo projects",oy).command("hooks <subcommand>","Pause or resume Ripplo hooks",ny).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=>pi({appUrl:e["app-url"],engineUrl:e["engine-url"],envFile:e.env,projectId:e.project})).command("run [ids..]","Run tests in parallel",e=>{let t=[];return e.positional("ids",{array:!0,default:t,describe:"Test ids to run (all if omitted)",type:"string"})},e=>hi(e.ids)).command("lint [ids..]","Compile and lint tests (all or specific ids)",e=>{let t=[];return e.positional("ids",{array:!0,default:t,describe:"Test ids to lint (all if omitted)",type:"string"}).option("require-implemented",{array:!0,default:t,describe:"Test ids that must not be .notImplemented() \u2014 fails if any still are",type:"string"})},e=>gi({ids:e.ids,requireImplemented:e["require-implemented"]})).command("flake-detect <id>","Run N times to detect flakiness",e=>e.positional("id",{demandOption:!0,type:"string"}).option("runs",{default:10,type:"number"}),e=>oi({id:e.id,runs:e.runs})).command("sync","Push the compiled .ripplo/ resources to the server (no run)",()=>{},()=>wi()).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=>Do({check:e.check})).command("cover","Audit all coverage statements",()=>{},()=>Fo()).command("doctor","Check project health",()=>{},()=>ei()).command("status","Report unimplemented tests and preconditions",e=>e.option("format",{choices:["json","summary"],default:"json",describe:"Output format"}),e=>Ei({format:e.format})).command("scope <subcommand>","Manage testing scope",ry).command("hook <name>","Internal: run a Claude Code plugin hook",e=>e.positional("name",{choices:Object.keys(ls),demandOption:!0,type:"string"}),e=>ey(e.name)).strict().help().parse()}async function ey(e){let t=ls[e];t==null&&(process.stderr.write(`Unknown hook: ${e}
641
+ `),process.exit(1));let r=await ty(),n=r.trim()===""?{}:JSON.parse(r),o=await t.run(n);o!=null&&process.stdout.write(JSON.stringify(o))}function ty(){return new Promise((e,t)=>{if(process.stdin.isTTY){e("");return}let r=[];process.stdin.on("data",n=>r.push(n)),process.stdin.on("end",()=>{e(Buffer.concat(r).toString("utf8"))}),process.stdin.on("error",t)})}Zg().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
642
+ `),process.exit(1)});function ry(e){return e.command("status","Print the current scope",t=>t.option("format",{choices:["json","text"],default:"text",describe:"Output format"}),t=>Ri({format:t.format})).command("add <test-ids..>","Bind one or more existing tests (stubs or implemented) to scope as agent intent",t=>{let r=[];return t.positional("test-ids",{array:!0,default:r,demandOption:!0,describe:"Slugs of existing workflows",type:"string"})},t=>Pi({testIds:t["test-ids"]})).command("link <id> <test-id>","Link an existing scope item to a test",t=>t.positional("id",{demandOption:!0,describe:"Scope item id",type:"string"}).positional("test-id",{demandOption:!0,describe:"Slug of the workflow to link",type:"string"}),t=>xi({id:t.id,testId:t["test-id"]})).command("remove <ids..>","Remove one or more scope items by id",t=>{let r=[];return t.positional("ids",{array:!0,default:r,demandOption:!0,describe:"Scope item ids",type:"string"})},t=>Ci({ids:t.ids})).demandCommand(1)}function ny(e){return e.command("pause","Disable all Ripplo pre-edit gates and stop enforcement until resumed",()=>{},()=>ii()).command("resume","Re-enable Ripplo hooks paused via `ripplo hooks pause`",()=>{},()=>si()).demandCommand(1)}function oy(e){return e.command("list","List projects you have access to (JSON)",()=>{},()=>yi()).demandCommand(1)}function iy(e){return e.command("login","Authenticate via device flow",()=>{},()=>ao()).command("status","Show authentication status",()=>{},()=>lo()).command("logout","Remove the saved token",()=>{},()=>{co()}).demandCommand(1)}function sy(){let e=process.cwd(),t=Jt(e);t!=null&&t!==e&&(process.chdir(t),process.stderr.write(`ripplo: resolved .ripplo/ at ${t}
643
+ `))}export{Zg as main};
611
644
  /*! Bundled license information:
612
645
 
613
646
  mustache/mustache.mjs: