document360-writer 0.5.314 → 0.5.315

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1 -1
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -336,7 +336,7 @@ ${n.map(c=>` \u25CB ${c.title}`).join(`
336
336
  `)}`};let a=i[0].title;IS(e,a,{doneAt:new Date().toISOString()});let l=gy(e)?.next;return{ok:!0,line:`\u2713 Marked done: ${a}${l?` \u2014 next up: ${l.title}${l.run?` (${l.run})`:""}`:" \u2014 that was the last step: campaign complete."}`}}function yy(e,t){let n=r=>r.trim().replace(/\s+/g," ").toLowerCase(),o=n(t);return o.startsWith("/")?Ms(e).find(r=>r.status==="open"&&!!r.run&&n(r.run)===o)??null:null}function Ga(e,t){let n=(Array.isArray(t)?t:[t]).filter(s=>s.trim().startsWith("/"));if(n.length===0)return[];let o=new Set,r=[];for(let s of Ms(e))s.status==="open"&&n.some(i=>uy(i,s.run))&&(o.has(s.title)||(o.add(s.title),r.push(s.title)));return r}function Ye(e,t){if(!t)return[];try{let n=Ga(e,t).filter(o=>Ir(e,o).ok);return n.length===0?[]:n.length===1?[`\u2713 That completed a docs-campaign step \u2014 marked done: ${n[0]}`]:[`\u2713 That completed ${n.length} docs-campaign steps \u2014 marked done:`,...n.map(o=>` \u2022 ${o}`)]}catch{return[]}}function qa(e,t){let n=Ga(e,t);if(n.length===0)return null;let o=n.map(r=>`"${r}"`).join(", ");return[n.length===1?`Note: this command executes the open docs-roadmap step ${o}.`:`Note: this command executes ${n.length} open docs-roadmap steps: ${o}.`,`Close the loop IN THE SAME TURN the goal state is reached: call d360_roadmap_done with exactly the title of EACH step it completed (${o}), then tell the user they are marked done \u2014 including when you find the goal already holds ("no action needed" completes the STEP, not just the turn). Leave one open ONLY if its work is genuinely incomplete, and say exactly what remains.`].join(`
337
337
  `)}function wy(e,t){let n=t.trim().toLowerCase();if(!n)return null;let o=Ms(e).find(r=>r.status==="open"&&r.title.trim().toLowerCase()===n);return o?[`Execute this open docs-roadmap step: "${o.title}".`,o.run?`Its run command: ${o.run}`:"",o.body?`Step detail:
338
338
  ${o.body}`:"","",`Close the loop IN THE SAME TURN the step's goal state is reached: call d360_roadmap_done with exactly the title "${o.title}", then tell the user it is marked done. This INCLUDES discovering the goal already holds \u2014 "no action needed" completes the STEP, not just the turn, so stamp it done then too. Leave it open ONLY if the work is genuinely incomplete (blocked, or background results not yet applied) \u2014 then say exactly what remains before it can be closed.`].filter(Boolean).join(`
339
- `):null}async function by(e,t){let n=t.cwd;if(e[0]?.toLowerCase()==="done"){let o=Ir(n,e.slice(1).join(" ").trim());return console.log(o.ok?Q(o.line):V(o.line)),{kind:"continue"}}for(let o of od(n,{all:e.some(r=>r.toLowerCase()==="--all")}))console.log(o);return{kind:"continue"}}var sd={"local-ahead":"modified locally","untracked-local":"new (never published)",conflict:"\u26A0 conflict \u2014 pushing overwrites the portal edit"},JS="\u26A0 held on Document360 in another editor \u2014 /resolve settles it";function Uo(e){return e.filter(t=>t.path!==null&&t.status in sd&&!t.link).map(t=>({path:t.path,label:sd[t.status]}))}function _o(e,t=[]){let n=Uo(e),o=new Set(t),r=new Set([...e.filter(s=>s.status==="conflict"&&s.path).map(s=>s.path),...o]);return{clean:n.filter(s=>!r.has(s.path)),conflicts:n.filter(s=>r.has(s.path)).map(s=>o.has(s.path)&&s.label!==sd.conflict?{...s,label:JS}:s)}}function Mo(e,t){try{return WS(e,Ka(e,t).name).map(n=>n.path)}catch{return[]}}function Us(e,t=0){let n=e!==1;if(t>=e)return`\u26A0 ${e} article${n?"s":""} NOT pushed (nothing on the portal overwritten) \u2014 Document360 holds ${n?"their bodies":"its body"} in another editor, so ${n?"they cannot be":"it cannot be"} read back. Settle ${n?"them":"it"}: /resolve (or ask your Document360 admin to move ${n?"them":"it"} to the Markdown editor).`;let o=t>0?` (${t} of ${n?"them are":"these is"} held in another editor and cannot be read back)`:"";return`\u26A0 ${e} conflict${n?"s":""} NOT pushed (portal edit${n?"s":""} kept safe) \u2014 ${n?"these were":"it was"} edited both here and on the portal, so ${n?"they are":"it is"} left untouched.${o} Settle ${n?"them":"it"}: /resolve (or /push <path> to keep yours, /pull <path> to take the portal's).`}function id(e,t){let n=t.scope?.replace(/\\/g,"/").replace(/\/+$/,"");return!n||!n.endsWith(".md")||!xy(e,t.profileName).map(r=>r.replace(/\\/g,"/")).some(r=>r===n||r.endsWith(`/${n}`)||n.endsWith(`/${r}`))?null:{cmd:Se("/publish","",n),reason:"it takes this draft live \u2014 until it does, your readers still see the previous version"}}function Lr(e,t){let n=xy(e,t),o=n.length;if(o===0)return null;let r=o===1?"":"s",s=0;try{s=FS(e,n).length}catch{}if(s>0){let i=o-s,a=`${s===o?o===1?"It is":"All of them are":`${s} of them ${s===1?"is":"are"}`} waiting on a missing picture`;return`\u2139 ${o} article${r} ${o===1?"is":"are"} saved on Document360 as a draft and not live yet \u2014 readers still see the previous version. ${a}, so /publish holds ${s===1?"it":"them"} back${i>0?` and takes the other ${i} live once ${i===1?"it has":"they have"} been signed off.`:"."}`}return`\u2139 ${o} article${r} ${o===1?"is":"are"} saved on Document360 as a draft and not live yet \u2014 readers still see the previous version. /publish takes ${o===1?"it":"them"} live once ${o===1?"it has":"they have"} been signed off.`}function Ya(e){return e.filter(t=>t.link).length}function ad(e,t){try{let n=vy(e,Ka(e,t).name);if(!n)return null;let o=0;for(let r of Object.keys(n.articles))try{o+=QS(LS(OS(e,r),"utf8"),r,n).unresolved}catch{}return o===0?null:`\u2139 ${o} cross-link${o===1?"":"s"} point to pages that aren't on Document360 yet \u2014 they show as plain text for now and turn into links once those pages are pushed.`}catch{return null}}function Ey(e,t=[]){let n=[];e.length>1&&n.push({kind:"all",label:`Push all ${e.length} articles`,paths:e.map(r=>r.path),indent:!1});let o=new Map;for(let r of e){let s=r.path.replace(/\\/g,"/").split("/").slice(0,-1).join("/"),i=o.get(s);i?i.push(r):o.set(s,[r])}for(let r of[...o.keys()].sort()){let s=o.get(r).sort((i,a)=>i.path.localeCompare(a.path));n.push({kind:"category",label:`${qn(r)} (${s.length})`,paths:s.map(i=>i.path),indent:!1});for(let i of s){let a=i.path.replace(/\\/g,"/").split("/").pop()??i.path;n.push({kind:"article",label:a,paths:[i.path],indent:!0,note:i.label})}}for(let r of[...t].sort((s,i)=>s.path.localeCompare(i.path))){let s=r.path.replace(/\\/g,"/").split("/").pop()??r.path;n.push({kind:"article",label:s,paths:[r.path],indent:!1,note:r.label,conflict:!0})}return n}async function Fs(e,t){try{let n=Ka(e,t),o=n.project.projectId,r=n.project.workspaceId;if(!o||!r)return null;let s=await XS({profile:n.name,connection:n.connection,projectId:o,projectName:n.project.projectName},{cwd:e,profile:n.name,projectPath:`/v3/projects/${o}`,workspaceId:r,verifyHidden:!1});if(s.kind!=="created")return null;if(s.hidden!=="confirmed"){let i=s.seeded.length===0?" Its internal articles were NOT created, because that would put docs-agent scaffolding in front of your readers.":"";return`\u26A0 Created the "${ky}" folder on Document360 for reviewer feedback, but could NOT confirm it is hidden from readers.${i} Open its category settings in the portal and turn on "Hide from readers", then run /catch-up.`}return`\u2139 Created a hidden "${ky}" folder on Document360 \u2014 it's where your reviewers leave feedback for d360-writer to action (confirmed hidden from readers; its README explains how). /catch-up picks up what they write.`}catch{return null}}var Lt="--skip-review";function Ws(e,t){return BS(e,t)}function za(e,t){try{return Ws(e,Sy(e,t)).length}catch{return 0}}function Ot(e,t,n="",o,r){let s=za(e,o),i=Xa(e,o),a=o?` ${o}`:"",l=o?"/push":"/push --all",c=ly(e,{just:r}),f=!!o&&o.endsWith(".md"),g=c&&f?` (${c.short} \u2014 ${c.cmd} once this page is delivered.)`:"";if(c&&!f){let T=s>0?` (${s} article${s===1?"":"s"}${o&&!o.endsWith(".md")?` in ${o}`:""} ${s===1?"carries":"carry"} text no editor has read, so ${s===1?"it cannot":"they cannot"} be sent up until /review${a} --blocked --fix runs \u2014 that can wait until the writing is done.)`:i>0?` (${i} article${i===1?"":"s"} break${i===1?"s":""} house style rules that block sending up \u2014 /lint${a} --blocked --fix clears that whenever you want.)`:"";return{text:`${n}${c.text}${T}`,cmd:c.cmd}}if(s===0&&i===0)return g?{...t,text:`${t.text}${g}`}:t;let y=o&&!o.endsWith(".md")?` in ${o}`:"";if(s>0){let T=s===1?"":"s",C=i>0?` ${i} also break house style rules \u2014 /lint${a} --blocked --fix clears those afterwards.`:"",P=`/review${a} --blocked --fix`;return{text:`${n}${s} article${T}${y} ${s===1?"carries":"carry"} text no editor has read, so pushing ${s===1?"it is":"them is"} blocked. ${P} reads ${s===1?"it":"just those"} and applies its fixes.${C} Then ${l}.${g}`,cmd:P}}return{text:`${n}${i} article${i===1?"":"s"}${y} break${i===1?"s":""} house style rules that block publishing. /lint${a} --blocked --fix corrects them in place \u2014 finding them costs nothing \u2014 then ${l}.${g}`,cmd:`/lint${a} --blocked --fix`}}function Xa(e,t){try{return Ry(e,Sy(e,t)).length}catch{return 0}}function Ny(e,t){return Ry(e,t)}function ld(e,t,n){try{let o=vy(e,Ka(e,n).name);if(!o)return 0;let r=i=>i.replace(/\\/g,"/"),s=new Set(Object.keys(o.articles??{}).map(r));return t.filter(i=>s.has(r(i))).length}catch{return 0}}function cd(e,t){let n=`/push${t.scope?` ${t.scope}`:" --all"}`,o=l=>`${l} article${l===1?"":"s"}`,r=[],s=e.pushed.length,i=t.elapsedMs!==void 0?` \xB7 ${tC(t.elapsedMs)}`:"",a=e.screenshotsUploaded>0?` \xB7 ${e.screenshotsUploaded} screenshot${e.screenshotsUploaded===1?"":"s"} uploaded`:"";if(s===0&&e.failed.length===0?r.push("Nothing was pushed \u2014 no articles were sent."):t.recorded<s?(r.push(`Pushed ${t.recorded}/${o(s)}${a}${i}.`),r.push(`${s-t.recorded} article${s-t.recorded===1?"":"s"} reported success but ${s-t.recorded===1?"is":"are"} not recorded in the category map \u2014 re-run ${n}.`)):(r.push(`Pushed ${o(s)}${e.created>0?` (${e.created} new)`:""}${a}${i}.`),r.push(`Saved on Document360 as draft${s===1?"":"s"} \u2014 readers don't see ${s===1?"it":"them"} until /publish takes ${s===1?"it":"them"} live.`)),e.stopped?.reason==="unavailable"&&r.push(`Stopped: ${e.stopped.streak} articles in a row could not reach Document360${e.notRun.length>0?`, so the remaining ${e.notRun.length} ${e.notRun.length===1?"was":"were"} not attempted`:""}.`),e.stopped?.reason==="identity"&&r.push(`Nothing was pushed. ${e.stopped.error}`),e.failed.length>0){let l=e.failed.filter(g=>g.refused),c=eC(e.failed,t.docsRoot),f=e.failed.length;if(c.length===1&&f>1)r.push(`${o(f)} could not be pushed \u2014 all with the same error: ${c[0].error}`),r.push(...$y(c[0].paths," \u2717 "));else{r.push(`${o(f)} could not be pushed:`);for(let g of c)g.paths.length===1?r.push(` \u2717 ${g.paths[0]} \u2014 ${g.error}`):(r.push(` \u2717 ${o(g.paths.length)} \u2014 ${g.error}`),r.push(...$y(g.paths," ")))}l.length<e.failed.length&&(e.stopped?r.push(`Nothing was lost \u2014 your articles are unchanged. Re-run ${n} when Document360 is back.`):r.push(`Re-run ${n} to retry the rest \u2014 articles already saved are sent again only if they changed.`))}if(e.notRun.length>0&&!e.stopped&&r.push(`\u26A0 ${o(e.notRun.length)} never started (the run was stopped): ${ZS(e.notRun)}`),e.missingShots.length>0){let l=e.missingShots.flatMap(c=>c.ids);r.push(`\u26A0 ${l.length} screenshot${l.length===1?"":"s"} in ${o(e.missingShots.length)} ${l.length===1?"has":"have"} not been taken, so the description was published instead: /screenshot capture`)}if(e.categoryLookupFailed.length>0){let l=e.categoryLookupFailed.length;r.push(`\u26A0 Could not check Document360 for existing categories, so ${l===1?"a new one was":`${l} new ones were`} created. There may now be two with the same name: /doctor lists them.`),r.push(` Reason: ${e.categoryLookupFailed[0].reason}`)}return t.conflictsSkipped&&t.conflictsSkipped>0&&r.push(Us(t.conflictsSkipped,t.unreadableSkipped??0)),r}var ZS=(e,t=5)=>`${e.slice(0,t).join(", ")}${e.length>t?`, +${e.length-t} more`:""}`,rd=5;function $y(e,t){let n=e.slice(0,rd).map(o=>`${t}${o}`);return e.length>rd&&n.push(`${" ".repeat(t.length)}\u2026 +${e.length-rd} more`),n}function eC(e,t){let n=t?`${t.replace(/\\/g,"/").replace(/\/+$/,"")}/`:null,o=s=>{let i=s.replace(/\\/g,"/");return n&&i.startsWith(n)?i.slice(n.length):i},r=new Map;for(let s of e){let i=r.get(s.error);i?i.push(o(s.path)):r.set(s.error,[o(s.path)])}return[...r.entries()].map(([s,i])=>({error:s,paths:i})).sort((s,i)=>i.paths.length-s.paths.length)}function tC(e){let t=Math.round(e/1e3);return t<60?`${t}s`:`${Math.floor(t/60)}m ${String(t%60).padStart(2,"0")}s`}async function Jt(e,t,n=0,o){let r=HS(e.cwd,t,{allowUnreviewed:o?.skipReview===!0,allowStyleErrors:o?.skipStyle===!0});if(!r.clear){for(let c of GS(r,t.length,{act:"push"}))console.log(V(c));return{kind:"continue"}}let s={aborted:!1,partitions:0,failed:0,notRun:0,producedNothing:!0},i={retryable:0,stopped:!1,paths:[]},a=async(c,f)=>{let g=Date.now();console.log(d(f?`Resolving cross-links in ${c.length} article${c.length===1?"":"s"} whose targets published this run (second pass)\u2026`:`Pushing ${c.length} article${c.length===1?"":"s"} to Document360 (${YS} at a time)\u2026`));let y=await qS({cwd:e.cwd,...e.profileName?{profileName:e.profileName}:{},paths:c,...o?.allowUnpullable===!0?{allowUnpullable:!0}:{},onEvent:G=>{G.type==="article_done"?console.log(Q(` \u2713 ${G.result.path}${G.result.action==="created"?" (new)":""}`)):G.type==="article_failed"&&console.log(_(` \u2717 ${G.failure.path}`))}});console.log("");let x=ld(e.cwd,c,e.profileName);f||(i={retryable:y.failed.filter(G=>!G.refused).length,stopped:!!y.stopped,paths:y.failed.map(G=>G.path)},s={aborted:y.aborted||!!y.stopped,partitions:MS(c).length,failed:y.failed.length,notRun:y.notRun.length,producedNothing:y.pushed.length===0,verifiedComplete:x>=c.length,leftBehind:n});let T=y.pushed.length>0;for(let G of cd(y,{recorded:x,elapsedMs:Date.now()-g,docsRoot:zS(e.cwd),...f?{}:{conflictsSkipped:n,unreadableSkipped:o?.unreadableSkipped??0},...o?.scope?{scope:o.scope}:{}}))console.log(T&&y.failed.length===0?Q(G):V(G));let C=y.aborted||!!y.stopped;if(!T)return C;let P=ad(e.cwd,e.profileName);P&&console.log(d(P));let S=Lr(e.cwd,e.profileName);if(S&&console.log(d(S)),C)return C;try{let G=Py(await Cy({cwd:e.cwd,profileName:e.profileName,onRateLimit:D=>console.log(d(` Document360 rate limit \u2014 waiting ${Math.ceil(D.waitMs/1e3)}s\u2026`))}));G&&console.log(G.startsWith("\u26A0")?V(G):d(G))}catch{}try{let G=Ay(await Ty({cwd:e.cwd,profileName:e.profileName}));G&&console.log(d(G))}catch{}let j=await Fs(e.cwd,e.profileName);return j&&console.log(d(j)),C};try{if(!await a(t,!1)){let f=await ud(e.cwd,e.profileName,t,i.paths);if(f.length>0&&await a(f,!0),Qt(s))for(let g of Ye(e.cwd,o?.scope?`/push ${o.scope}`:"/push --all"))console.log(Q(g))}}catch(c){console.log(_(`Push run failed: ${c.message}`))}if(console.log(""),i.retryable>0){let c=Se("/push",o?.scope?"":"--all",o?.scope);return console.log(d(i.stopped?`Next: ${c} \u2014 your articles are unchanged, so it picks up where the run left off.`:`Next: ${c} \u2014 retries the ${i.retryable} article${i.retryable===1?"":"s"} that could not be saved.`)),{kind:"continue",ownNext:!0}}let l=id(e.cwd,{scope:o?.scope,profileName:e.profileName});return l?(console.log(d(`Next: ${l.cmd} \u2014 ${l.reason}.`)),{kind:"continue",ownNext:!0}):{kind:"continue"}}async function Qa(e,t,n){let o=US(e,n);if(o.length===0)return[];let r=new Set(Uo((await Va({cwd:e,profileName:t})).entries).map(s=>s.path));return o.filter(s=>r.has(s))}async function ud(e,t,n,o=[]){try{let r=new Set(n),s=new Set(o),i=await Va({cwd:e,profileName:t});return Uo(i.entries).map(a=>a.path).filter(a=>r.has(a)&&!s.has(a))}catch{return[]}}async function nC(e,t,n){try{let o=await VS({cwd:e,...t?{profileName:t}:{},relPath:n.replace(/\\/g,"/"),preview:!0});console.log(""),console.log(Q(`${o.title}`)),console.log(d(`${o.path} \xB7 ${o.articleId?`updates article ${o.articleId}`:"would be created as a new article"}`)),o.shots.length>0&&console.log(d(`${o.shots.length} screenshot${o.shots.length===1?"":"s"} embedded: ${o.shots.map(r=>r.id).join(", ")}`)),o.missingShots.length>0&&console.log(V(`\u26A0 ${o.missingShots.length} screenshot${o.missingShots.length===1?"":"s"} not taken yet \u2014 the description publishes instead: ${o.missingShots.join(", ")}`)),console.log(d("\u2500".repeat(Math.min(80,Math.max(20,(process.stdout.columns??80)-1))))),console.log(o.body),console.log(d("\u2500".repeat(Math.min(80,Math.max(20,(process.stdout.columns??80)-1))))),console.log(d(`Nothing was sent. Run /push ${o.path} to save it as a draft.`))}catch(o){console.log(_(o.message))}return{kind:"continue"}}async function Ja(e,t){let n=Je(t?.cwd??process.cwd(),t?.profileName);if(!n.ok)return console.log(_(n.reason)),{kind:"continue"};let o=e.includes(Lt),r=e.includes(KS);if(e.includes("--preview")){let i=e.find(a=>!a.startsWith("--"));return i?await nC(t?.cwd??process.cwd(),t?.profileName,i):(console.log(_("Which article? Use /push --preview <path> \u2014 a preview is per article.")),{kind:"continue"})}let s=e[0];if(e.includes("--screenshots")||e.includes("--shots")){let i=t?.cwd??process.cwd(),a=e.find(x=>!x.startsWith("--")),l=He(i),c=_a(l,a);if(c.length===0){let x=l.filter(T=>T.state==="captured").length;return console.log(d(x>0?`${x} screenshot${x===1?" has":"s have"} been taken but the callouts aren't drawn on yet, so there is nothing to embed \u2014 /screenshot annotate finishes ${x===1?"it":"them"}.`:a?`No captured screenshots in ${a} \u2014 /push --screenshots covers the whole knowledge base.`:"No captured screenshots yet \u2014 /screenshot capture first.")),{kind:"continue"}}let f=Ma(l,a);f&&console.log(f);let g=Ua(l);g&&console.log(g);let y=Se("","--screenshots",a).trim();return await Jt(t??{cwd:process.cwd()},c,0,{skipReview:o,skipStyle:r,scope:y})}if(s==="--related"&&e[1]){if(!t)return{kind:"continue"};let i=await Qa(t.cwd,t.profileName,e[1].replace(/\\/g,"/"));return i.length===0?(console.log(Q("\u2713 All related articles are already on Document360.")),{kind:"continue"}):await Jt(t,i,0,{skipReview:o,skipStyle:r,scope:`--related ${e[1]}`})}if(s==="--all"){console.log(d("Checking what needs pushing\u2026"));try{let i=await Va({cwd:t?.cwd??process.cwd()}),a=new Set(Mo(t?.cwd??process.cwd(),t?.profileName)),{clean:l,conflicts:c}=_o(i.entries,a),f=c.filter(C=>a.has(C.path)).length,g=Ya(i.entries),y=e.includes("--force"),x=y&&c.length>0;g>0&&console.log(V(`\u26A0 ${g} article(s) already exist on Document360 but aren't linked \u2014 left out (pushing would duplicate them). Run /catch-up or ask me to link them first.`)),c.length>0&&!x&&console.log(V(Us(c.length,f)));let T=x?[...l,...c]:l;if(T.length===0){if(c.length>0){let P=Lr(t?.cwd??process.cwd(),t?.profileName);return P&&console.log(d(P)),console.log(d("Resolve the conflicts: /resolve")),{kind:"continue"}}console.log(Q(g>0?"\u2713 Nothing else is ahead of Document360 to push.":"\u2713 Nothing is ahead of Document360 \u2014 nothing to push."));let C=Lr(t?.cwd??process.cwd(),t?.profileName);if(C&&console.log(d(C)),t)try{let P=Py(await Cy({cwd:t.cwd,profileName:t.profileName,onRateLimit:j=>console.log(d(` Document360 rate limit \u2014 waiting ${Math.ceil(j.waitMs/1e3)}s\u2026`))}));P&&console.log(P.startsWith("\u26A0")?V(P):d(P));let S=Ay(await Ty({cwd:t.cwd,profileName:t.profileName}));S&&console.log(d(S))}catch{}return{kind:"continue"}}return await Jt(t??{cwd:process.cwd()},T.map(C=>C.path),x?0:c.length,{skipReview:o,skipStyle:r,allowUnpullable:y,unreadableSkipped:x?0:f})}catch(i){return console.log(_(`Could not compute sync status: ${i.message}`)),{kind:"continue"}}}if(s&&s.startsWith("--")&&(s=e.find(i=>!i.startsWith("--"))??""),!s){console.log(d("Checking what needs pushing\u2026"));let i,a=0,l=0;try{let c=await Va({cwd:t?.cwd??process.cwd()}),f=new Set(Mo(t?.cwd??process.cwd(),t?.profileName)),{clean:g,conflicts:y}=_o(c.entries,f);l=y.filter(T=>f.has(T.path)).length,i=g,a=y.length;let x=Ya(c.entries);if(x>0&&console.log(V(`\u26A0 ${x} article(s) already exist on Document360 but aren't linked \u2014 left out (pushing would duplicate them). Run /catch-up or ask me to link them first.`)),y.length>0&&console.log(V(Us(y.length,l))),i.length===0&&y.length>0)return console.log(d("Resolve the conflicts: /resolve")),{kind:"continue"}}catch(c){return console.log(_(`Could not compute sync status: ${c.message}`)),console.log(d("Push a specific article: /push <article-path>")),{kind:"continue"}}if(i.length===0)return console.log(Q("\u2713 Nothing is ahead of Document360 \u2014 nothing to push.")),console.log(d(" (Articles without a sync base are unverified \u2014 push those by path if needed.)")),{kind:"continue"};if(!process.stdin.isTTY||!t){for(let c of i)console.log(` ${c.path} ${d(`(${c.label})`)}`);return console.log(d("Run: /push <article-path>")),{kind:"continue"}}try{s=await t.withPausedInput(()=>_S({message:"Push which article?",choices:[...i.length>1?[{name:`All ${i.length} candidates`,value:"--all",description:"one agent run over every candidate"}]:[],...i.map(c=>({name:c.path,value:c.path,description:c.label}))]}))}catch{return console.log(d("Cancelled.")),{kind:"continue"}}if(s==="--all")return await Jt(t,i.map(c=>c.path),a,{skipReview:o,skipStyle:r,unreadableSkipped:l})}return await Jt(t??{cwd:process.cwd()},[s],0,{skipReview:o,skipStyle:r,scope:s})}ie();import{select as iC}from"@inquirer/prompts";import{computeSyncStatus as aC,localAheadPaths as lC,pendingDrafts as cC,publishArticleLive as uC,publishGateVerdict as dC,publishGateLines as pC,blockedPaths as hC,placeholderRemedy as fC,resolveActiveProfile as eM,SKIP_STYLE_FLAG as mC,SKIP_SHOTS_FLAG as gC}from"document360-engine";import{gateFixTarget as oC}from"document360-engine";var pd=e=>"ruleId"in e,Za=e=>pd(e)?e.severity:"warning",zn=e=>e===1?"":"s";function Bs(e,t){let n=t??oC(e),o=n?` ${n}`:"";return e.unreviewed.length>0?`/review${o} --blocked --fix`:`/lint${o} --blocked --fix`}var dd=new Map;var rC=e=>pd(e)?e.fp||`${e.ruleId}:${e.path}:${e.line}:${e.term}`:`${e.kind}:${e.path}:${e.line}`;function Hs(e){let{cwd:t,after:n,scope:o,aborted:r,blocked:s}=e,i=n.filter(x=>Za(x)==="error").length,a=n.filter(x=>Za(x)==="warning").length,l=n.filter(x=>Za(x)==="suggestion").length,c=Se("/lint","--blocked --fix",o);if(r){let x=Se("/lint",s?"--blocked --fix":"--fix",o);return{text:`Stopped. Nothing else changed \u2014 ${x} picks up where this left off.`,cmd:x}}if(i>0){let x=n.filter(j=>Za(j)==="error"),T=new Set(x.map(j=>j.path)).size,C=`${t}::${o??""}`,P=x.map(rC).sort().join("|"),S=dd.get(C)===P;if(dd.set(C,P),S){let j=Se("/lint","--blocked --full",o),G=[...new Set(x.filter(pd).map(D=>D.ruleId))];return{text:`The same ${i} error${zn(i)} in ${T} article${zn(T)} survived two ${c} passes, so a third would change nothing \u2014 ${i===1?"it needs":"they need"} a person. ${j} shows ${i===1?"it":"them"} in place. If ${G.length===1?`${G[0]} is`:"a rule is"} wrong for your product, switch it off in .d360-writer/config.json under "styleGuide": { "rules": { "<rule>": "off" } }.`,cmd:j}}return{text:`${i} error${zn(i)} in ${T} article${zn(T)} still block${i===1?"s":""} publishing. ${c} has another go at ${i===1?"it":"just those"} \u2014 if a second pass leaves the same ones, they need a human eye.`,cmd:c}}dd.delete(`${t}::${o??""}`);let f=a>0&&l>0?`${a} warning${zn(a)} and ${l} suggestion${zn(l)} remain (neither blocks publishing). `:a>0?`${a} warning${zn(a)} remain${a===1?"s":""}, which ${a===1?"does":"do"} not block publishing. `:l>0?`${l} suggestion${zn(l)} remain${l===1?"s":""} \u2014 ${l===1?"it is":"they are"} judgment calls and ${l===1?"does":"do"} not block publishing. `:"",g=f===""?"\u2713 Nothing left to fix. ":`\u2713 ${f}`,y=o?"/push":"/push --all";return Ot(t,{text:`${g}Nothing is blocking${o?` in ${o}`:""} \u2014 ${y} saves the articles to Document360 as drafts.`,cmd:y},g,o)}var sC={check:"/check is now /lint (same command)."};function el(){return"That saves drafts rather than taking anything live, so running /push. (/publish takes saved drafts live.)"}var Dy=new Set;function Gs(e){let t=sC[e.replace(/^\//,"")];return!t||Dy.has(e)?null:(Dy.add(e),t)}var yC=150,wC=e=>e>0?new Promise(t=>setTimeout(t,e)):Promise.resolve(),fd=e=>e===1?"":"s",hd=e=>e.replace(/\\/g,"/").split("/").pop()??e;function md(e){return e.includes("--screenshots")||e.includes("--shots")||e[0]==="--related"}async function gd(e,t,n){await aC({cwd:e,profileName:t});let o=n?n.replace(/\\/g,"/").replace(/\/+$/,""):null;return cC(e,t).filter(r=>!o||r.path===o||r.path.startsWith(`${o}/`)).map(r=>({...r,label:bC(e,r.path)}))}function bC(e,t){let n=Pa(e,t)??Is(t),o=Ca(t),r=o?qn(o):"";return r?`${r} \u203A ${n}`:n}function kC(e){let t=Math.min(58,Math.max(...e.map(n=>n.label.length),20));return e.map(n=>` ${n.label.padEnd(t)} ${n.version?`draft v${n.version} \u2192 live`:"draft \u2192 live"}`)}function yd(e,t,n){let r=[`\u2713 Nothing is waiting to go live${n?` in ${n}`:""} \u2014 readers are seeing the latest version of every article.`],s=n?n.replace(/\\/g,"/").replace(/\/+$/,""):null,i=lC(e,t).filter(a=>!s||a===s||a.startsWith(`${s}/`)).length;return i>0&&r.push(`${i} article${fd(i)} ${i===1?"has":"have"} local edits that aren't on Document360 yet \u2014 that's /push${n?` ${n}`:""}.`),{lines:r,ahead:i}}function tl(e,t,n){let o=null;try{o=fC(e,t.placeholders)?.cmd??null}catch{}return{cmd:t.unreviewed.length>0||t.styleErrors.length>0?Bs(t,n):o,shotFix:o}}async function wd(e,t,n,o={}){let r={published:[],blocked:[],failed:[]},s=0;for(let i of n){o.onProgress?.(s,n.length,i.label);let a=await uC({articleId:i.articleId,cwd:e,profileName:t,allowUnreviewed:o.skipReview===!0,allowStyleErrors:o.skipStyle===!0,allowProduction:o.allowProduction===!0});a.ok?r.published.push({path:i.path,...a.url?{url:a.url}:{}}):a.blocked?r.blocked.push({path:i.path,message:a.message}):r.failed.push({path:i.path,message:a.error.message}),s+=1,s<n.length&&await wC(yC)}return r}function bd(e){let{published:t,blocked:n,failed:o}=e,r=[];if(t.length>0){r.push(`\u2713 ${t.length} article${fd(t.length)} ${t.length===1?"is":"are"} now live \u2014 readers see ${t.length===1?"it":"them"} now.`);let s=t.filter(i=>i.url);for(let i of s.slice(0,3))r.push(` \u2B95 ${i.url}`);s.length>3&&r.push(` \u2B95 \u2026and ${s.length-3} more`)}if(n.length>0){r.push(`\u2717 ${n.length} refused by the publish gates \u2014 still ${n.length===1?"a draft":"drafts"}, nothing was lost:`);for(let s of n.slice(0,3))r.push(` \xB7 ${hd(s.path)}`);n.length>3&&r.push(` \xB7 \u2026and ${n.length-3} more`),r.push(" /status names what is blocking each one, and the command that clears it. It costs nothing.")}if(o.length>0){r.push(`\u2717 ${o.length} failed \u2014 ${o.length===1?"it is":"they are"} still a draft, so re-running /publish retries ${o.length===1?"it":"them"}:`);for(let s of o.slice(0,3))r.push(` \xB7 ${hd(s.path)} \u2014 ${s.message}`);o.length>3&&r.push(` \xB7 \u2026and ${o.length-3} more`)}return r.length===0&&r.push("Nothing was published."),r}async function jy(e,t){let n=t?.cwd??process.cwd();if(md(e))return console.log(d(`\u2192 ${el()}`)),Ja(e,t);let o=Je(n,t?.profileName);if(!o.ok)return console.log(_(o.reason)),{kind:"continue"};let r=e.includes(Lt),s=e.includes(mC),i=e.includes(gC),a=e.includes("--all"),l=e.find(P=>!P.startsWith("--"));console.log(d("Checking what is waiting to go live\u2026"));let c;try{c=await gd(n,t?.profileName,l)}catch(P){return console.log(_(`Could not check Document360: ${P.message}`)),{kind:"continue"}}if(c.length===0){for(let P of yd(n,t?.profileName,l).lines)console.log(P.startsWith("\u2713")?Q(P):d(P));return{kind:"continue"}}let f=dC(n,c.map(P=>P.path),{allowUnreviewed:r,allowStyleErrors:s,checkScreenshots:!0,allowPlaceholders:i}),g=new Set(hC(f)),y=c.filter(P=>!g.has(P.path)),x=tl(n,f,l);if(g.size>0)for(let P of pC(f,c.length,{scope:l,act:"publish",going:y.length,shotFix:x.shotFix}))console.log(V(P));if(y.length===0)return x.cmd&&console.log(d(`Run: ${x.cmd}`)),{kind:"continue"};c=y,console.log(""),console.log(`${c.length} draft${fd(c.length)} ${c.length===1?"is":"are"} ahead of what readers see:`);for(let P of kC(c.slice(0,12)))console.log(d(P));c.length>12&&console.log(d(` \u2026and ${c.length-12} more`)),console.log("");let T=c;if(!a){let P=await iC({message:"Publish live to your readers?",choices:[{name:`Yes \u2014 publish all ${c.length} live`,value:"all"},...c.slice(0,8).map(S=>({name:`Just ${S.label}`,value:S.path})),{name:"No \u2014 leave them as drafts",value:"none"}]});if(P==="none")return console.log(d("Left as drafts. Nothing changed.")),{kind:"continue"};P!=="all"&&(T=c.filter(S=>S.path===P))}let C=await wd(n,t?.profileName,T,{skipReview:r,skipStyle:s,allowProduction:t?.allowProdWrites?.()===!0,onProgress:(P,S,j)=>console.log(d(` \u25B8 ${P+1}/${S} ${hd(j)}\u2026`))});console.log("");for(let P of bd(C))console.log(P.startsWith("\u2713")?Q(P):P.startsWith("\u2717")?V(P):d(P));return{kind:"continue"}}import{loadKnownConflicts as $C,localArticleTitle as vC,resolveActiveProfile as xC,surfaceCoverageSafe as Iy,readProjectConfig as Ly,recordDeclinedSurface as RC,areaExists as SC,declineTargets as Oy}from"document360-engine";function CC(e,t){let n=vC(e,t);return n||(t.split(/[\\/]/).pop()??t).replace(/\.md$/,"").replace(/^\d+[-_]/,"").replace(/[-_]+/g," ")}function nl(e,t){let n=[];try{n=$C(e,xC(e,t).name)}catch{return null}if(n.length===0)return null;let o=n.length,r=n.slice(0,3).map(i=>CC(e,i)),s=o>3?`${r.join(", ")} and ${o-3} more`:r.join(", ");return{paths:n,note:`\u26A0 ${o} article${o===1?"":"s"} ${o===1?"has":"have"} been changed both here and on Document360 (${s}), so your local copy is not a reliable picture of what your readers have. An audit over it could tell you to write an article that already exists, or miss one that needs your decision \u2014 so it needs ${o===1?"that":"those"} settled first.`}}var Or={cmd:"/resolve",reason:"it walks each one with you \u2014 keep yours, take the portal's, or skip"};function ol(e,t){let n=t.indexOf("--decline");if(n<0)return null;let o=t[n+1];if(!o||o.startsWith("--")){let a=Iy(e,Ly(e)),l=a.recognised?a.areas.slice(0,5).flatMap(c=>Oy(c).map(f=>` /audit --decline ${f}`)):[];return["Name the path to decline \u2014 a folder, or a single file.",...l.length?["","No article covers these right now. Run one of these to settle it for good:",...l]:["","For example:"," /audit --decline src/internal-admin"]]}let r=o.replace(/\\/g,"/").replace(/\/+$/,"");if(!SC(e,r))return[`There is nothing at ${r}, so there is nothing to decline. Copy the path from /eval or /audit.`];let s=t.slice(n+2).filter(a=>!a.startsWith("--")).join(" ")||void 0;return RC(e,r,s)?[`\u2713 ${r} will not be reported as needing an article. /audit and /eval leave it out from now on, and say how many you have declined.`]:[`Could not write .d360-writer/declined-surfaces.json, so ${r} was not declined.`]}async function qs(e){if(e?.cwd){let r=nl(e.cwd,e.profileName);if(r)return{kind:"audit-blocked",note:r.note,lead:Or}}let t=e?.cwd?Iy(e.cwd,Ly(e.cwd)):null,n=[];if(t?.recognised&&t.areas.length>0){let r=t.areas.slice(0,15);n.push("","2.7. These parts of the product are cited by NO article's `sources:` frontmatter. This list is DETERMINISTIC and complete as of now \u2014 it does not depend on the diff window above, so treat it as authoritative rather than re-deriving it:",...r.map(s=>` - ${s.folder} (${s.files} of ${s.total} file${s.total===1?"":"s"} uncited, e.g. ${s.sample.join(", ")}) \u2014 to decline: ${Oy(s).map(i=>`/audit --decline ${i}`).join(" \xB7 ")}`),...t.areas.length>r.length?[` \u2026and ${t.areas.length-r.length} more areas.`]:[]," EVERY row above must leave this run accounted for: a `create` row, or a stated reason it needs no article (internal-only, unreachable by a reader, already covered by an existing article's prose). Do NOT silently drop one \u2014 if you list fewer paths than this section handed you, say which you left out and why."," A `decline` verdict is not a record. For EVERY row you decline, print its exact /audit --decline line (copied from above \u2014 it already picks folder-or-file correctly) in the closing block, each on its own line so the terminal makes it a button. A judgement that leaves the turn as prose changes nothing and will be re-derived, at this price, on every future audit.",...t.declined>0?[` (${t.declined} more ${t.declined===1?"is":"are"} already declined and left out of that list.)`]:[])}return{kind:"forward-to-agent",prompt:["Run the gap-analysis skill against this repo.","","Follow its stages in order:","1. d360_sync_status first; stop if any article is changed in both places, or changed on Document360 and not yet pulled down (the local copy cannot be trusted until that is resolved). Say which articles, in plain words, never the internal status word \u2014 and tell the user to run /resolve, which walks the affected articles one at a time (keep the local copy, take the portal's, or skip). Do NOT ask them to report back to you: they will run /resolve and then /audit again.","2. git diff since the lastAnalyzedCommit marker in d360-category-map.json (or last 30 days if no marker), filtered to the authoritativeSourceFiles paths.","2.5. d360_pending_hints: the source agent's own notes on what it shipped. Verify each against the CURRENT code (its `sources` exist, and the code as it stands shows the surface it claims) before trusting it; never judge it by its `branch` or `commit`. Fold verified ones into the table, flag the rest.","2.6. Anything d360_pending_hints returns under `held` is NOT a proposal. Its cited code has uncommitted changes, so the builder is still writing that surface and has already been asked whether it is done. List each held item at the END of the table as a `waiting` row (Reason: the code behind it is not finished), do NOT propose create/update/retire for it, and do NOT stamp it consumed.",...n,"3. Map changes (diff + hints) to articles via their `sources:` frontmatter; read only implicated articles and changed source files. If articles are missing `sources:`, this is the one-time bootstrap pass, so backfill them.","4. Output the proposal table: | Article (path or proposed title) | Action (create | update | retire | link) | Reason | Evidence | Scope |. Leave every hint OPEN \u2014 this run proposes, it does not write articles, so nothing has been acted on yet.","5. End with the skill's closing block, and put NOTHING below it \u2014 no receipts, no roadmap confirmations, no feedback marker line, no summary. It ends in a question, and a question the user has to scroll back to find is one you did not really ask.","","Do not start writing or updating any article yet; the proposal table is the deliverable.","The audited-up-to marker is recorded for you when this command finishes. Read it to size the diff window; never write it, and never report it as a missing capability."].join(`
339
+ `):null}async function by(e,t){let n=t.cwd;if(e[0]?.toLowerCase()==="done"){let o=Ir(n,e.slice(1).join(" ").trim());return console.log(o.ok?Q(o.line):V(o.line)),{kind:"continue"}}for(let o of od(n,{all:e.some(r=>r.toLowerCase()==="--all")}))console.log(o);return{kind:"continue"}}var sd={"local-ahead":"modified locally","untracked-local":"new (never published)",conflict:"\u26A0 conflict \u2014 pushing overwrites the portal edit"},JS="\u26A0 held on Document360 in another editor \u2014 /resolve settles it";function Uo(e){return e.filter(t=>t.path!==null&&t.status in sd&&!t.link).map(t=>({path:t.path,label:sd[t.status]}))}function _o(e,t=[]){let n=Uo(e),o=new Set(t),r=new Set([...e.filter(s=>s.status==="conflict"&&s.path).map(s=>s.path),...o]);return{clean:n.filter(s=>!r.has(s.path)),conflicts:n.filter(s=>r.has(s.path)).map(s=>o.has(s.path)&&s.label!==sd.conflict?{...s,label:JS}:s)}}function Mo(e,t){try{return WS(e,Ka(e,t).name).map(n=>n.path)}catch{return[]}}function Us(e,t=0){let n=e!==1;if(t>=e)return`\u26A0 ${e} article${n?"s":""} NOT pushed (nothing on the portal overwritten) \u2014 Document360 holds ${n?"their bodies":"its body"} in another editor, so ${n?"they cannot be":"it cannot be"} read back. Settle ${n?"them":"it"}: /resolve (or ask your Document360 admin to move ${n?"them":"it"} to the Markdown editor).`;let o=t>0?` (${t} of ${n?"them are":"these is"} held in another editor and cannot be read back)`:"";return`\u26A0 ${e} conflict${n?"s":""} NOT pushed (portal edit${n?"s":""} kept safe) \u2014 ${n?"these were":"it was"} edited both here and on the portal, so ${n?"they are":"it is"} left untouched.${o} Settle ${n?"them":"it"}: /resolve (or /push <path> to keep yours, /pull <path> to take the portal's).`}function id(e,t){let n=t.scope?.replace(/\\/g,"/").replace(/\/+$/,"");return!n||!n.endsWith(".md")||!xy(e,t.profileName).map(r=>r.replace(/\\/g,"/")).some(r=>r===n||r.endsWith(`/${n}`)||n.endsWith(`/${r}`))?null:{cmd:Se("/publish","",n),reason:"it takes this draft live \u2014 until it does, your readers still see the previous version"}}function Lr(e,t){let n=xy(e,t),o=n.length;if(o===0)return null;let r=o===1?"":"s",s=0;try{s=FS(e,n).length}catch{}if(s>0){let i=o-s,a=`${s===o?o===1?"It is":"All of them are":`${s} of them ${s===1?"is":"are"}`} waiting on a missing picture`;return`\u2139 ${o} article${r} ${o===1?"is":"are"} saved on Document360 as a draft and not live yet \u2014 readers still see the previous version. ${a}, so /publish holds ${s===1?"it":"them"} back${i>0?` and takes the other ${i} live once ${i===1?"it has":"they have"} been signed off.`:"."}`}return`\u2139 ${o} article${r} ${o===1?"is":"are"} saved on Document360 as a draft and not live yet \u2014 readers still see the previous version. /publish takes ${o===1?"it":"them"} live once ${o===1?"it has":"they have"} been signed off.`}function Ya(e){return e.filter(t=>t.link).length}function ad(e,t){try{let n=vy(e,Ka(e,t).name);if(!n)return null;let o=0;for(let r of Object.keys(n.articles))try{o+=QS(LS(OS(e,r),"utf8"),r,n).unresolved}catch{}return o===0?null:`\u2139 ${o} cross-link${o===1?"":"s"} point to pages that aren't on Document360 yet \u2014 they show as plain text for now and turn into links once those pages are pushed.`}catch{return null}}function Ey(e,t=[]){let n=[];e.length>1&&n.push({kind:"all",label:`Push all ${e.length} articles`,paths:e.map(r=>r.path),indent:!1});let o=new Map;for(let r of e){let s=r.path.replace(/\\/g,"/").split("/").slice(0,-1).join("/"),i=o.get(s);i?i.push(r):o.set(s,[r])}for(let r of[...o.keys()].sort()){let s=o.get(r).sort((i,a)=>i.path.localeCompare(a.path));n.push({kind:"category",label:`${qn(r)} (${s.length})`,paths:s.map(i=>i.path),indent:!1});for(let i of s){let a=i.path.replace(/\\/g,"/").split("/").pop()??i.path;n.push({kind:"article",label:a,paths:[i.path],indent:!0,note:i.label})}}for(let r of[...t].sort((s,i)=>s.path.localeCompare(i.path))){let s=r.path.replace(/\\/g,"/").split("/").pop()??r.path;n.push({kind:"article",label:s,paths:[r.path],indent:!1,note:r.label,conflict:!0})}return n}async function Fs(e,t){try{let n=Ka(e,t),o=n.project.projectId,r=n.project.workspaceId;if(!o||!r)return null;let s=await XS({profile:n.name,connection:n.connection,projectId:o,projectName:n.project.projectName},{cwd:e,profile:n.name,projectPath:`/v3/projects/${o}`,workspaceId:r,verifyHidden:!1});if(s.kind!=="created")return null;if(s.hidden!=="confirmed"){let i=s.seeded.length===0?" Its internal articles were NOT created, because that would put docs-agent scaffolding in front of your readers.":"";return`\u26A0 Created the "${ky}" folder on Document360 for reviewer feedback, but could NOT confirm it is hidden from readers.${i} Open its category settings in the portal and turn on "Hide from readers", then run /catch-up.`}return`\u2139 Created a hidden "${ky}" folder on Document360 \u2014 it's where your reviewers leave feedback for d360-writer to action (confirmed hidden from readers; its README explains how). /catch-up picks up what they write.`}catch{return null}}var Lt="--skip-review";function Ws(e,t){return BS(e,t)}function za(e,t){try{return Ws(e,Sy(e,t)).length}catch{return 0}}function Ot(e,t,n="",o,r){let s=za(e,o),i=Xa(e,o),a=o?` ${o}`:"",l=o?"/push":"/push --all",c=ly(e,{just:r}),f=!!o&&o.endsWith(".md"),g=c&&f?` (${c.short} \u2014 ${c.cmd} once this page is delivered.)`:"";if(c&&!f){let T=s>0?` (${s} article${s===1?"":"s"}${o&&!o.endsWith(".md")?` in ${o}`:""} ${s===1?"carries":"carry"} text no editor has read, so ${s===1?"it cannot":"they cannot"} be sent up until /review${a} --blocked --fix runs \u2014 that can wait until the writing is done.)`:i>0?` (${i} article${i===1?"":"s"} break${i===1?"s":""} house style rules that block sending up \u2014 /lint${a} --blocked --fix clears that whenever you want.)`:"";return{text:`${n}${c.text}${T}`,cmd:c.cmd}}if(s===0&&i===0)return g?{...t,text:`${t.text}${g}`}:t;let y=o&&!o.endsWith(".md")?` in ${o}`:"";if(s>0){let T=s===1?"":"s",C=i>0?` ${i} also break house style rules \u2014 /lint${a} --blocked --fix clears those afterwards.`:"",P=`/review${a} --blocked --fix`;return{text:`${n}${s} article${T}${y} ${s===1?"carries":"carry"} text no editor has read, so pushing ${s===1?"it is":"them is"} blocked. ${P} reads ${s===1?"it":"just those"} and applies its fixes.${C} Then ${l}.${g}`,cmd:P}}return{text:`${n}${i} article${i===1?"":"s"}${y} break${i===1?"s":""} house style rules that block publishing. /lint${a} --blocked --fix corrects them in place \u2014 finding them costs nothing \u2014 then ${l}.${g}`,cmd:`/lint${a} --blocked --fix`}}function Xa(e,t){try{return Ry(e,Sy(e,t)).length}catch{return 0}}function Ny(e,t){return Ry(e,t)}function ld(e,t,n){try{let o=vy(e,Ka(e,n).name);if(!o)return 0;let r=i=>i.replace(/\\/g,"/"),s=new Set(Object.keys(o.articles??{}).map(r));return t.filter(i=>s.has(r(i))).length}catch{return 0}}function cd(e,t){let n=`/push${t.scope?` ${t.scope}`:" --all"}`,o=l=>`${l} article${l===1?"":"s"}`,r=[],s=e.pushed.length,i=t.elapsedMs!==void 0?` \xB7 ${tC(t.elapsedMs)}`:"",a=e.screenshotsUploaded>0?` \xB7 ${e.screenshotsUploaded} screenshot${e.screenshotsUploaded===1?"":"s"} uploaded`:"";s===0&&e.failed.length===0?r.push("Nothing was pushed \u2014 no articles were sent."):t.recorded<s?(r.push(`Pushed ${t.recorded}/${o(s)}${a}${i}.`),r.push(`${s-t.recorded} article${s-t.recorded===1?"":"s"} reported success but ${s-t.recorded===1?"is":"are"} not recorded in the category map \u2014 re-run ${n}.`)):(r.push(`Pushed ${o(s)}${e.created>0?` (${e.created} new)`:""}${a}${i}.`),r.push(`Saved on Document360 as draft${s===1?"":"s"} \u2014 readers don't see ${s===1?"it":"them"} until /publish takes ${s===1?"it":"them"} live.`)),e.stopped?.reason==="unavailable"&&r.push(`Stopped: ${e.stopped.streak} articles in a row could not reach Document360${e.notRun.length>0?`, so the remaining ${e.notRun.length} ${e.notRun.length===1?"was":"were"} not attempted`:""}.`);for(let l of e.duplicateCategories??[])r.push(`\u26A0 Document360 now holds ${l.ids.length} categories called "${l.name}" \u2014 your articles are split between them.`);if((e.duplicateCategories??[]).length>0&&r.push(" Nothing here deletes a category. Remove the extra ones in the portal, then run /push --all again."),e.stopped?.reason==="identity"&&r.push(`Nothing was pushed. ${e.stopped.error}`),e.failed.length>0){let l=e.failed.filter(g=>g.refused),c=eC(e.failed,t.docsRoot),f=e.failed.length;if(c.length===1&&f>1)r.push(`${o(f)} could not be pushed \u2014 all with the same error: ${c[0].error}`),r.push(...$y(c[0].paths," \u2717 "));else{r.push(`${o(f)} could not be pushed:`);for(let g of c)g.paths.length===1?r.push(` \u2717 ${g.paths[0]} \u2014 ${g.error}`):(r.push(` \u2717 ${o(g.paths.length)} \u2014 ${g.error}`),r.push(...$y(g.paths," ")))}l.length<e.failed.length&&(e.stopped?r.push(`Nothing was lost \u2014 your articles are unchanged. Re-run ${n} when Document360 is back.`):r.push(`Re-run ${n} to retry the rest \u2014 articles already saved are sent again only if they changed.`))}if(e.notRun.length>0&&!e.stopped&&r.push(`\u26A0 ${o(e.notRun.length)} never started (the run was stopped): ${ZS(e.notRun)}`),e.missingShots.length>0){let l=e.missingShots.flatMap(c=>c.ids);r.push(`\u26A0 ${l.length} screenshot${l.length===1?"":"s"} in ${o(e.missingShots.length)} ${l.length===1?"has":"have"} not been taken, so the description was published instead: /screenshot capture`)}if(e.categoryLookupFailed.length>0){let l=e.categoryLookupFailed.length;r.push(`\u26A0 Could not check Document360 for existing categories, so ${l===1?"a new one was":`${l} new ones were`} created. There may now be two with the same name: /doctor lists them.`),r.push(` Reason: ${e.categoryLookupFailed[0].reason}`)}return t.conflictsSkipped&&t.conflictsSkipped>0&&r.push(Us(t.conflictsSkipped,t.unreadableSkipped??0)),r}var ZS=(e,t=5)=>`${e.slice(0,t).join(", ")}${e.length>t?`, +${e.length-t} more`:""}`,rd=5;function $y(e,t){let n=e.slice(0,rd).map(o=>`${t}${o}`);return e.length>rd&&n.push(`${" ".repeat(t.length)}\u2026 +${e.length-rd} more`),n}function eC(e,t){let n=t?`${t.replace(/\\/g,"/").replace(/\/+$/,"")}/`:null,o=s=>{let i=s.replace(/\\/g,"/");return n&&i.startsWith(n)?i.slice(n.length):i},r=new Map;for(let s of e){let i=r.get(s.error);i?i.push(o(s.path)):r.set(s.error,[o(s.path)])}return[...r.entries()].map(([s,i])=>({error:s,paths:i})).sort((s,i)=>i.paths.length-s.paths.length)}function tC(e){let t=Math.round(e/1e3);return t<60?`${t}s`:`${Math.floor(t/60)}m ${String(t%60).padStart(2,"0")}s`}async function Jt(e,t,n=0,o){let r=HS(e.cwd,t,{allowUnreviewed:o?.skipReview===!0,allowStyleErrors:o?.skipStyle===!0});if(!r.clear){for(let c of GS(r,t.length,{act:"push"}))console.log(V(c));return{kind:"continue"}}let s={aborted:!1,partitions:0,failed:0,notRun:0,producedNothing:!0},i={retryable:0,stopped:!1,paths:[]},a=async(c,f)=>{let g=Date.now();console.log(d(f?`Resolving cross-links in ${c.length} article${c.length===1?"":"s"} whose targets published this run (second pass)\u2026`:`Pushing ${c.length} article${c.length===1?"":"s"} to Document360 (${YS} at a time)\u2026`));let y=await qS({cwd:e.cwd,...e.profileName?{profileName:e.profileName}:{},paths:c,...o?.allowUnpullable===!0?{allowUnpullable:!0}:{},onEvent:G=>{G.type==="article_done"?console.log(Q(` \u2713 ${G.result.path}${G.result.action==="created"?" (new)":""}`)):G.type==="article_failed"&&console.log(_(` \u2717 ${G.failure.path}`))}});console.log("");let x=ld(e.cwd,c,e.profileName);f||(i={retryable:y.failed.filter(G=>!G.refused).length,stopped:!!y.stopped,paths:y.failed.map(G=>G.path)},s={aborted:y.aborted||!!y.stopped,partitions:MS(c).length,failed:y.failed.length,notRun:y.notRun.length,producedNothing:y.pushed.length===0,verifiedComplete:x>=c.length,leftBehind:n});let T=y.pushed.length>0;for(let G of cd(y,{recorded:x,elapsedMs:Date.now()-g,docsRoot:zS(e.cwd),...f?{}:{conflictsSkipped:n,unreadableSkipped:o?.unreadableSkipped??0},...o?.scope?{scope:o.scope}:{}}))console.log(T&&y.failed.length===0?Q(G):V(G));let C=y.aborted||!!y.stopped;if(!T)return C;let P=ad(e.cwd,e.profileName);P&&console.log(d(P));let S=Lr(e.cwd,e.profileName);if(S&&console.log(d(S)),C)return C;try{let G=Py(await Cy({cwd:e.cwd,profileName:e.profileName,onRateLimit:D=>console.log(d(` Document360 rate limit \u2014 waiting ${Math.ceil(D.waitMs/1e3)}s\u2026`))}));G&&console.log(G.startsWith("\u26A0")?V(G):d(G))}catch{}try{let G=Ay(await Ty({cwd:e.cwd,profileName:e.profileName}));G&&console.log(d(G))}catch{}let j=await Fs(e.cwd,e.profileName);return j&&console.log(d(j)),C};try{if(!await a(t,!1)){let f=await ud(e.cwd,e.profileName,t,i.paths);if(f.length>0&&await a(f,!0),Qt(s))for(let g of Ye(e.cwd,o?.scope?`/push ${o.scope}`:"/push --all"))console.log(Q(g))}}catch(c){console.log(_(`Push run failed: ${c.message}`))}if(console.log(""),i.retryable>0){let c=Se("/push",o?.scope?"":"--all",o?.scope);return console.log(d(i.stopped?`Next: ${c} \u2014 your articles are unchanged, so it picks up where the run left off.`:`Next: ${c} \u2014 retries the ${i.retryable} article${i.retryable===1?"":"s"} that could not be saved.`)),{kind:"continue",ownNext:!0}}let l=id(e.cwd,{scope:o?.scope,profileName:e.profileName});return l?(console.log(d(`Next: ${l.cmd} \u2014 ${l.reason}.`)),{kind:"continue",ownNext:!0}):{kind:"continue"}}async function Qa(e,t,n){let o=US(e,n);if(o.length===0)return[];let r=new Set(Uo((await Va({cwd:e,profileName:t})).entries).map(s=>s.path));return o.filter(s=>r.has(s))}async function ud(e,t,n,o=[]){try{let r=new Set(n),s=new Set(o),i=await Va({cwd:e,profileName:t});return Uo(i.entries).map(a=>a.path).filter(a=>r.has(a)&&!s.has(a))}catch{return[]}}async function nC(e,t,n){try{let o=await VS({cwd:e,...t?{profileName:t}:{},relPath:n.replace(/\\/g,"/"),preview:!0});console.log(""),console.log(Q(`${o.title}`)),console.log(d(`${o.path} \xB7 ${o.articleId?`updates article ${o.articleId}`:"would be created as a new article"}`)),o.shots.length>0&&console.log(d(`${o.shots.length} screenshot${o.shots.length===1?"":"s"} embedded: ${o.shots.map(r=>r.id).join(", ")}`)),o.missingShots.length>0&&console.log(V(`\u26A0 ${o.missingShots.length} screenshot${o.missingShots.length===1?"":"s"} not taken yet \u2014 the description publishes instead: ${o.missingShots.join(", ")}`)),console.log(d("\u2500".repeat(Math.min(80,Math.max(20,(process.stdout.columns??80)-1))))),console.log(o.body),console.log(d("\u2500".repeat(Math.min(80,Math.max(20,(process.stdout.columns??80)-1))))),console.log(d(`Nothing was sent. Run /push ${o.path} to save it as a draft.`))}catch(o){console.log(_(o.message))}return{kind:"continue"}}async function Ja(e,t){let n=Je(t?.cwd??process.cwd(),t?.profileName);if(!n.ok)return console.log(_(n.reason)),{kind:"continue"};let o=e.includes(Lt),r=e.includes(KS);if(e.includes("--preview")){let i=e.find(a=>!a.startsWith("--"));return i?await nC(t?.cwd??process.cwd(),t?.profileName,i):(console.log(_("Which article? Use /push --preview <path> \u2014 a preview is per article.")),{kind:"continue"})}let s=e[0];if(e.includes("--screenshots")||e.includes("--shots")){let i=t?.cwd??process.cwd(),a=e.find(x=>!x.startsWith("--")),l=He(i),c=_a(l,a);if(c.length===0){let x=l.filter(T=>T.state==="captured").length;return console.log(d(x>0?`${x} screenshot${x===1?" has":"s have"} been taken but the callouts aren't drawn on yet, so there is nothing to embed \u2014 /screenshot annotate finishes ${x===1?"it":"them"}.`:a?`No captured screenshots in ${a} \u2014 /push --screenshots covers the whole knowledge base.`:"No captured screenshots yet \u2014 /screenshot capture first.")),{kind:"continue"}}let f=Ma(l,a);f&&console.log(f);let g=Ua(l);g&&console.log(g);let y=Se("","--screenshots",a).trim();return await Jt(t??{cwd:process.cwd()},c,0,{skipReview:o,skipStyle:r,scope:y})}if(s==="--related"&&e[1]){if(!t)return{kind:"continue"};let i=await Qa(t.cwd,t.profileName,e[1].replace(/\\/g,"/"));return i.length===0?(console.log(Q("\u2713 All related articles are already on Document360.")),{kind:"continue"}):await Jt(t,i,0,{skipReview:o,skipStyle:r,scope:`--related ${e[1]}`})}if(s==="--all"){console.log(d("Checking what needs pushing\u2026"));try{let i=await Va({cwd:t?.cwd??process.cwd()}),a=new Set(Mo(t?.cwd??process.cwd(),t?.profileName)),{clean:l,conflicts:c}=_o(i.entries,a),f=c.filter(C=>a.has(C.path)).length,g=Ya(i.entries),y=e.includes("--force"),x=y&&c.length>0;g>0&&console.log(V(`\u26A0 ${g} article(s) already exist on Document360 but aren't linked \u2014 left out (pushing would duplicate them). Run /catch-up or ask me to link them first.`)),c.length>0&&!x&&console.log(V(Us(c.length,f)));let T=x?[...l,...c]:l;if(T.length===0){if(c.length>0){let P=Lr(t?.cwd??process.cwd(),t?.profileName);return P&&console.log(d(P)),console.log(d("Resolve the conflicts: /resolve")),{kind:"continue"}}console.log(Q(g>0?"\u2713 Nothing else is ahead of Document360 to push.":"\u2713 Nothing is ahead of Document360 \u2014 nothing to push."));let C=Lr(t?.cwd??process.cwd(),t?.profileName);if(C&&console.log(d(C)),t)try{let P=Py(await Cy({cwd:t.cwd,profileName:t.profileName,onRateLimit:j=>console.log(d(` Document360 rate limit \u2014 waiting ${Math.ceil(j.waitMs/1e3)}s\u2026`))}));P&&console.log(P.startsWith("\u26A0")?V(P):d(P));let S=Ay(await Ty({cwd:t.cwd,profileName:t.profileName}));S&&console.log(d(S))}catch{}return{kind:"continue"}}return await Jt(t??{cwd:process.cwd()},T.map(C=>C.path),x?0:c.length,{skipReview:o,skipStyle:r,allowUnpullable:y,unreadableSkipped:x?0:f})}catch(i){return console.log(_(`Could not compute sync status: ${i.message}`)),{kind:"continue"}}}if(s&&s.startsWith("--")&&(s=e.find(i=>!i.startsWith("--"))??""),!s){console.log(d("Checking what needs pushing\u2026"));let i,a=0,l=0;try{let c=await Va({cwd:t?.cwd??process.cwd()}),f=new Set(Mo(t?.cwd??process.cwd(),t?.profileName)),{clean:g,conflicts:y}=_o(c.entries,f);l=y.filter(T=>f.has(T.path)).length,i=g,a=y.length;let x=Ya(c.entries);if(x>0&&console.log(V(`\u26A0 ${x} article(s) already exist on Document360 but aren't linked \u2014 left out (pushing would duplicate them). Run /catch-up or ask me to link them first.`)),y.length>0&&console.log(V(Us(y.length,l))),i.length===0&&y.length>0)return console.log(d("Resolve the conflicts: /resolve")),{kind:"continue"}}catch(c){return console.log(_(`Could not compute sync status: ${c.message}`)),console.log(d("Push a specific article: /push <article-path>")),{kind:"continue"}}if(i.length===0)return console.log(Q("\u2713 Nothing is ahead of Document360 \u2014 nothing to push.")),console.log(d(" (Articles without a sync base are unverified \u2014 push those by path if needed.)")),{kind:"continue"};if(!process.stdin.isTTY||!t){for(let c of i)console.log(` ${c.path} ${d(`(${c.label})`)}`);return console.log(d("Run: /push <article-path>")),{kind:"continue"}}try{s=await t.withPausedInput(()=>_S({message:"Push which article?",choices:[...i.length>1?[{name:`All ${i.length} candidates`,value:"--all",description:"one agent run over every candidate"}]:[],...i.map(c=>({name:c.path,value:c.path,description:c.label}))]}))}catch{return console.log(d("Cancelled.")),{kind:"continue"}}if(s==="--all")return await Jt(t,i.map(c=>c.path),a,{skipReview:o,skipStyle:r,unreadableSkipped:l})}return await Jt(t??{cwd:process.cwd()},[s],0,{skipReview:o,skipStyle:r,scope:s})}ie();import{select as iC}from"@inquirer/prompts";import{computeSyncStatus as aC,localAheadPaths as lC,pendingDrafts as cC,publishArticleLive as uC,publishGateVerdict as dC,publishGateLines as pC,blockedPaths as hC,placeholderRemedy as fC,resolveActiveProfile as eM,SKIP_STYLE_FLAG as mC,SKIP_SHOTS_FLAG as gC}from"document360-engine";import{gateFixTarget as oC}from"document360-engine";var pd=e=>"ruleId"in e,Za=e=>pd(e)?e.severity:"warning",zn=e=>e===1?"":"s";function Bs(e,t){let n=t??oC(e),o=n?` ${n}`:"";return e.unreviewed.length>0?`/review${o} --blocked --fix`:`/lint${o} --blocked --fix`}var dd=new Map;var rC=e=>pd(e)?e.fp||`${e.ruleId}:${e.path}:${e.line}:${e.term}`:`${e.kind}:${e.path}:${e.line}`;function Hs(e){let{cwd:t,after:n,scope:o,aborted:r,blocked:s}=e,i=n.filter(x=>Za(x)==="error").length,a=n.filter(x=>Za(x)==="warning").length,l=n.filter(x=>Za(x)==="suggestion").length,c=Se("/lint","--blocked --fix",o);if(r){let x=Se("/lint",s?"--blocked --fix":"--fix",o);return{text:`Stopped. Nothing else changed \u2014 ${x} picks up where this left off.`,cmd:x}}if(i>0){let x=n.filter(j=>Za(j)==="error"),T=new Set(x.map(j=>j.path)).size,C=`${t}::${o??""}`,P=x.map(rC).sort().join("|"),S=dd.get(C)===P;if(dd.set(C,P),S){let j=Se("/lint","--blocked --full",o),G=[...new Set(x.filter(pd).map(D=>D.ruleId))];return{text:`The same ${i} error${zn(i)} in ${T} article${zn(T)} survived two ${c} passes, so a third would change nothing \u2014 ${i===1?"it needs":"they need"} a person. ${j} shows ${i===1?"it":"them"} in place. If ${G.length===1?`${G[0]} is`:"a rule is"} wrong for your product, switch it off in .d360-writer/config.json under "styleGuide": { "rules": { "<rule>": "off" } }.`,cmd:j}}return{text:`${i} error${zn(i)} in ${T} article${zn(T)} still block${i===1?"s":""} publishing. ${c} has another go at ${i===1?"it":"just those"} \u2014 if a second pass leaves the same ones, they need a human eye.`,cmd:c}}dd.delete(`${t}::${o??""}`);let f=a>0&&l>0?`${a} warning${zn(a)} and ${l} suggestion${zn(l)} remain (neither blocks publishing). `:a>0?`${a} warning${zn(a)} remain${a===1?"s":""}, which ${a===1?"does":"do"} not block publishing. `:l>0?`${l} suggestion${zn(l)} remain${l===1?"s":""} \u2014 ${l===1?"it is":"they are"} judgment calls and ${l===1?"does":"do"} not block publishing. `:"",g=f===""?"\u2713 Nothing left to fix. ":`\u2713 ${f}`,y=o?"/push":"/push --all";return Ot(t,{text:`${g}Nothing is blocking${o?` in ${o}`:""} \u2014 ${y} saves the articles to Document360 as drafts.`,cmd:y},g,o)}var sC={check:"/check is now /lint (same command)."};function el(){return"That saves drafts rather than taking anything live, so running /push. (/publish takes saved drafts live.)"}var Dy=new Set;function Gs(e){let t=sC[e.replace(/^\//,"")];return!t||Dy.has(e)?null:(Dy.add(e),t)}var yC=150,wC=e=>e>0?new Promise(t=>setTimeout(t,e)):Promise.resolve(),fd=e=>e===1?"":"s",hd=e=>e.replace(/\\/g,"/").split("/").pop()??e;function md(e){return e.includes("--screenshots")||e.includes("--shots")||e[0]==="--related"}async function gd(e,t,n){await aC({cwd:e,profileName:t});let o=n?n.replace(/\\/g,"/").replace(/\/+$/,""):null;return cC(e,t).filter(r=>!o||r.path===o||r.path.startsWith(`${o}/`)).map(r=>({...r,label:bC(e,r.path)}))}function bC(e,t){let n=Pa(e,t)??Is(t),o=Ca(t),r=o?qn(o):"";return r?`${r} \u203A ${n}`:n}function kC(e){let t=Math.min(58,Math.max(...e.map(n=>n.label.length),20));return e.map(n=>` ${n.label.padEnd(t)} ${n.version?`draft v${n.version} \u2192 live`:"draft \u2192 live"}`)}function yd(e,t,n){let r=[`\u2713 Nothing is waiting to go live${n?` in ${n}`:""} \u2014 readers are seeing the latest version of every article.`],s=n?n.replace(/\\/g,"/").replace(/\/+$/,""):null,i=lC(e,t).filter(a=>!s||a===s||a.startsWith(`${s}/`)).length;return i>0&&r.push(`${i} article${fd(i)} ${i===1?"has":"have"} local edits that aren't on Document360 yet \u2014 that's /push${n?` ${n}`:""}.`),{lines:r,ahead:i}}function tl(e,t,n){let o=null;try{o=fC(e,t.placeholders)?.cmd??null}catch{}return{cmd:t.unreviewed.length>0||t.styleErrors.length>0?Bs(t,n):o,shotFix:o}}async function wd(e,t,n,o={}){let r={published:[],blocked:[],failed:[]},s=0;for(let i of n){o.onProgress?.(s,n.length,i.label);let a=await uC({articleId:i.articleId,cwd:e,profileName:t,allowUnreviewed:o.skipReview===!0,allowStyleErrors:o.skipStyle===!0,allowProduction:o.allowProduction===!0});a.ok?r.published.push({path:i.path,...a.url?{url:a.url}:{}}):a.blocked?r.blocked.push({path:i.path,message:a.message}):r.failed.push({path:i.path,message:a.error.message}),s+=1,s<n.length&&await wC(yC)}return r}function bd(e){let{published:t,blocked:n,failed:o}=e,r=[];if(t.length>0){r.push(`\u2713 ${t.length} article${fd(t.length)} ${t.length===1?"is":"are"} now live \u2014 readers see ${t.length===1?"it":"them"} now.`);let s=t.filter(i=>i.url);for(let i of s.slice(0,3))r.push(` \u2B95 ${i.url}`);s.length>3&&r.push(` \u2B95 \u2026and ${s.length-3} more`)}if(n.length>0){r.push(`\u2717 ${n.length} refused by the publish gates \u2014 still ${n.length===1?"a draft":"drafts"}, nothing was lost:`);for(let s of n.slice(0,3))r.push(` \xB7 ${hd(s.path)}`);n.length>3&&r.push(` \xB7 \u2026and ${n.length-3} more`),r.push(" /status names what is blocking each one, and the command that clears it. It costs nothing.")}if(o.length>0){r.push(`\u2717 ${o.length} failed \u2014 ${o.length===1?"it is":"they are"} still a draft, so re-running /publish retries ${o.length===1?"it":"them"}:`);for(let s of o.slice(0,3))r.push(` \xB7 ${hd(s.path)} \u2014 ${s.message}`);o.length>3&&r.push(` \xB7 \u2026and ${o.length-3} more`)}return r.length===0&&r.push("Nothing was published."),r}async function jy(e,t){let n=t?.cwd??process.cwd();if(md(e))return console.log(d(`\u2192 ${el()}`)),Ja(e,t);let o=Je(n,t?.profileName);if(!o.ok)return console.log(_(o.reason)),{kind:"continue"};let r=e.includes(Lt),s=e.includes(mC),i=e.includes(gC),a=e.includes("--all"),l=e.find(P=>!P.startsWith("--"));console.log(d("Checking what is waiting to go live\u2026"));let c;try{c=await gd(n,t?.profileName,l)}catch(P){return console.log(_(`Could not check Document360: ${P.message}`)),{kind:"continue"}}if(c.length===0){for(let P of yd(n,t?.profileName,l).lines)console.log(P.startsWith("\u2713")?Q(P):d(P));return{kind:"continue"}}let f=dC(n,c.map(P=>P.path),{allowUnreviewed:r,allowStyleErrors:s,checkScreenshots:!0,allowPlaceholders:i}),g=new Set(hC(f)),y=c.filter(P=>!g.has(P.path)),x=tl(n,f,l);if(g.size>0)for(let P of pC(f,c.length,{scope:l,act:"publish",going:y.length,shotFix:x.shotFix}))console.log(V(P));if(y.length===0)return x.cmd&&console.log(d(`Run: ${x.cmd}`)),{kind:"continue"};c=y,console.log(""),console.log(`${c.length} draft${fd(c.length)} ${c.length===1?"is":"are"} ahead of what readers see:`);for(let P of kC(c.slice(0,12)))console.log(d(P));c.length>12&&console.log(d(` \u2026and ${c.length-12} more`)),console.log("");let T=c;if(!a){let P=await iC({message:"Publish live to your readers?",choices:[{name:`Yes \u2014 publish all ${c.length} live`,value:"all"},...c.slice(0,8).map(S=>({name:`Just ${S.label}`,value:S.path})),{name:"No \u2014 leave them as drafts",value:"none"}]});if(P==="none")return console.log(d("Left as drafts. Nothing changed.")),{kind:"continue"};P!=="all"&&(T=c.filter(S=>S.path===P))}let C=await wd(n,t?.profileName,T,{skipReview:r,skipStyle:s,allowProduction:t?.allowProdWrites?.()===!0,onProgress:(P,S,j)=>console.log(d(` \u25B8 ${P+1}/${S} ${hd(j)}\u2026`))});console.log("");for(let P of bd(C))console.log(P.startsWith("\u2713")?Q(P):P.startsWith("\u2717")?V(P):d(P));return{kind:"continue"}}import{loadKnownConflicts as $C,localArticleTitle as vC,resolveActiveProfile as xC,surfaceCoverageSafe as Iy,readProjectConfig as Ly,recordDeclinedSurface as RC,areaExists as SC,declineTargets as Oy}from"document360-engine";function CC(e,t){let n=vC(e,t);return n||(t.split(/[\\/]/).pop()??t).replace(/\.md$/,"").replace(/^\d+[-_]/,"").replace(/[-_]+/g," ")}function nl(e,t){let n=[];try{n=$C(e,xC(e,t).name)}catch{return null}if(n.length===0)return null;let o=n.length,r=n.slice(0,3).map(i=>CC(e,i)),s=o>3?`${r.join(", ")} and ${o-3} more`:r.join(", ");return{paths:n,note:`\u26A0 ${o} article${o===1?"":"s"} ${o===1?"has":"have"} been changed both here and on Document360 (${s}), so your local copy is not a reliable picture of what your readers have. An audit over it could tell you to write an article that already exists, or miss one that needs your decision \u2014 so it needs ${o===1?"that":"those"} settled first.`}}var Or={cmd:"/resolve",reason:"it walks each one with you \u2014 keep yours, take the portal's, or skip"};function ol(e,t){let n=t.indexOf("--decline");if(n<0)return null;let o=t[n+1];if(!o||o.startsWith("--")){let a=Iy(e,Ly(e)),l=a.recognised?a.areas.slice(0,5).flatMap(c=>Oy(c).map(f=>` /audit --decline ${f}`)):[];return["Name the path to decline \u2014 a folder, or a single file.",...l.length?["","No article covers these right now. Run one of these to settle it for good:",...l]:["","For example:"," /audit --decline src/internal-admin"]]}let r=o.replace(/\\/g,"/").replace(/\/+$/,"");if(!SC(e,r))return[`There is nothing at ${r}, so there is nothing to decline. Copy the path from /eval or /audit.`];let s=t.slice(n+2).filter(a=>!a.startsWith("--")).join(" ")||void 0;return RC(e,r,s)?[`\u2713 ${r} will not be reported as needing an article. /audit and /eval leave it out from now on, and say how many you have declined.`]:[`Could not write .d360-writer/declined-surfaces.json, so ${r} was not declined.`]}async function qs(e){if(e?.cwd){let r=nl(e.cwd,e.profileName);if(r)return{kind:"audit-blocked",note:r.note,lead:Or}}let t=e?.cwd?Iy(e.cwd,Ly(e.cwd)):null,n=[];if(t?.recognised&&t.areas.length>0){let r=t.areas.slice(0,15);n.push("","2.7. These parts of the product are cited by NO article's `sources:` frontmatter. This list is DETERMINISTIC and complete as of now \u2014 it does not depend on the diff window above, so treat it as authoritative rather than re-deriving it:",...r.map(s=>` - ${s.folder} (${s.files} of ${s.total} file${s.total===1?"":"s"} uncited, e.g. ${s.sample.join(", ")}) \u2014 to decline: ${Oy(s).map(i=>`/audit --decline ${i}`).join(" \xB7 ")}`),...t.areas.length>r.length?[` \u2026and ${t.areas.length-r.length} more areas.`]:[]," EVERY row above must leave this run accounted for: a `create` row, or a stated reason it needs no article (internal-only, unreachable by a reader, already covered by an existing article's prose). Do NOT silently drop one \u2014 if you list fewer paths than this section handed you, say which you left out and why."," A `decline` verdict is not a record. For EVERY row you decline, print its exact /audit --decline line (copied from above \u2014 it already picks folder-or-file correctly) in the closing block, each on its own line so the terminal makes it a button. A judgement that leaves the turn as prose changes nothing and will be re-derived, at this price, on every future audit.",...t.declined>0?[` (${t.declined} more ${t.declined===1?"is":"are"} already declined and left out of that list.)`]:[])}return{kind:"forward-to-agent",prompt:["Run the gap-analysis skill against this repo.","","Follow its stages in order:","1. d360_sync_status first; stop if any article is changed in both places, or changed on Document360 and not yet pulled down (the local copy cannot be trusted until that is resolved). Say which articles, in plain words, never the internal status word \u2014 and tell the user to run /resolve, which walks the affected articles one at a time (keep the local copy, take the portal's, or skip). Do NOT ask them to report back to you: they will run /resolve and then /audit again.","2. git diff since the lastAnalyzedCommit marker in d360-category-map.json (or last 30 days if no marker), filtered to the authoritativeSourceFiles paths.","2.5. d360_pending_hints: the source agent's own notes on what it shipped. Verify each against the CURRENT code (its `sources` exist, and the code as it stands shows the surface it claims) before trusting it; never judge it by its `branch` or `commit`. Fold verified ones into the table, flag the rest.","2.6. Anything d360_pending_hints returns under `held` is NOT a proposal. Its cited code has uncommitted changes, so the builder is still writing that surface and has already been asked whether it is done. List each held item at the END of the table as a `waiting` row (Reason: the code behind it is not finished), do NOT propose create/update/retire for it, and do NOT stamp it consumed.",...n,"3. Map changes (diff + hints) to articles via their `sources:` frontmatter; read only implicated articles and changed source files. If articles are missing `sources:`, this is the one-time bootstrap pass, so backfill them.","4. Output the proposal table: | Article (path or proposed title) | Action (create | update | retire | link) | Reason | Evidence | Scope |. Leave every hint OPEN \u2014 this run proposes, it does not write articles, so nothing has been acted on yet.","5. End with the skill's closing block, and put NOTHING below it \u2014 no receipts, no roadmap confirmations, no feedback marker line, no summary. It ends in a question, and a question the user has to scroll back to find is one you did not really ask.","","Do not start writing or updating any article yet; the proposal table is the deliverable.","The audited-up-to marker is recorded for you when this command finishes. Read it to size the diff window; never write it, and never report it as a missing capability."].join(`
340
340
  `),display:"/audit"}}ie();import{readProjectConfig as PC,writeProjectConfig as _y,autoBudgetUsd as TC,autoModeEnabled as AC,DEFAULT_AUTO_BUDGET_USD as EC}from"document360-engine";function Ke(e){return`$${e%1===0?e.toFixed(0):e.toFixed(2)}`}function Vs(e){return e?"I run the next step myself instead of waiting for you \u2014 writing, reviewing, checking style, saving and going live.":"I suggest the next step and wait for you to run it."}function Ys(e){let t=AC(e),n=TC(e),o=e.auto?.budgetUsd===void 0?" (the standard limit \u2014 nothing set for this repo)":"",r=[{tone:"info",text:`Mode: ${t?"auto":"manual"} \u2014 ${Vs(t)}`}];return t?(r.push({tone:"info",text:`Spending limit: ${Ke(n)} a run${o}. I stop and report when it is reached.`}),r.push({tone:"info",text:"Screenshots are never part of an automatic run \u2014 they need your product running."}),r.push({tone:"info",text:"Stop a run at any time: esc. Switch back to manual: shift+tab, or /auto off."})):r.push({tone:"info",text:`Turn it on: /auto on \xB7 or shift+tab. Spending limit if you do: ${Ke(n)} a run.`}),r}function rl(e,t,n,o=new Date){let r={...t,auto:{...t.auto,enabled:n,decidedAt:o.toISOString()}};return _y(r,e),[n?{tone:"ok",text:Y("\u2713")+"Auto mode ON \u2014 "+Vs(!0)}:{tone:"ok",text:Y("\u2713")+"Manual mode \u2014 "+Vs(!1)},...Ys(r)]}function $d(e,t,n){if(!Number.isFinite(n)||n<=0)return null;let o={...t,auto:{...t.auto,enabled:t.auto?.enabled===!0,budgetUsd:n}};return _y(o,e),[{tone:"ok",text:Y("\u2713")+`Spending limit set to ${Ke(n)} a run.`},...Ys(o)]}function vd(e){let t=e.findIndex(r=>r==="--budget"||r.startsWith("--budget="));if(t===-1)return{malformed:!1};let n=e[t].includes("=")?e[t].split("=").slice(1).join("="):e[t+1];if(n===void 0)return{malformed:!0};let o=Number(String(n).replace(/^\$/,"").replace(/,/g,""));return!Number.isFinite(o)||o<=0?{malformed:!0}:{usd:o,malformed:!1}}function xd(e,t){let n=e.findIndex(o=>o==="--budget");return e.filter((o,r)=>!o.startsWith("--")&&!(n!==-1&&r===n+1)&&o!==String(t)).map(o=>o.trim().toLowerCase()).find(o=>o.length>0)}var kd=e=>{for(let t of e)console.log(t.tone==="ok"?Q(t.text):t.tone==="warn"?V(t.text):t.tone==="error"?_(t.text):d(t.text))};async function My(e,t){let n=t.cwd,o=PC(n);if(!o)return console.log(_("No d360-writer config here. Run /init first.")),{kind:"continue"};let r=vd(e);if(r.malformed)return console.log(_(`I could not read that spending limit. Give it as an amount, like: /auto --budget 150 (the standard limit is ${Ke(EC)}).`)),{kind:"continue"};let s=xd(e,r.usd);if(r.usd!==void 0&&!s){let i=$d(n,o,r.usd);return i&&kd(i),{kind:"continue"}}if(!s||s==="status")return kd(Ys(o)),{kind:"continue"};if(s==="on"||s==="off"){let i=r.usd!==void 0?{...o,auto:{...o.auto,enabled:o.auto?.enabled===!0,budgetUsd:r.usd}}:o;return kd(rl(n,i,s==="on")),{kind:"continue"}}return console.log(_(`I don't know "${s}". Use: /auto on \xB7 /auto off \xB7 /auto status \xB7 /auto --budget <amount>`)),{kind:"continue"}}ie();import{readProjectConfig as NC}from"document360-engine";function Rd(){return["Run the discover-product skill against this repo.","","Build (or continue refining) the product brief at `.d360-writer/product-brief.md` by interviewing the","source repo's coding agent (the builder):","1. Gather all grounding: the code surface (analyze-codebase pass), `.d360-writer/architecture/`"," (+ CREATOR-NOTES), any specs under `.d360-writer/input/`, existing docs, git log, and the current"," product brief if one exists.","2. Self-assess against the 8-point readiness rubric (identity, audience, core mental model,"," misconceptions, positioning, key concepts & terminology, critical journeys, boundaries/non-goals).","3. Write/update the brief, with every claim cited to its grounding, and keep its frontmatter current"," (status, rounds, open_questions, last_updated).","4. For the dimensions you cannot yet ground confidently, ask the builder a focused batch (\u22646) of pointed",' questions via d360_ask_builder, with the subject prefixed "[discovery] ". Then END the turn;'," builder answers async; /catch-up will ingest the answers and continue the loop.","","Set status: confident and hand off to propose-structure ONLY when every rubric dimension passes. If the","builder stays silent across a few rounds, degrade gracefully per the skill (best-effort brief, flagged","open questions) rather than blocking the docs. Do NOT write or publish any articles in this turn."].join(`
341
341
  `)}async function Uy(e,t){let n=t?.cwd??process.cwd();return NC(n)?{kind:"forward-to-agent",prompt:Rd(),display:"/discover"}:(console.log(_("No d360-writer config here. Run /init first.")),{kind:"continue"})}ie();import{checkbox as DC}from"@inquirer/prompts";import{inventoryRepo as jC,readProjectConfig as Fy,writeProjectConfig as IC}from"document360-engine";function Sd(e,t){let n=Fy(e);n&&(n.authoritativeSourceFiles=t,IC(n,e))}function sl(e){let t=[`${e.fileCount}${e.fileCount>=5e3?"+":""} files`];return e.stacks.length&&t.push(e.stacks.join("+")),t.push(e.reason),t.join(" \xB7 ")}async function Wy(e,t){let n=t?.cwd??process.cwd();if(!Fy(n))return console.log(_("No .d360-writer/config.json here. Run /init first.")),{kind:"continue"};let o=jC(n);if(o.length===0)return console.log(d('No candidate source folders found. Set "authoritativeSourceFiles" in .d360-writer/config.json manually.')),{kind:"continue"};if(!process.stdin.isTTY||!t){console.log(""),console.log(Z("Recommended documentation scope (run /scope in the REPL to choose):"));for(let s of o)console.log(` ${s.recommended?"\u25C9":"\u25CB"} ${s.path} ${d(sl(s))}`);return console.log(""),{kind:"continue"}}let r;try{r=await t.withPausedInput(()=>DC({message:"Which folders back the user docs? (space toggles, enter confirms)",choices:o.map(s=>({name:`${s.path} (${sl(s)})`,value:s.path,checked:s.recommended})),pageSize:20}))}catch{return console.log(d("Cancelled.")),{kind:"continue"}}if(r.length===0)return console.log(d("Nothing selected \u2014 scope unchanged.")),{kind:"continue"};Sd(n,r),console.log(Q(`\u2713 Scoped to ${r.length} folder(s) \u2014 written to .d360-writer/config.json`));for(let s of r)console.log(` ${s}`);return console.log(""),{kind:"continue"}}ie();import{confirm as VC}from"@inquirer/prompts";import{applyPull as Ad,computeSyncStatus as Zy,compareRemoteSources as YC,planPull as ew,planImport as KC,applyImport as zC,establishBaselines as XC,rebaselineServerMeta as QC,D360AuthError as JC}from"document360-engine";ie();var By=[{status:"conflict",header:"Changed in both places \u2014 pick a direction (/pull <path> or /push <path>):",paint:_,mark:"!"},{status:"local-ahead",header:"Changed here \u2014 push with /push <path>:",paint:V,mark:"M"},{status:"remote-ahead",header:"Changed on Document360 \u2014 pull with /pull <path> (or just /pull):",paint:Z,mark:"M"},{status:"deleted-local",header:"Removed locally \u2014 still on Document360 (pull to restore, or leave it):",paint:_,mark:"D"},{status:"deleted-remote",header:"Gone from Document360 \u2014 still here (publish to recreate, or delete the file):",paint:_,mark:"D"},{status:"orphaned",header:"Gone from both sides \u2014 these tracking entries point at nothing now:",paint:Te,mark:"X"},{status:"untracked-local",header:"New here \u2014 not on Document360 yet (publish to add them):",paint:d,mark:"?"},{status:"untracked-remote",header:"On Document360 \u2014 not in your local docs yet (run /pull to import them):",paint:d,mark:"?"},{status:"unknown-base",header:"Tracked, not yet compared \u2014 run /pull --baseline to set a baseline (or the next publish/pull sets one):",paint:Te,mark:"\xB7"}];function LC(e){return e.path?e.path:`${e.title??"(untitled)"} ${Te(`[${e.articleId}]`)}`}function OC(e){let t=e/1e3;if(t<60)return`${t.toFixed(1)}s`;let n=Math.floor(t/60);return`${n}m ${Math.round(t-n*60)}s`}function il(e,t){let n=[],o=e.entries.filter(f=>f.link);if(o.length>0){n.push(""),n.push(Qe("Already on Document360, just not linked \u2014 ask me to link these (a republish would duplicate them):"));for(let f of o)n.push(` ${V("~")} ${V(f.path??"")} ${Te(`\u2192 "${f.link.title}"`)}`)}for(let f of By){let g=e.entries.filter(y=>y.status===f.status&&!(f.status==="untracked-local"&&y.link));if(g.length!==0){n.push(""),n.push(Qe(f.header));for(let y of g)n.push(` ${f.paint(f.mark)} ${f.paint(LC(y))}${y.detail?Te(` (${y.detail})`):""}`)}}let r=e.counts["in-sync"]??0,s=e.entries.length;n.push("");let i={"in-sync":"up to date","local-ahead":"changed here","remote-ahead":"changed on Document360",conflict:"changed both places","untracked-local":"new here","untracked-remote":"only on Document360","deleted-local":"removed locally","deleted-remote":"gone from Document360",orphaned:"gone both sides","unknown-base":"not yet compared"},a=o.length,l=s===r?Q("\u2713 everything up to date"):[r>0?Q(`\u2713 ${r} up to date`):null,a>0?`${a} to link`:null,...By.map(f=>{let g=(e.counts[f.status]??0)-(f.status==="untracked-local"?a:0);return g>0?`${g} ${i[f.status]}`:null})].filter(Boolean).join(Te(" \xB7 ")),c=t!==void 0?` \xB7 synced in ${OC(t)}`:"";return n.push(l+Te(` \xB7 ${s} tracked+seen \xB7 profile "${e.profile}" \xB7 docs root ${e.docsRoot}/${c}`)),n}import{structuredPatch as _C}from"diff";import Hy from"picocolors";var MC=80,_r=400,UC="\x1B[48;2;74;28;28m",FC="\x1B[48;2;24;66;24m",Gy="\x1B[49m",Cd=e=>String(e).padStart(5);function qy(e,t){let n=Math.min(e.length,t.length),o=0;for(;o<n&&e[o]===t[o];)o++;return o}function Vy(e,t,n){if(n===void 0||n<t-4)return e.slice(0,t);let o=Math.max(0,n-12);return`\u2026${e.slice(o).slice(0,Math.max(8,t-1))}`}function _t(e,t,n,o=MC){let r=T=>{let C=T.replace(/\r\n/g,`
342
342
  `);return C.endsWith(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document360-writer",
3
- "version": "0.5.314",
3
+ "version": "0.5.315",
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.279",
37
+ "document360-engine": "^0.2.280",
38
38
  "ink": "^5.2.1",
39
39
  "picocolors": "^1.1.1",
40
40
  "react": "^18.3.1",