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