document360-writer 0.5.160 → 0.5.161
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/cli.js +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -312,7 +312,7 @@ ${s}${p}`).join("")}).join(`
|
|
|
312
312
|
`+Zh[e.tone](Ln(Ns(e.text)));case"done":{let o=e.inTokens&&e.inTokens>0?` \xB7 ${Fn(e.inTokens)} in${e.cachedPct!=null?` (${e.cachedPct}% cached)`:""}`:"";return`
|
|
313
313
|
`+(e.ok?ne.magenta("\u2736 "):ne.red("\u2736 "))+ne.gray(`Cooked for ${Bn(e.seconds)} \xB7 ${Fn(e.tokens)} tokens out${o}`+(e.costUsd>0?` \xB7 ${Pe(e.costUsd)}`:""))}}}function lc(e,t){return e.map(n=>Is(n,t)).join(`
|
|
314
314
|
`)}import{resolveActiveProfile as ef,loadTokens as tf,isExpired as nf,decodeJwtClaims as cc}from"document360-engine";function of(e){return e.length>12?`${e.slice(0,8)}\u2026`:e}function Ue(e,t){try{let n=ef(e,t),o=tf(n.name);if(!o)return{ok:!1,reason:`Not signed in to Document360 (profile "${n.name}"). Run /login first.`};if(nf(o)&&!o.refreshToken)return{ok:!1,reason:`Your Document360 session expired (profile "${n.name}"). Run /login.`};let r=n.project.projectId;if(r){let a={...cc(o.idToken)??{},...cc(o.accessToken)??{}}.doc360_project_id;if(typeof a=="string"&&a&&a!==r)return{ok:!1,reason:`Your Document360 session is signed into a different project than profile "${n.name}" (this profile's project is ${of(r)}). Run /login and pick this project.`}}return{ok:!0}}catch{return{ok:!0}}}import{existsSync as Ms,readFileSync as Bs}from"node:fs";import{isAbsolute as Jo,join as Zo,resolve as er}from"node:path";import O from"picocolors";function qn(e,t){let n=[],o="";for(let r of e.split(/\s+/).filter(Boolean))for(o.length?o.length+1+r.length<=t?o+=` ${r}`:(n.push(o),o=r):o=r;o.length>t;)n.push(o.slice(0,t)),o=o.slice(t);return o&&n.push(o),n}function Ko(e){if(e.length===0)return[];let t=(s,a)=>`${s}${"\u2500".repeat(78)}${a}`,n=(s="")=>`\u2502 ${s.padEnd(76)} \u2502`,o=[`\u2B06 d360-writer upgraded \u2014 ${e.length===1?"one improvement affects":`${e.length} improvements affect`} the docs you've already written:`,"",t("\u250C","\u2510")];e.forEach((s,a)=>{a>0&&o.push(t("\u251C","\u2524")),o.push(n());for(let u of qn(`${a+1}. ${s.feature}`,76))o.push(n(u));o.push(n());for(let u of qn(s.why,73))o.push(n(` ${u}`));o.push(n())}),o.push(t("\u2514","\u2518")),o.push("");let r=e.length>1;return o.push(`\u2713 ${r?"These are":"This is"} now on your docs roadmap \u2014 each launch shows your next step below (/roadmap shows the full campaign).`),o}import{readProjectConfig as hn,screenshotPlaceholderIds as kc,DEFAULT_CAPTURE_DIR as tr,DEFAULT_OUTPUT_DIR as cf,DEFAULT_DOCS_DIR as qs}from"document360-engine";import{createHash as rf}from"node:crypto";import{readFileSync as sf,writeFileSync as af}from"node:fs";import{captureStatePath as dc}from"document360-engine";var uc={version:1,shots:{}};function pc(e){return rf("sha1").update(e).digest("hex").slice(0,12)}function _t(e){try{let t=JSON.parse(sf(dc(e),"utf8").replace(/^/,""));return!t||typeof t!="object"||t.version!==1||!t.shots?{...uc,shots:{}}:{version:1,shots:t.shots}}catch{return{...uc,shots:{}}}}function lf(e,t){af(dc(e),`${JSON.stringify(t,null,2)}
|
|
315
|
-
`,"utf8")}function Os(e){return e&&/data-testid|\btest-?id\b|has(?:n't| not) landed/i.test(e)?"testid":"data"}function hc(e,t,n){if(t.length===0)return;let o=_t(e);for(let r of t){let s=r.specSource?pc(r.specSource):void 0;o.shots[r.id]={outcome:r.outcome,at:n,...r.reason?{reason:r.reason}:{},...r.outcome==="skipped"?{blockedBy:Os(r.reason)}:{},...s?{specHash:s}:{}}}lf(e,o)}function _s(e,t,n){let o=e.shots[t];return!o||n===null||!o.specHash||o.specHash!==pc(n)?null:o}function fc(e){let t=_t(e).shots;return Object.keys(t).filter(n=>t[n].outcome==="failed").sort()}function Ls(e,t,n){let o=_s(_t(e),t,n);return o?.outcome==="failed"?o.reason??null:null}var bc=e=>e.replace(/\\/g,"/").replace(/\/+$/,"");function mc(e){let t=e.includes("--detail")||e.includes("-d"),n=e.find(o=>o!=="--detail"&&o!=="-d");return{mode:"list",scope:n?bc(n):void 0,...t?{detail:!0}:{}}}function Hn(e){let t=(e[0]??"").toLowerCase();if(t==="capture")return{mode:"capture",rest:e.slice(1)};if(t==="checklist"||t==="setup")return{mode:"checklist"};if(t==="list")return mc(e.slice(1));t==="author"&&(e=e.slice(1));let n=!e.includes("--no-setup"),o=e.filter(s=>s!=="--no-setup");if(o[0]==="--list")return mc(o.slice(1));let r=o[0];return r?r==="--all"?{mode:"all",setup:n}:/[\\/]/.test(r)||r.endsWith(".md")?{mode:"scope",scope:bc(r),setup:n}:{mode:"single",id:r}:{mode:"list",picker:!0}}function ze(e,t){let n=hn(e),o=n?.captureDir??tr,r=n?.outputDir??cf,s=(u,p)=>Jo(u)?Zo(u,p):er(e,u,p),a=_t(e);return kc(e,{scope:t}).map(({id:u,file:p})=>{let b=s(o,`${u}.spec.ts`),k=null;if(Ms(b))try{k=Bs(b,"utf8")}catch{}let $=Ms(s(r,`${u}.png`)),x=k===null?null:_s(a,u,k),j=k!==null&&df(k),v=$?"captured":k===null?"placeholder":j||x?.outcome==="skipped"?"blocked":x?.outcome==="failed"?"broken":"spec",D=k===null?void 0:xc(k),E=j?pf(k):x?.reason,U=j?"testid":x?.outcome==="skipped"?Os(x.reason):void 0;return{id:u,file:p,state:v,...D?{mutates:D.mutates,...D.declared?{}:{undeclared:!0}}:{},...k!==null&&gc(k).length>0?{unguardedWaits:gc(k)}:{},...v==="blocked"||v==="broken"?{...E?{reason:E}:{},...U?{blockedBy:U}:{}}:{}}})}function nr(e,t){let n=hn(e)?.captureDir??tr,o=Jo(n)?Zo(n,`${t}.spec.ts`):er(e,n,`${t}.spec.ts`);try{return Bs(o,"utf8")}catch{return null}}function $c(e,t){let n=hn(e)?.captureDir??tr;return Ms(Jo(n)?Zo(n,`${t}.spec.ts`):er(e,n,`${t}.spec.ts`))}function uf(e,t,n){let o=[n.trim()];for(let r=t+1;r<e.length;r++){let s=/^\s*\/\/\s?(.*)$/.exec(e[r]);if(!s)break;let a=s[1].trim();if(!a||/^(CAPTURE-[A-Z]+:|TODO\b|NOTE\b|Grounding\b|\d+\.\s)/.test(a)||(o.push(a),o.join(" ").length>400))break}return o.join(" ")}function xc(e){let t=e.split(/\r?\n/),n=t.findIndex(u=>/^\s*\/\/\s*CAPTURE-SAFETY:\s*(readonly|mutates)\b/i.test(u)),o=n>=0?/^\s*\/\/\s*CAPTURE-SAFETY:\s*(readonly|mutates)\b[\s—:–-]*(.*)$/i.exec(t[n]):null;if(o){let u=uf(t,n,o[2]??"");return{mutates:o[1].toLowerCase()==="mutates",declared:!0,what:u||void 0}}let r=/setInputFiles/.test(e),s=/\{\s*name:\s*['"][^'"]*\b(create|import|save|apply|confirm|delete|submit|send|mint)\b[^'"]*['"][^}]*\}\s*\)\s*\.click\(/i.test(e),a=/\.fill\(/.test(e);return{mutates:r||a&&s,declared:!1}}function df(e){return/(^|\n)\s*test\.skip\s*\(\s*['"`]/.test(e)}function pf(e){let t=/\/\/\s*CAPTURE-TODO:\s*(\{.*\})\s*$/m.exec(e),n,o;if(t)try{let s=JSON.parse(t[1]);n=s.testid,o=s.file}catch{}return`Inert until your developers add ${n?`data-testid="${n}"${o?` in ${o}`:""}`:"a stable data-testid"} \u2014 the spec is skipped at compile time, so no capture run can produce this shot. /screenshot checklist builds the hand-off.`}function gc(e){let t=r=>{if(/^\s*\*/.test(r))return"";let s=/(?<!:)\/\//.exec(r);return s?r.slice(0,s.index):r},n=[],o=e.split(/\r?\n/).map(t);for(let[r,s]of o.entries()){if(!/\.waitFor\s*\(/.test(s))continue;let a=s;for(let u=r+1;u<o.length&&!/;\s*$/.test(a.trim())&&u<r+4;u++)a+=o[u];/\.catch\s*\(/.test(a)||n.push(r+1)}return n}function vc(e){return(e??"").trim().replace(/[.\s]+$/,"")}function Cc(e,t){let n=hn(e)?.captureDir??tr,o=Jo(n)?Zo(n,`${t}.spec.ts`):er(e,n,`${t}.spec.ts`);try{return xc(Bs(o,"utf8"))}catch{return{mutates:!1,declared:!1}}}function or(e){return[...new Set(e.filter(t=>t.state==="captured").map(t=>t.file))].sort()}var hf={placeholder:"\u25CB",spec:"\u25D0",blocked:"\u2298",broken:"\u2717",captured:"\u25CF"},ff={placeholder:O.gray,spec:O.yellow,blocked:O.cyan,broken:O.red,captured:O.green},Rc=` ${O.green("\u25CF captured")} ${O.yellow("\u25D0 ready")} ${O.cyan("\u2298 blocked")} ${O.red("\u2717 broken")} ${O.gray("\u25CB to spec")}`;function st(e,t=n=>n){return`${t(e)} `}function mf(e){let t=e.replace(/\s+/g," ").trim().replace(/^Prerequisite not met:\s*/i,""),n=/^(.*?[a-z0-9)"'”’])\.(?:\s|$)/i.exec(t);return(n?n[1]:t).trim()}function Hs(e){let t=new Map;for(let n of e){if(n.state!=="blocked")continue;let o=(n.reason??"No reason recorded \u2014 re-run this capture to find out why.").replace(/\s+/g," ").trim(),r=n.blockedBy??"data",s=mf(o),a=`${r} ${s.toLowerCase()}`,u=t.get(a)??{reason:s,blockedBy:r,ids:[]};t.has(a)||t.set(a,u),u.ids.push(n.id)}return[...t.values()].sort((n,o)=>o.ids.length-n.ids.length||n.reason.localeCompare(o.reason))}function Sc(e){let t=Hs(e);if(t.length===0)return[];let n=t.filter(s=>s.blockedBy==="data"),o=t.filter(s=>s.blockedBy==="testid"),r=[""];if(n.length>0){let s=n.reduce((u,p)=>u+p.ids.length,0);r.push(` ${O.bold("Stage this data")} ${O.gray(`\u2014 unblocks ${s} shot${s===1?"":"s"} in ${n.length} action${n.length===1?"":"s"}`)}`);let a=8;for(let u of n.slice(0,a)){let p=qn(u.reason,86);r.push(` ${O.cyan(yt(`${u.ids.length}\xD7`,4))} ${p[0]??""}`);for(let k of p.slice(1))r.push(` ${k}`);let b=u.ids.slice(0,4).join(" \xB7 ");r.push(O.gray(` ${b}${u.ids.length>4?` \xB7 +${u.ids.length-4} more`:""}`))}n.length>a&&r.push(O.gray(` \u2026 ${n.length-a} more staging action${n.length-a===1?"":"s"} \u2014 /screenshot list --detail`))}if(o.length>0){let s=o.reduce((a,u)=>a+u.ids.length,0);r.push(` ${st("\u2298",O.cyan)}${s} shot${s===1?"":"s"} need${s===1?"s":""} a data-testid from your developers \u2014 ${O.bold("/screenshot checklist")} builds the hand-off.`)}return r}function Pc(e){let t=e.filter(o=>(o.unguardedWaits?.length??0)>0);if(t.length===0)return[];let n=t.reduce((o,r)=>o+(r.unguardedWaits?.length??0),0);return["",` ${st("\u26A0",O.yellow)}${t.length} spec${t.length===1?"":"s"} ${t.length===1?"has":"have"} ${n} unguarded wait${n===1?"":"s"}`+O.gray(" \u2014 on missing data these fail with a stack trace instead of telling you what to stage"),O.gray(` ${t.slice(0,6).map(o=>`${o.id}:${o.unguardedWaits.join(",")}`).join(" \xB7 ")}${t.length>6?` \xB7 +${t.length-6} more`:""}`),O.gray(` Re-author to guard them: ${O.bold("/screenshot author <id>")}`)]}function Tc(e){let t=e.filter(o=>o.undeclared);if(t.length===0)return[];let n=t.filter(o=>o.mutates);return["",` ${st("\u26A0",O.yellow)}${t.length} spec${t.length===1?"":"s"} carr${t.length===1?"ies":"y"} no CAPTURE-SAFETY declaration`+(n.length>0?O.gray(` \u2014 ${n.length} of them read as "writes real data" by heuristic, so bulk capture benches ${n.length===1?"it":"them"}`):""),O.gray(` ${t.slice(0,6).map(o=>o.id).join(" \xB7 ")}${t.length>6?` \xB7 +${t.length-6} more`:""}`),O.gray(` Re-author them to declare it: ${O.bold("/screenshot author <id>")}`)]}var Xo=(e,t)=>e.length>=t?e:e+" ".repeat(t-e.length),yt=(e,t)=>e.length>=t?e:" ".repeat(t-e.length)+e;function Dc(e,t){let n=e.replace(/\\/g,"/"),o=t.replace(/\\/g,"/").replace(/\/+$/,"");o&&(n===o||n.startsWith(`${o}/`))&&(n=n.slice(o.length+1));let r=n.split("/").filter(Boolean);return r.length>1?r[0]:""}var gf=new Set(["and","or","of","the","to","a","an","in","on","for","with","vs"]),yf=new Set(["ai","api","cli","ui","ux","url","id","http","https","json","xml","sdk","csv","pdf","oauth","sso","mfa","io","faq"]);function wf(e){return e.replace(/[-_]+/g," ").trim().split(/\s+/).filter(Boolean).map((n,o)=>{let r=n.toLowerCase();return yf.has(r)?r.toUpperCase():o>0&&gf.has(r)?r:r.charAt(0).toUpperCase()+r.slice(1)}).join(" ")}function Us(e){if(!e)return{num:"",name:"(docs root)"};let t=/^(\d+)[-_](.+)$/.exec(e);return{num:t?t[1]:"",name:wf(t?t[2]:e)}}function kf(e){return e.replace(/\\/g,"/").split("/").filter(Boolean).pop()??e}function yc(e,t,n){let o=t>0?Math.round(e/t*n):0;return O.green("\u2588".repeat(o))+O.gray("\u2591".repeat(Math.max(0,n-o)))}function bf(e,t){let n=t>0?Math.round(e/t*100):0,o=yt(`${n}%`,4);return n===100?O.green(o):n>0?O.yellow(o):O.gray(o)}function Ac(e,t){let n=new Map;for(let o of e){let r=Dc(o.file,t),s=n.get(r)??[];n.has(r)||n.set(r,s),s.push(o)}return[...n.keys()].sort().map(o=>[o,n.get(o)])}function $f(e,t){let n=(L,G)=>L.filter(H=>H.state===G).length,o=e.length,r=n(e,"captured"),s=n(e,"spec"),a=n(e,"blocked"),u=n(e,"broken"),p=n(e,"placeholder"),b=new Set(e.map(L=>L.file)).size,k=Ac(e,t),$=[];$.push(` ${O.bold("Screenshots")} ${O.green(`${r}/${o} captured`)} ${O.gray("\xB7")} ${s>0?O.yellow(`${s} ready`):O.gray("0 ready")}`+(a>0?` ${O.gray("\xB7")} ${O.cyan(`${a} blocked`)}`:"")+(u>0?` ${O.gray("\xB7")} ${O.red(`${u} broken`)}`:"")+` ${O.gray("\xB7")} ${p>0?O.red(`${p} to spec`):O.gray("0 to spec")} `+O.gray(`across ${b} article${b===1?"":"s"}`));let x=o>0?Math.round(r/o*100):0;$.push(` [${yc(r,o,32)}] ${O.bold(`${x}%`)}`),$.push("");let j=k.map(([L])=>Us(L)),v=Math.min(32,Math.max(7,...j.map(L=>L.name.length))),D=L=>L.length>v?`${L.slice(0,v-1)}\u2026`:Xo(L,v),E=[{head:"Ready",w:5,state:"spec",color:O.yellow},...a>0?[{head:"Blocked",w:7,state:"blocked",color:O.cyan}]:[],...u>0?[{head:"Broken",w:6,state:"broken",color:O.red}]:[],{head:"To spec",w:7,state:"placeholder",color:O.red}];$.push(O.gray(` ${Xo("#",2)} ${Xo("Chapter",v)} ${yt("Captured",8)} ${E.map(L=>yt(L.head,L.w)).join(" ")} Progress`));for(let[L,[,G]]of k.entries()){let H=n(G,"captured"),se=G.length,{num:Be,name:fe}=j[L],Xe=H===se,me=D(fe),Qe=E.map(be=>{let qe=n(G,be.state);return qe>0?be.color(yt(String(qe),be.w)):O.gray(yt("\u2014",be.w))}).join(" ");$.push(` ${O.cyan(Xo(Be,2))} ${Xe?O.green(me):H>0?me:O.gray(me)} ${Xe?O.green(yt(`${H}/${se}`,8)):yt(`${H}/${se}`,8)} ${Qe} ${yc(H,se,10)} ${bf(H,se)}`)}$.push("");let U=u>0?` ${O.gray("\xB7")} ${u} spec${u===1?"":"s"} broken \u2014 ${O.bold(`/screenshot ${e.find(L=>L.state==="broken").id}`)} re-authors ${u===1?"it":"the first"}`:"";if(p>0)$.push(` ${st("\u2192",O.yellow)}${p} placeholder${p===1?"":"s"} still need a spec \u2014 run ${O.bold("/screenshot author --all")}${U}`);else if(s>0){let L=e.filter(H=>H.state==="spec"&&H.mutates).length,G=L>0?O.gray(` \u2014 ${s-L} in one go; ${L} write${L===1?"s":""} real data and run${L===1?"s":""} one at a time`):"";$.push(` ${st("\u2192",O.yellow)}${s} spec${s===1?"":"s"} ready to capture \u2014 run ${O.bold("/screenshot capture")}${G}${U}`)}else a>0?$.push(` ${st("\u2192",O.cyan)}${a} shot${a===1?"":"s"} blocked \u2014 stage the data below, then ${O.bold("/screenshot capture --all --retry-blocked")}${U}`):u>0?$.push(` ${st("\u2192",O.red)}${u} spec${u===1?"":"s"} broken \u2014 ${O.bold(`/screenshot ${e.find(L=>L.state==="broken").id}`)} re-authors ${u===1?"it":"the first"} (re-capturing fails the same way)`):$.push(` ${st("\u2713",O.green)}all ${o} captured \u2014 ${O.bold("/publish")} embeds them in the articles`);if(r>0&&r<o){let L=k.filter(([,G])=>G.every(H=>H.state!=="captured")).map(([G])=>{let{num:H,name:se}=Us(G);return H?`${H} ${se}`:se});L.length>0&&$.push(O.gray(` Furthest behind: ${L.slice(0,5).join(" \xB7 ")}${L.length>5?" \u2026":""} (0% captured)`))}return $.push(...Sc(e),...Pc(e),...Tc(e)),$.push(""),$.push(Rc),$.push(O.gray(" Drill in: /screenshot list <chapter#> \xB7 /screenshot list --detail (all)")),$}function xf(e,t,n){let o=new Set(e.map(a=>a.file)),r=e.filter(a=>a.state==="captured").length,s=[` ${O.bold("Screenshots")}: ${e.length} placeholder${e.length===1?"":"s"} across ${o.size} article${o.size===1?"":"s"} \xB7 ${O.green(`${r} captured`)}${n?O.gray(` \xB7 scope ${n}`):""}`,""];for(let[a,u]of Ac(e,t)){let p=u.filter(j=>j.state==="captured").length,{num:b,name:k}=Us(a),$=b?`${b} \xB7 ${k}`:k;s.push(` ${O.bold($)} ${O.gray(`(${p}/${u.length})`)}`);let x=new Map;for(let j of u){let v=kf(j.file),D=x.get(v)??[];x.has(v)||x.set(v,D),D.push(j)}for(let j of[...x.keys()].sort()){s.push(O.gray(` ${j}`));for(let v of x.get(j)){let D=ff[v.state];if(s.push(` ${st(hf[v.state],D)}${D(v.id)}`),(v.state==="blocked"||v.state==="broken")&&v.reason)for(let E of qn(v.reason,84))s.push(O.gray(` ${E}`))}}}return s.push(...Sc(e),...Pc(e),...Tc(e)),s.push("",Rc),s}function rr(e,t){let n=t?.scope;if(e.length===0)return[n?`No screenshot placeholders under ${n}.`:"No screenshot placeholders found in the docs."];let o=t?.docsDir??qs;return t?.detail||n?xf(e,o,n):$f(e,o)}function Vs(e,t){if(!t||!/^\d+$/.test(t))return t;let n=hn(e)?.docsDir??qs,o=String(Number(t));for(let{file:r}of kc(e)){let s=Dc(r,n),a=/^(\d+)/.exec(s);if(a&&String(Number(a[1]))===o)return s}return t}function vf(e){let t=e.replace(/\\/g,"/").split("/").filter(Boolean),n=t.pop()??e;return{category:t.pop()??"(docs root)",article:n}}function Qo(e,t,n){let o=new Map;for(let s of t){let{category:a,article:u}=vf(s.file),p=o.get(a)??new Map;o.set(a,p);let b=p.get(u)??[];p.set(u,b),b.push(s)}let r=s=>n==="author"||!s.mutates;for(let s of[...o.keys()].sort()){let a=o.get(s),u=[...a.values()].flat().filter(r).map(p=>p.id);u.length>0&&e.push({kind:"category",action:n,label:`${s} (${u.length})`,ids:u,depth:1});for(let p of[...a.keys()].sort()){let b=a.get(p),k=b.filter(r).map($=>$.id);k.length>0&&e.push({kind:"article",action:n,label:`${p} (${k.length})`,ids:k,depth:2});for(let $ of b){let x=n==="capture"&&$.mutates?" \u26A0 creates real data \u2014 runs only alone":void 0;e.push({kind:"shot",action:n,label:$.id,ids:[$.id],depth:3,...x?{note:x}:{}})}}}}function wc(e,t=58){if(!e)return"";let n=e.replace(/\s+/g," ").trim();return n.length>t?`${n.slice(0,t-1)}\u2026`:n}function Gs(e,t,n){let o=t.filter(r=>!r.mutates);o.length>1&&e.push({kind:"all",action:"capture",label:`Retry ${o.length} blocked shots (stage the data first)`,ids:o.map(r=>r.id),depth:0});for(let r of[...t].sort((s,a)=>s.id.localeCompare(a.id)))e.push({kind:"shot",action:"capture",label:r.id,ids:[r.id],depth:1,note:` \u2298 ${wc(r.reason)}`});n.length>1&&e.push({kind:"all",action:"author",label:`Re-author ${n.length} broken specs`,ids:n.map(r=>r.id),depth:0});for(let r of[...n].sort((s,a)=>s.id.localeCompare(a.id)))e.push({kind:"shot",action:"author",label:r.id,ids:[r.id],depth:1,note:` \u2717 ${wc(r.reason)} \u2014 re-authors`})}function Ec(e){let t=e.filter(s=>s.state==="placeholder"),n=e.filter(s=>s.state==="spec"),o=[];t.length>1&&o.push({kind:"all",action:"author",label:`Author all ${t.length} remaining specs`,ids:t.map(s=>s.id),depth:0}),Qo(o,t,"author");let r=n.filter(s=>!s.mutates);return r.length>1&&o.push({kind:"all",action:"capture",label:`Capture all ${r.length} ready specs`,ids:r.map(s=>s.id),depth:0}),Qo(o,n,"capture"),Gs(o,e.filter(s=>s.state==="blocked"),e.filter(s=>s.state==="broken")),o}var Ws=["MANDATORY \u2014 a missing prerequisite must SKIP, never time out. Two rules, applied to EVERY locator","in the spec, not just the first one or the one shown in the skill's example:","1. A bare `await x.waitFor(...)` is itself the bug: on a missing element it THROWS and the run"," reports a hard failure with a stack trace instead of a staging instruction. Every wait must be",' `await x.waitFor({ state: "visible", timeout: 10_000 }).catch(() => {});` followed by',' `if ((await x.count()) === 0) test.skip(true, "Prerequisite not met: \u2026")`. That applies to each'," element you wait on or click IN TURN \u2014 tabs, toggles, rows, buttons reached after the first"," guard \u2014 because any of them can be absent for the same reason the data is.",' And when a wait DOES time out, diagnose before you guard: a skip asserts "your app lacks this',' data", so if the real cause is a locator matching a node you did not intend (a rollup row, a'," sibling with the same testid), that assertion is a lie and sends the user to stage what they"," already have. Narrow the selector first, THEN guard.","2. Never reason about whether a role/text locator is unique; you cannot verify that by reading"," source, and Playwright fails the whole spec on 2+ matches. Every `getByRole`/`getByText`/"," `:has-text` without a unique data-testid gets `.first()` or a container scope"," (`page.locator('[data-testid=\"\u2026\"]').getByRole(\u2026)`). If you catch yourself writing a comment"," ARGUING a selector is unique, scope it instead \u2014 that argument is what fails.","3. Every skip reason opens with the SHARED staging action, worded identically across every shot",' that needs the same thing, naming where in the product to do it ("Create an API key in Settings',' \u2192 API Keys."); shot-specific detail (counts, which record, which state) goes in LATER sentences.'," The dashboard groups blocked shots by that first sentence, so a near-duplicate wording splits one"," chore into two. Grep sibling specs and reuse their exact opening sentence.","Before you finish, re-read each spec you wrote and check all three rules line by line."].join(`
|
|
315
|
+
`,"utf8")}function Os(e){return e&&/data-testid|\btest-?id\b|has(?:n't| not) landed/i.test(e)?"testid":"data"}function hc(e,t,n){if(t.length===0)return;let o=_t(e);for(let r of t){let s=r.specSource?pc(r.specSource):void 0;o.shots[r.id]={outcome:r.outcome,at:n,...r.reason?{reason:r.reason}:{},...r.outcome==="skipped"?{blockedBy:Os(r.reason)}:{},...s?{specHash:s}:{}}}lf(e,o)}function _s(e,t,n){let o=e.shots[t];return!o||n===null||!o.specHash||o.specHash!==pc(n)?null:o}function fc(e){let t=_t(e).shots;return Object.keys(t).filter(n=>t[n].outcome==="failed").sort()}function Ls(e,t,n){let o=_s(_t(e),t,n);return o?.outcome==="failed"?o.reason??null:null}var bc=e=>e.replace(/\\/g,"/").replace(/\/+$/,"");function mc(e){let t=e.includes("--detail")||e.includes("-d"),n=e.find(o=>o!=="--detail"&&o!=="-d");return{mode:"list",scope:n?bc(n):void 0,...t?{detail:!0}:{}}}function Hn(e){let t=(e[0]??"").toLowerCase();if(t==="capture")return{mode:"capture",rest:e.slice(1)};if(t==="checklist"||t==="setup")return{mode:"checklist"};if(t==="list")return mc(e.slice(1));t==="author"&&(e=e.slice(1));let n=!e.includes("--no-setup"),o=e.filter(s=>s!=="--no-setup");if(o[0]==="--list")return mc(o.slice(1));let r=o[0];return r?r==="--all"?{mode:"all",setup:n}:/[\\/]/.test(r)||r.endsWith(".md")?{mode:"scope",scope:bc(r),setup:n}:{mode:"single",id:r}:{mode:"list",picker:!0}}function ze(e,t){let n=hn(e),o=n?.captureDir??tr,r=n?.outputDir??cf,s=(u,p)=>Jo(u)?Zo(u,p):er(e,u,p),a=_t(e);return kc(e,{scope:t}).map(({id:u,file:p})=>{let b=s(o,`${u}.spec.ts`),k=null;if(Ms(b))try{k=Bs(b,"utf8")}catch{}let $=Ms(s(r,`${u}.png`)),x=k===null?null:_s(a,u,k),j=k!==null&&df(k),v=$?"captured":k===null?"placeholder":j||x?.outcome==="skipped"?"blocked":x?.outcome==="failed"?"broken":"spec",D=k===null?void 0:xc(k),E=j?pf(k):x?.reason,U=j?"testid":x?.outcome==="skipped"?Os(x.reason):void 0;return{id:u,file:p,state:v,...D?{mutates:D.mutates,...D.declared?{}:{undeclared:!0}}:{},...k!==null&&gc(k).length>0?{unguardedWaits:gc(k)}:{},...v==="blocked"||v==="broken"?{...E?{reason:E}:{},...U?{blockedBy:U}:{}}:{}}})}function nr(e,t){let n=hn(e)?.captureDir??tr,o=Jo(n)?Zo(n,`${t}.spec.ts`):er(e,n,`${t}.spec.ts`);try{return Bs(o,"utf8")}catch{return null}}function $c(e,t){let n=hn(e)?.captureDir??tr;return Ms(Jo(n)?Zo(n,`${t}.spec.ts`):er(e,n,`${t}.spec.ts`))}function uf(e,t,n){let o=[n.trim()];for(let r=t+1;r<e.length;r++){let s=/^\s*\/\/\s?(.*)$/.exec(e[r]);if(!s)break;let a=s[1].trim();if(!a||/^(CAPTURE-[A-Z]+:|TODO\b|NOTE\b|Grounding\b|\d+\.\s)/.test(a)||(o.push(a),o.join(" ").length>400))break}return o.join(" ")}function xc(e){let t=e.split(/\r?\n/),n=t.findIndex(u=>/^\s*\/\/\s*CAPTURE-SAFETY:\s*(readonly|mutates)\b/i.test(u)),o=n>=0?/^\s*\/\/\s*CAPTURE-SAFETY:\s*(readonly|mutates)\b[\s—:–-]*(.*)$/i.exec(t[n]):null;if(o){let u=uf(t,n,o[2]??"");return{mutates:o[1].toLowerCase()==="mutates",declared:!0,what:u||void 0}}let r=/setInputFiles/.test(e),s=/\{\s*name:\s*['"][^'"]*\b(create|import|save|apply|confirm|delete|submit|send|mint)\b[^'"]*['"][^}]*\}\s*\)\s*\.click\(/i.test(e),a=/\.fill\(/.test(e);return{mutates:r||a&&s,declared:!1}}function df(e){return/(^|\n)\s*test\.skip\s*\(\s*['"`]/.test(e)}function pf(e){let t=/\/\/\s*CAPTURE-TODO:\s*(\{.*\})\s*$/m.exec(e),n,o;if(t)try{let s=JSON.parse(t[1]);n=s.testid,o=s.file}catch{}return`Inert until your developers add ${n?`data-testid="${n}"${o?` in ${o}`:""}`:"a stable data-testid"} \u2014 the spec is skipped at compile time, so no capture run can produce this shot. /screenshot checklist builds the hand-off.`}function gc(e){let t=r=>{if(/^\s*\*/.test(r))return"";let s=/(?<!:)\/\//.exec(r);return s?r.slice(0,s.index):r},n=[],o=e.split(/\r?\n/).map(t);for(let[r,s]of o.entries()){if(!/\.waitFor\s*\(/.test(s))continue;let a=s;for(let u=r+1;u<o.length&&!/;\s*$/.test(a.trim())&&u<r+4;u++)a+=o[u];/\.catch\s*\(/.test(a)||n.push(r+1)}return n}function vc(e){return(e??"").trim().replace(/[.\s]+$/,"")}function Cc(e,t){let n=hn(e)?.captureDir??tr,o=Jo(n)?Zo(n,`${t}.spec.ts`):er(e,n,`${t}.spec.ts`);try{return xc(Bs(o,"utf8"))}catch{return{mutates:!1,declared:!1}}}function or(e){return[...new Set(e.filter(t=>t.state==="captured").map(t=>t.file))].sort()}var hf={placeholder:"\u25CB",spec:"\u25D0",blocked:"\u2298",broken:"\u2717",captured:"\u25CF"},ff={placeholder:O.gray,spec:O.yellow,blocked:O.cyan,broken:O.red,captured:O.green},Rc=` ${O.green("\u25CF captured")} ${O.yellow("\u25D0 ready")} ${O.cyan("\u2298 blocked")} ${O.red("\u2717 broken")} ${O.gray("\u25CB to spec")}`;function st(e,t=n=>n){return`${t(e)} `}function mf(e){let t=e.replace(/\s+/g," ").trim().replace(/^Prerequisite not met:\s*/i,""),n=/^(.*?[a-z0-9)"'”’])\.(?:\s|$)/i.exec(t);return(n?n[1]:t).trim()}function Hs(e){let t=new Map;for(let n of e){if(n.state!=="blocked")continue;let o=(n.reason??"No reason recorded \u2014 re-run this capture to find out why.").replace(/\s+/g," ").trim(),r=n.blockedBy??"data",s=mf(o),a=`${r} ${s.toLowerCase()}`,u=t.get(a)??{reason:s,blockedBy:r,ids:[]};t.has(a)||t.set(a,u),u.ids.push(n.id)}return[...t.values()].sort((n,o)=>o.ids.length-n.ids.length||n.reason.localeCompare(o.reason))}function Sc(e){let t=Hs(e);if(t.length===0)return[];let n=t.filter(s=>s.blockedBy==="data"),o=t.filter(s=>s.blockedBy==="testid"),r=[""];if(n.length>0){let s=n.reduce((u,p)=>u+p.ids.length,0);r.push(` ${O.bold("Stage this data")} ${O.gray(`\u2014 unblocks ${s} shot${s===1?"":"s"} in ${n.length} action${n.length===1?"":"s"}`)}`);let a=8;for(let u of n.slice(0,a)){let p=qn(u.reason,86);r.push(` ${O.cyan(yt(`${u.ids.length}\xD7`,4))} ${p[0]??""}`);for(let k of p.slice(1))r.push(` ${k}`);let b=u.ids.slice(0,4).join(" \xB7 ");r.push(O.gray(` ${b}${u.ids.length>4?` \xB7 +${u.ids.length-4} more`:""}`))}n.length>a&&r.push(O.gray(` \u2026 ${n.length-a} more staging action${n.length-a===1?"":"s"} \u2014 /screenshot list --detail`))}if(o.length>0){let s=o.reduce((a,u)=>a+u.ids.length,0);r.push(` ${st("\u2298",O.cyan)}${s} shot${s===1?"":"s"} need${s===1?"s":""} a data-testid from your developers \u2014 ${O.bold("/screenshot checklist")} builds the hand-off.`)}return r}function Pc(e){let t=e.filter(o=>(o.unguardedWaits?.length??0)>0);if(t.length===0)return[];let n=t.reduce((o,r)=>o+(r.unguardedWaits?.length??0),0);return["",` ${st("\u26A0",O.yellow)}${t.length} spec${t.length===1?"":"s"} ${t.length===1?"has":"have"} ${n} unguarded wait${n===1?"":"s"}`+O.gray(" \u2014 on missing data these fail with a stack trace instead of telling you what to stage"),O.gray(` ${t.slice(0,6).map(o=>`${o.id}:${o.unguardedWaits.join(",")}`).join(" \xB7 ")}${t.length>6?` \xB7 +${t.length-6} more`:""}`),O.gray(` Re-author to guard them: ${O.bold("/screenshot author <id>")}`)]}function Tc(e){let t=e.filter(o=>o.undeclared);if(t.length===0)return[];let n=t.filter(o=>o.mutates);return["",` ${st("\u26A0",O.yellow)}${t.length} spec${t.length===1?"":"s"} carr${t.length===1?"ies":"y"} no CAPTURE-SAFETY declaration`+(n.length>0?O.gray(` \u2014 ${n.length} of them read as "writes real data" by heuristic, so bulk capture benches ${n.length===1?"it":"them"}`):""),O.gray(` ${t.slice(0,6).map(o=>o.id).join(" \xB7 ")}${t.length>6?` \xB7 +${t.length-6} more`:""}`),O.gray(` Re-author them to declare it: ${O.bold("/screenshot author <id>")}`)]}var Xo=(e,t)=>e.length>=t?e:e+" ".repeat(t-e.length),yt=(e,t)=>e.length>=t?e:" ".repeat(t-e.length)+e;function Dc(e,t){let n=e.replace(/\\/g,"/"),o=t.replace(/\\/g,"/").replace(/\/+$/,"");o&&(n===o||n.startsWith(`${o}/`))&&(n=n.slice(o.length+1));let r=n.split("/").filter(Boolean);return r.length>1?r[0]:""}var gf=new Set(["and","or","of","the","to","a","an","in","on","for","with","vs"]),yf=new Set(["ai","api","cli","ui","ux","url","id","http","https","json","xml","sdk","csv","pdf","oauth","sso","mfa","io","faq"]);function wf(e){return e.replace(/[-_]+/g," ").trim().split(/\s+/).filter(Boolean).map((n,o)=>{let r=n.toLowerCase();return yf.has(r)?r.toUpperCase():o>0&&gf.has(r)?r:r.charAt(0).toUpperCase()+r.slice(1)}).join(" ")}function Us(e){if(!e)return{num:"",name:"(docs root)"};let t=/^(\d+)[-_](.+)$/.exec(e);return{num:t?t[1]:"",name:wf(t?t[2]:e)}}function kf(e){return e.replace(/\\/g,"/").split("/").filter(Boolean).pop()??e}function yc(e,t,n){let o=t>0?Math.round(e/t*n):0;return O.green("\u2588".repeat(o))+O.gray("\u2591".repeat(Math.max(0,n-o)))}function bf(e,t){let n=t>0?Math.round(e/t*100):0,o=yt(`${n}%`,4);return n===100?O.green(o):n>0?O.yellow(o):O.gray(o)}function Ac(e,t){let n=new Map;for(let o of e){let r=Dc(o.file,t),s=n.get(r)??[];n.has(r)||n.set(r,s),s.push(o)}return[...n.keys()].sort().map(o=>[o,n.get(o)])}function $f(e,t){let n=(L,G)=>L.filter(H=>H.state===G).length,o=e.length,r=n(e,"captured"),s=n(e,"spec"),a=n(e,"blocked"),u=n(e,"broken"),p=n(e,"placeholder"),b=new Set(e.map(L=>L.file)).size,k=Ac(e,t),$=[];$.push(` ${O.bold("Screenshots")} ${O.green(`${r}/${o} captured`)} ${O.gray("\xB7")} ${s>0?O.yellow(`${s} ready`):O.gray("0 ready")}`+(a>0?` ${O.gray("\xB7")} ${O.cyan(`${a} blocked`)}`:"")+(u>0?` ${O.gray("\xB7")} ${O.red(`${u} broken`)}`:"")+` ${O.gray("\xB7")} ${p>0?O.red(`${p} to spec`):O.gray("0 to spec")} `+O.gray(`across ${b} article${b===1?"":"s"}`));let x=o>0?Math.round(r/o*100):0;$.push(` [${yc(r,o,32)}] ${O.bold(`${x}%`)}`),$.push("");let j=k.map(([L])=>Us(L)),v=Math.min(32,Math.max(7,...j.map(L=>L.name.length))),D=L=>L.length>v?`${L.slice(0,v-1)}\u2026`:Xo(L,v),E=[{head:"Ready",w:5,state:"spec",color:O.yellow},...a>0?[{head:"Blocked",w:7,state:"blocked",color:O.cyan}]:[],...u>0?[{head:"Broken",w:6,state:"broken",color:O.red}]:[],{head:"To spec",w:7,state:"placeholder",color:O.red}];$.push(O.gray(` ${Xo("#",2)} ${Xo("Chapter",v)} ${yt("Captured",8)} ${E.map(L=>yt(L.head,L.w)).join(" ")} Progress`));for(let[L,[,G]]of k.entries()){let H=n(G,"captured"),se=G.length,{num:Be,name:fe}=j[L],Xe=H===se,me=D(fe),Qe=E.map(be=>{let qe=n(G,be.state);return qe>0?be.color(yt(String(qe),be.w)):O.gray(yt("\u2014",be.w))}).join(" ");$.push(` ${O.cyan(Xo(Be,2))} ${Xe?O.green(me):H>0?me:O.gray(me)} ${Xe?O.green(yt(`${H}/${se}`,8)):yt(`${H}/${se}`,8)} ${Qe} ${yc(H,se,10)} ${bf(H,se)}`)}$.push("");let U=u>0?` ${O.gray("\xB7")} ${u} spec${u===1?"":"s"} broken \u2014 ${O.bold(`/screenshot ${e.find(L=>L.state==="broken").id}`)} re-authors ${u===1?"it":"the first"}`:"";if(p>0)$.push(` ${st("\u2192",O.yellow)}${p} placeholder${p===1?"":"s"} still need a spec \u2014 run ${O.bold("/screenshot author --all")}${U}`);else if(s>0){let L=e.filter(H=>H.state==="spec"&&H.mutates).length,G=L>0?O.gray(` \u2014 ${s-L} in one go; ${L} write${L===1?"s":""} real data and run${L===1?"s":""} one at a time`):"";$.push(` ${st("\u2192",O.yellow)}${s} spec${s===1?"":"s"} ready to capture \u2014 run ${O.bold("/screenshot capture")}${G}${U}`)}else a>0?$.push(` ${st("\u2192",O.cyan)}${a} shot${a===1?"":"s"} blocked \u2014 stage the data below, then ${O.bold("/screenshot capture --all --retry-blocked")}${U}`):u>0?$.push(` ${st("\u2192",O.red)}${u} spec${u===1?"":"s"} broken \u2014 ${O.bold(`/screenshot ${e.find(L=>L.state==="broken").id}`)} re-authors ${u===1?"it":"the first"} (re-capturing fails the same way)`):$.push(` ${st("\u2713",O.green)}all ${o} captured \u2014 ${O.bold("/publish")} embeds them in the articles`);if(r>0&&r<o){let L=k.filter(([,G])=>G.every(H=>H.state!=="captured")).map(([G])=>{let{num:H,name:se}=Us(G);return H?`${H} ${se}`:se});L.length>0&&$.push(O.gray(` Furthest behind: ${L.slice(0,5).join(" \xB7 ")}${L.length>5?" \u2026":""} (0% captured)`))}return $.push(...Sc(e),...Pc(e),...Tc(e)),$.push(""),$.push(Rc),$.push(O.gray(" Drill in: /screenshot list <chapter#> \xB7 /screenshot list --detail (all)")),$}function xf(e,t,n){let o=new Set(e.map(a=>a.file)),r=e.filter(a=>a.state==="captured").length,s=[` ${O.bold("Screenshots")}: ${e.length} placeholder${e.length===1?"":"s"} across ${o.size} article${o.size===1?"":"s"} \xB7 ${O.green(`${r} captured`)}${n?O.gray(` \xB7 scope ${n}`):""}`,""];for(let[a,u]of Ac(e,t)){let p=u.filter(j=>j.state==="captured").length,{num:b,name:k}=Us(a),$=b?`${b} \xB7 ${k}`:k;s.push(` ${O.bold($)} ${O.gray(`(${p}/${u.length})`)}`);let x=new Map;for(let j of u){let v=kf(j.file),D=x.get(v)??[];x.has(v)||x.set(v,D),D.push(j)}for(let j of[...x.keys()].sort()){s.push(O.gray(` ${j}`));for(let v of x.get(j)){let D=ff[v.state];if(s.push(` ${st(hf[v.state],D)}${D(v.id)}`),(v.state==="blocked"||v.state==="broken")&&v.reason)for(let E of qn(v.reason,84))s.push(O.gray(` ${E}`))}}}return s.push(...Sc(e),...Pc(e),...Tc(e)),s.push("",Rc),s}function rr(e,t){let n=t?.scope;if(e.length===0)return[n?`No screenshot placeholders under ${n}.`:"No screenshot placeholders found in the docs."];let o=t?.docsDir??qs;return t?.detail||n?xf(e,o,n):$f(e,o)}function Vs(e,t){if(!t||!/^\d+$/.test(t))return t;let n=hn(e)?.docsDir??qs,o=String(Number(t));for(let{file:r}of kc(e)){let s=Dc(r,n),a=/^(\d+)/.exec(s);if(a&&String(Number(a[1]))===o)return s}return t}function vf(e){let t=e.replace(/\\/g,"/").split("/").filter(Boolean),n=t.pop()??e;return{category:t.pop()??"(docs root)",article:n}}function Qo(e,t,n){let o=new Map;for(let s of t){let{category:a,article:u}=vf(s.file),p=o.get(a)??new Map;o.set(a,p);let b=p.get(u)??[];p.set(u,b),b.push(s)}let r=s=>n==="author"||!s.mutates;for(let s of[...o.keys()].sort()){let a=o.get(s),u=[...a.values()].flat().filter(r).map(p=>p.id);u.length>0&&e.push({kind:"category",action:n,label:`${s} (${u.length})`,ids:u,depth:1});for(let p of[...a.keys()].sort()){let b=a.get(p),k=b.filter(r).map($=>$.id);k.length>0&&e.push({kind:"article",action:n,label:`${p} (${k.length})`,ids:k,depth:2});for(let $ of b){let x=n==="capture"&&$.mutates?" \u26A0 creates real data \u2014 runs only alone":void 0;e.push({kind:"shot",action:n,label:$.id,ids:[$.id],depth:3,...x?{note:x}:{}})}}}}function wc(e,t=58){if(!e)return"";let n=e.replace(/\s+/g," ").trim();return n.length>t?`${n.slice(0,t-1)}\u2026`:n}function Gs(e,t,n){let o=t.filter(r=>!r.mutates);o.length>1&&e.push({kind:"all",action:"capture",label:`Retry ${o.length} blocked shots (stage the data first)`,ids:o.map(r=>r.id),depth:0});for(let r of[...t].sort((s,a)=>s.id.localeCompare(a.id)))e.push({kind:"shot",action:"capture",label:r.id,ids:[r.id],depth:1,note:` \u2298 ${wc(r.reason)}`});n.length>1&&e.push({kind:"all",action:"author",label:`Re-author ${n.length} broken specs`,ids:n.map(r=>r.id),depth:0});for(let r of[...n].sort((s,a)=>s.id.localeCompare(a.id)))e.push({kind:"shot",action:"author",label:r.id,ids:[r.id],depth:1,note:` \u2717 ${wc(r.reason)} \u2014 re-authors`})}function Ec(e){let t=e.filter(s=>s.state==="placeholder"),n=e.filter(s=>s.state==="spec"),o=[];t.length>1&&o.push({kind:"all",action:"author",label:`Author all ${t.length} remaining specs`,ids:t.map(s=>s.id),depth:0}),Qo(o,t,"author");let r=n.filter(s=>!s.mutates);return r.length>1&&o.push({kind:"all",action:"capture",label:`Capture all ${r.length} ready specs`,ids:r.map(s=>s.id),depth:0}),Qo(o,n,"capture"),Gs(o,e.filter(s=>s.state==="blocked"),e.filter(s=>s.state==="broken")),o}var Ws=["MANDATORY \u2014 a missing prerequisite must SKIP, never time out. Two rules, applied to EVERY locator","in the spec, not just the first one or the one shown in the skill's example:","1. A bare `await x.waitFor(...)` is itself the bug: on a missing element it THROWS and the run"," reports a hard failure with a stack trace instead of a staging instruction. Every wait must be",' `await x.waitFor({ state: "visible", timeout: 10_000 }).catch(() => {});` followed by',' `if ((await x.count()) === 0) test.skip(true, "Prerequisite not met: \u2026")`. That applies to each'," element you wait on or click IN TURN \u2014 tabs, toggles, rows, buttons reached after the first"," guard \u2014 because any of them can be absent for the same reason the data is.",' And when a wait DOES time out, diagnose before you guard: a skip asserts "your app lacks this',' data", so if the real cause is a locator matching a node you did not intend (a rollup row, a'," sibling with the same testid), that assertion is a lie and sends the user to stage what they"," already have. Narrow the selector first, THEN guard."," Two corollaries, both of which have cost a wasted repair round:"," (a) An element that is not RENDERED is not absent. A collapsed tree node, an unopened accordion,"," an inactive tab, a virtualised list or page 2 of a table keeps its children out of the DOM."," EXPAND/scroll/switch to reach the target \u2014 that is a step to script, not data to stage."," (b) When you narrow past a decoy, count the UNNARROWED set too and put the number in the reason:",' "Found 6 folder roll-ups but no endpoint-level score \u2014 if one exists it may not be rendered".',' A bare "prerequisite not met" cannot tell absent data from an over-narrow selector.',"2. Never reason about whether a role/text locator is unique; you cannot verify that by reading"," source, and Playwright fails the whole spec on 2+ matches. Every `getByRole`/`getByText`/"," `:has-text` without a unique data-testid gets `.first()` or a container scope"," (`page.locator('[data-testid=\"\u2026\"]').getByRole(\u2026)`). If you catch yourself writing a comment"," ARGUING a selector is unique, scope it instead \u2014 that argument is what fails.","3. Every skip reason opens with the SHARED staging action, worded identically across every shot",' that needs the same thing, naming where in the product to do it ("Create an API key in Settings',' \u2192 API Keys."); shot-specific detail (counts, which record, which state) goes in LATER sentences.'," The dashboard groups blocked shots by that first sentence, so a near-duplicate wording splits one"," chore into two. Grep sibling specs and reuse their exact opening sentence.","Before you finish, re-read each spec you wrote and check all three rules line by line."].join(`
|
|
316
316
|
`),Fs="MANDATORY, check before you finish: every spec you write or touch must carry exactly one `// CAPTURE-SAFETY: readonly` or `// CAPTURE-SAFETY: mutates \u2014 <what it creates>; cleanup: <how>` line at the top. Re-read each file you wrote and add it if missing \u2014 a spec without it is classified by guesswork. A fail-by-design spec (the app rejects the input) is `readonly`.";function jc(e,t){let n=e.paths.filter(o=>t?.get(o));return["Run the emit-screenshot-spec skill to author the document360-capture spec for EACH of these","SCREENSHOT placeholder ids:",e.paths.map(o=>`- ${o}`).join(`
|
|
317
317
|
`),"",...n.length>0?["REPAIR \u2014 these already have a spec, and it FAILED its last capture. Read the existing spec","first and fix the named failure specifically; do not re-emit the same locator and hope. A",'"strict mode violation" means the locator matched 2+ elements (scope it to a stable container',"or take .first()); a click timeout means the element was absent or unreachable (if the data","simply is not staged, the spec must test.skip with a reason naming what to stage, not time out).",...n.map(o=>`- ${o}: ${t.get(o).replace(/\s+/g," ").trim()}`),""]:[],"For each: locate its <!-- SCREENSHOT --> block in the docs tree (the configured docsDir); read the product source for","EXACT routes + stable data-testid selectors (never guess); enter the prepared context via","captureScope(); guard data prerequisites with test.skip(reason); write <captureDir>/<id>.spec.ts","(skip-with-TODO if a stable selector is missing). Do NOT run other skills. Report the specs you","wrote and any TODO data-testids.",Ws,Fs].join(`
|
|
318
318
|
`)}async function sr(e,t,n){let o=Hn(e);if(o.mode==="list"){let u=t?.cwd??process.cwd(),p=Vs(u,o.scope),b=hn(u)?.docsDir??qs,k=ze(u,p);for(let $ of rr(k,{scope:p,detail:o.detail,docsDir:b}))console.log($);return{kind:"continue"}}if(o.mode==="single")return{kind:"forward-to-agent",prompt:[`Run the emit-screenshot-spec skill for the SCREENSHOT placeholder with id \`${o.id}\`.`,"Locate its <!-- SCREENSHOT --> block in the docs tree (the configured docsDir), then follow that skill exactly: read the","product source for EXACT routes + stable data-testid selectors (never guess); enter the prepared","context via captureScope(); guard data prerequisites with test.skip(reason); write",`<captureDir>/${o.id}.spec.ts. Report per the skill's writer-facing format \u2014 spec path, data to`,"stage, dev requests only if any, and the capture command; no grounding narration.",Ws,Fs,...n?.lastFailure?["","REPAIR \u2014 the existing spec FAILED its last capture with:",n.lastFailure,'Read the existing spec first and fix that failure specifically. A "strict mode violation" means the',"locator matched 2+ elements \u2014 scope it to a stable container or take .first(); a click timeout means","the element was absent or unreachable \u2014 if that's because the data isn't staged, the spec must","test.skip with a reason naming what to stage, not time out. Re-verify every selector you keep","against the product source; do not re-emit the same locator and hope."]:[]].join(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document360-writer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.161",
|
|
4
4
|
"description": "Standalone documentation agent CLI. Reads your code, writes your docs. Specialized for Document360 publishing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@inquirer/prompts": "^8.4.3",
|
|
35
35
|
"commander": "^14.0.3",
|
|
36
36
|
"diff": "^8.0.4",
|
|
37
|
-
"document360-engine": "^0.2.
|
|
37
|
+
"document360-engine": "^0.2.159",
|
|
38
38
|
"ink": "^5.2.1",
|
|
39
39
|
"picocolors": "^1.1.1",
|
|
40
40
|
"react": "^18.3.1",
|