document360-writer 0.5.193 → 0.5.195

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
@@ -352,7 +352,7 @@ Articles:
352
352
  `)}var qi="No input docs found. Drop this feature's PRDs/specs in .d360-writer/input/<feature>/ (.md or .pdf), then run /draft.";async function Yd(e,t){let n=t?.cwd??process.cwd();if(!Vg(n))return console.log(E("No d360-writer config here. Run /init first.")),{kind:"continue"};let{features:o,looseDocs:r}=Bi(n);if(o.length===0&&r===0)return console.log(g(qi)),{kind:"continue"};let s=e.join(" ").trim();if(!s){if(o.length>1){console.log(g("Multiple features in .d360-writer/input/ \u2014 draft which one? Run: /draft <feature>"));for(let p of o)console.log(` ${p.name} ${g(`(${p.docCount} doc${p.docCount===1?"":"s"})`)}`);return{kind:"continue"}}let c=o[0]?.name??"";return{kind:"forward-to-agent",prompt:Lr(c),display:c?`/draft ${c}`:"/draft"}}let i=Hi(o,s);return{kind:"forward-to-agent",prompt:Lr(i??"",i?void 0:s),display:`/draft ${s}`}}K();import{listOpenForDocs as Yg,readProjectConfig as zg}from"document360-engine";function Vi(e){try{return Yg(e).filter(t=>t.kind==="request").length}catch{return 0}}function Gi(e){let t=e.includes("--publish"),n=e.filter(o=>o!=="--publish").join(" ").trim();return{publish:t,what:n}}function Yi(e,t=!1){let n=["Run the document-handoff skill.",""];return e?n.push(`The builder hand-off (what was shipped this session): "${e}".`,"Treat this as the Stage 1 hand-off: ground it in the cited/changed code + the architecture brief,","then decide and APPLY the right doc action \u2014 a new section, a new article, an update to existing","article(s), or a retirement, or a mix. No fixed shape; pick the smallest set that keeps the docs correct."):n.push("No description was given \u2014 call d360_inbox and act on the OPEN `request` messages the builder left:","for each, run the document-handoff flow (ground \u2192 decide \u2192 apply) and mark the request `status: done`","with `resulting_articles` when finished."),n.push("","Draft only \u2014 do not publish. If you would create multiple new articles (a section), call","d360_estimate_cost and present the count + cost band first, then wait for the go-ahead."),t&&n.push("","After the drafts exist and the user confirms any bulk gate, publish the new/updated articles to","Document360 (the publish-to-d360 skill). On a production profile, respect the write-guard."),n.join(`
353
353
  `)}var zi="Nothing to document yet. Say what changed: /document <what you shipped> \u2014 or have the builder drop a request via their /devhints hand-off.";async function zd(e,t){let n=t?.cwd??process.cwd();if(!zg(n))return console.log(E("No d360-writer config here. Run /init first.")),{kind:"continue"};let{publish:o,what:r}=Gi(e);if(!r&&Vi(n)===0)return console.log(g(zi)),{kind:"continue"};let s=`/document${r?` ${r}`:""}${o?" --publish":""}`;return{kind:"forward-to-agent",prompt:Yi(r,o),display:s}}K();import{search as Kg}from"@inquirer/prompts";import{findByName as Xg,getSession as Qg,listSessions as Jg,relativeTime as Xd}from"document360-engine";async function Qd(e,t){let n=Jg(t.cwd).filter(o=>o.uuid!==t.currentUuid());if(n.length===0)return console.log(g("No saved sessions for this repo yet \u2014 sessions auto-save as you work.")),{kind:"continue"};if(e.length>0){let o=e.join(" "),r=Xg(t.cwd,o);return r?{kind:"resume",uuid:r.uuid,name:r.name}:(console.log(E(`No session matches "${o}".`)),Kd(n),{kind:"continue"})}if(!process.stdin.isTTY)return Kd(n),console.log(g("Run: /resume <name>")),{kind:"continue"};try{let o=await t.withPausedInput(()=>Kg({message:"Resume session (type to filter, \u2191\u2193 to navigate):",source:async s=>{let i=(s??"").toLowerCase();return n.filter(c=>!i||c.name.toLowerCase().includes(i)||c.firstPrompt.toLowerCase().includes(i)).map(c=>({name:`${c.name} ${Xd(c.updatedAt)}`,value:c.uuid,description:c.firstPrompt.slice(0,100)}))}})),r=Qg(o);return r?{kind:"resume",uuid:r.uuid,name:r.name}:{kind:"continue"}}catch{return console.log(""),{kind:"continue"}}}function Kd(e){console.log("");for(let t of e.slice(0,15))console.log(` ${W(t.name)} ${g(Xd(t.updatedAt))}`),console.log(` ${g(t.firstPrompt.slice(0,80))}`);console.log("")}K();import{renameSession as Zg}from"document360-engine";function Ki(e){let t=e.trim();return t.length>=2&&(t[0]==='"'&&t.endsWith('"')||t[0]==="'"&&t.endsWith("'"))?t.slice(1,-1).trim():t}async function Jd(e,t){let n=Ki(e.join(" "));if(!n)return console.log(E("Usage: /rename <new name>")),{kind:"continue"};let o=t.currentUuid();return o?(Zg(o,n)?console.log(U(`\u2713 Session renamed to "${n}"`)):console.log(E("Could not find the current session record.")),{kind:"continue"}):(t.setPendingName(n),console.log(U(`\u2713 Session will be named "${n}" once it starts.`)),{kind:"continue"})}import{knownEnvironments as Zd,readProjectConfig as ey,writeProjectConfig as ty}from"document360-engine";K();function Xi(e,t,n){if(!t)return"Usage: /profile add <name> [environment]";let o=ey(e);if(!o)return"No .d360-writer/config.json \u2014 run /init first.";if(o.profiles?.[t])return`Profile "${t}" already exists.`;let r=n??t;return Zd().includes(r)?(o.profiles={...o.profiles,[t]:{connection:{environment:r},production:!1}},ty(o,e),null):`Unknown environment "${r}". Known: ${Zd().join(", ")} (or add the profile with explicit URLs in .d360-writer/config.json).`}async function eu(e,t){let n=e[0];if(!n)return er(t.cwd),{kind:"continue"};if(n==="add"){let o=Xi(t.cwd,e[1],e[2]);return o?(console.log(E(o)),{kind:"continue"}):(console.log(U(`\u2713 Profile "${e[1]}" created (environment: ${e[2]??e[1]}).`)),console.log(` Switch + sign in: ${W(`/profile ${e[1]}`)} then ${W("/login")}`),{kind:"continue"})}return tr(t.cwd,n),nr(t.cwd,n),console.log(g(" Restarting agent for the new profile\u2026")),{kind:"clear"}}K();import{select as ny}from"@inquirer/prompts";import{readProjectConfig as oy,readUserConfig as tu,resolveModelSetting as Qi,writeUserConfig as nu}from"document360-engine";var Je=[{value:null,label:"Auto",desc:"Sonnet (latest) \u2014 right-sized for docs work (recommended); heavier models only when you pin them"},{value:"claude-fable-5",label:"Fable",desc:"Fable 5 \xB7 most capable, for the hardest and longest-running tasks"},{value:"opus",label:"Opus",desc:"Opus (latest) \xB7 best for everyday complex tasks"},{value:"sonnet",label:"Sonnet",desc:"Sonnet (latest) \xB7 efficient for routine tasks"},{value:"haiku",label:"Haiku",desc:"Haiku (latest) \xB7 fastest for quick answers"}];function _r(e){if(e.source==="auto")return 0;let t=e.model.toLowerCase(),n=Je.findIndex(o=>o.value!==null&&(o.value===t||o.label.toLowerCase()===t||t.includes(o.label.toLowerCase())));return n>=0?n:0}function ry(e){switch(e.source){case"project":return".d360-writer/config.json defaultModel (team setting)";case"user":return"~/.document360-writer/config.json (your /model setting)";case"env":return"ANTHROPIC_MODEL environment variable";case"auto":return"auto \u2014 the docs-right default (newest Sonnet); /model to pin another"}}function ao(e,t){let n=()=>Qi(e).model;if(t==="default"){let s=tu();return s.defaultModel?(delete s.defaultModel,nu(s),{lines:[`\u2713 Personal model override cleared \u2014 now: ${Qi(e).model} (auto; applies from your next message)`],changed:!0,effective:n()}):{lines:["No personal model override set \u2014 nothing to clear."],changed:!1,effective:void 0}}nu({...tu(),defaultModel:t});let o=[`\u2713 Personal model set to "${t}" (applies from your next message \u2014 conversation continues)`],r=oy(e)?.defaultModel;return r&&o.push(`\u26A0 .d360-writer/config.json sets defaultModel "${r}" \u2014 the team setting overrides yours until it is removed.`),{lines:o,changed:!0,effective:n()}}async function ou(e,t){let n=e[0]?.trim();if(!n){let i=Qi(t.cwd);if(!process.stdin.isTTY)return console.log(`${Te("Model:")} ${W(i.model)}${i.source==="auto"?g(" (auto)"):""}`),console.log(g(` source: ${ry(i)}`)),console.log(g(" change: /model <haiku|sonnet|opus|full-model-id> \xB7 reset: /model default")),{kind:"continue"};let c=_r(i),p;try{p=await t.withPausedInput(()=>ny({message:`Select model (current: ${i.model}${i.source==="auto"?" \xB7 auto":""})`,default:Je[c].value,choices:Je.map((v,A)=>({name:`${v.label}${A===c?" \u2714":""}`,value:v.value,description:v.desc}))}))}catch{return console.log(g("Cancelled.")),{kind:"continue"}}let{lines:h,changed:b,effective:$}=ao(t.cwd,p??"default");for(let v of h)console.log(v.startsWith("\u26A0")?H(v):v.startsWith("\u2713")?U(v):g(v));return b&&await t.setModel($),{kind:"continue"}}let{lines:o,changed:r,effective:s}=ao(t.cwd,n);for(let i of o)console.log(i.startsWith("\u26A0")?H(i):i.startsWith("\u2713")?U(i):g(i));return r&&await t.setModel(s),{kind:"continue"}}Mr();async function lu(e,t){return await t.withPausedInput(()=>fn(t.cwd)),{kind:"clear"}}async function du(e,t){return await t.withPausedInput(()=>Xo(t.cwd,t.profileName)),{kind:"clear"}}K();import{resolveActiveProfile as xy}from"document360-engine";async function uu(e,t){let n=!1;try{n=xy(t.cwd).production}catch{}return n?(console.log(H("\u26A0 Authorizing writes to the PRODUCTION profile for this session.")),{kind:"allow-prod"}):(console.log(g("Current profile is not a production profile \u2014 writes are already allowed.")),{kind:"continue"})}K();var pu=async(e,t)=>{try{await t.withPausedInput(()=>Zo({}))}catch(n){console.log(E(`Login failed: ${n.message}`))}return{kind:"continue"}};K();import{resolveActiveProfile as Cy,clearTokens as Ry,clearProfileProject as Sy}from"document360-engine";async function hu(e,t){let n;try{n=Cy(t.cwd,t.profileName).name}catch(r){return console.log(E(r.message)),{kind:"continue"}}let o=Ry(n);return Sy(t.cwd,n),console.log(o?U(`\u2713 Signed out of "${n}" and cleared its project/workspace selection.`):g(`Profile "${n}" was not signed in. Cleared any project/workspace selection.`)),console.log(g("Run /login to sign in and pick a project.")),{kind:"continue"}}import{spawn as mu}from"node:child_process";import{existsSync as ea,readFileSync as Zi,statSync as Iy}from"node:fs";import{homedir as Oy}from"node:os";import{basename as Ly,join as ho}from"node:path";import{readProjectConfig as _y,DEFAULT_OUTPUT_DIR as My,existingAnnotatedShotPath as Uy,resolveOutputDir as Fy}from"document360-engine";import De from"picocolors";import{annotationsPath as Py,resolveOutputDir as Ty,readProjectConfig as Ey,DEFAULT_OUTPUT_DIR as Dy,existingAnnotatedShotPath as Ay}from"document360-engine";function jy(e){let n=e.filter(o=>o!=="--all"&&o!=="-a").find(o=>!o.startsWith("-"));return n?{mode:"single",id:n}:{mode:"all"}}function lo(e){return e.filter(t=>t.state==="captured").map(t=>t.id)}function Vt(e,t){let n=Ty(e,Ey(e)?.outputDir??Dy),o=new Map($e(e).map(i=>[i.id,i])),r=i=>i.replace(`${e}\\`,"").replace(`${e}/`,"").replace(/\\/g,"/"),s=t.map(i=>{let c=o.get(i)?.file??"(unknown article)";return[`- ${i}`,` image: ${r(Ay(n,i))}`,` facts: ${r(Py(n,i))}`,` article: ${c}`].join(`
354
354
  `)}).join(`
355
- `);return[`Run the review-screenshots skill over ${t.length} newly captured screenshot${t.length===1?"":"s"}.`,"","For EACH shot below:","1. READ THE IMAGE FILE. Actually look at the picture \u2014 this judgment cannot be made from the spec"," source or the JSON alone.","2. Read its facts JSON (the `capture` block records what the capture observed: image dimensions,"," crop origin, whether the origin was inferred rather than declared, selectors that never resolved,"," and boxes that fell outside the frame).","3. Read the article to see what the placeholder promises the reader at that point.","4. Record a verdict with d360_verify_screenshot \u2014 every shot gets exactly one call.",' On "rejected": give a one-line plain-language reason, then FIX THE SPEC that produced it and pass'," repaired: true. Do not re-run the capture yourself; the user does that.","",s,"","Then report to the user briefly: ONE line per rejected shot naming the defect in plain words and what","you changed. Do NOT state counts, totals, or the re-capture command \u2014 an exact receipt is printed from","your recorded verdicts right after your report, so any number you state can only contradict it."].join(`
355
+ `);return[`Run the review-screenshots skill over ${t.length} newly captured screenshot${t.length===1?"":"s"}.`,"","For EACH shot below:","1. READ THE IMAGE FILE. Actually look at the picture \u2014 this judgment cannot be made from the spec"," source or the JSON alone.","2. Read its facts JSON (the `capture` block records what the capture observed: image dimensions,"," crop origin, whether the origin was inferred rather than declared, selectors that never resolved,"," boxes that fell outside the frame, how many elements each highlight/redaction selector covered,"," and whether the page was still loading when the shutter fired).","3. Read the article to see what the placeholder promises the reader at that point.","4. Record a verdict with d360_verify_screenshot \u2014 every shot gets exactly one call.",' On "rejected": give a one-line plain-language reason, then FIX THE SPEC that produced it and pass'," repaired: true. Do not re-run the capture yourself; the user does that.","",s,"","Then report to the user briefly: ONE line per rejected shot naming the defect in plain words and what","you changed. Do NOT state counts, totals, or the re-capture command \u2014 an exact receipt is printed from","your recorded verdicts right after your report, so any number you state can only contradict it."].join(`
356
356
  `)}function uo(e){return`${we("\u2192",De.blue)}Verifying ${e} new screenshot${e===1?"":"s"} (reading each image)\u2026`}var Ny=(e,t=88)=>{let n=e.replace(/\s+/g," ").trim();return n.length>t?`${n.slice(0,t-1)}\u2026`:n};function po(e,t){if(t.length===0)return[];let n=at(e).shots,o=[],r=[],s=[];for(let c of t){let p=n[c]?.review;p?p.verdict==="ok"?o.push(c):r.push({id:c,reason:p.reason??"no reason recorded",repaired:!!p.repaired}):s.push(c)}let i=[""];if(i.push(`${we("\u2714",De.green)}${De.green(`${o.length} verified`)}`+(r.length>0?` ${De.magenta(`\u2691 ${r.length} flagged`)}`:"")+(s.length>0?` ${De.yellow(`? ${s.length} not verified`)}`:"")+De.gray(` ${t.length} shot${t.length===1?"":"s"} in this pass`)),r.length>0){let c=Math.max(...r.map(h=>h.id.length));for(let h of r)i.push(` ${De.magenta(h.id.padEnd(c))} ${De.gray(Ny(h.reason))}`);let p=r.filter(h=>h.repaired).length;i.push(`${we("\u2192",De.magenta)}Re-capture the ${r.length} flagged: ${De.bold("/screenshot capture --failed")}`+(p<r.length?De.gray(` (${r.length-p} still need${r.length-p===1?"s":""} a spec fix first)`):""))}return s.length>0&&i.push(`${we("\u26A0",De.yellow)}No verdict recorded for ${s.slice(0,3).join(", ")}${s.length>3?`, +${s.length-3}`:""} \u2014 still awaiting verification. Run ${De.bold("/screenshot verify")} again.`),i}async function fu(e,t){let n=t?.cwd??process.cwd(),o=to(n);o&&console.log(o);let r=$e(n),s=jy(e);if(s.mode==="single"){let c=r.find(p=>p.id===s.id);return c?["captured","verified","rejected"].includes(c.state)?{kind:"forward-to-agent",prompt:Vt(n,[s.id]),display:`/screenshot verify ${s.id}`}:(console.log(`"${s.id}" has no captured image yet \u2014 /screenshot capture ${s.id} takes the shot first.`),{kind:"continue"}):(console.log(`No screenshot placeholder called "${s.id}". /screenshot list shows them all.`),{kind:"continue"})}let i=lo(r);if(i.length===0){let c=r.filter(h=>h.state==="verified").length,p=r.filter(h=>h.state==="rejected").length;return console.log(p>0?`Nothing left to verify. ${p} shot${p===1?"":"s"} failed verification \u2014 re-capture with: /screenshot capture --failed`:c>0?`All ${c} captured shot${c===1?" is":"s are"} already verified.`:"No captured screenshots to verify yet \u2014 /screenshot capture takes the shots first."),{kind:"continue"}}return{kind:"forward-to-agent",prompt:Vt(n,i),display:"/screenshot verify"}}var Wy=e=>e.replace(/\\/g,"/").replace(/\/+$/,"");function ta(e){let t=e.find(o=>!o.startsWith("--")),n=e.includes("--blocked")||e.includes("--retry-blocked");return e.includes("--failed")?{mode:"failed"}:e.includes("--all")?{mode:"all",retryBlocked:n}:!t&&n?{mode:"blocked"}:t?/[\\/]/.test(t)||t.endsWith(".md")?{mode:"scope",scope:Wy(t),retryBlocked:n}:{mode:"single",id:t}:{mode:"picker"}}function fo(e,t){let n=$e(e,t),o=r=>n.filter(s=>s.state===r).map(s=>s.id);return{ready:o("spec"),captured:[...o("captured"),...o("verified"),...o("rejected")].sort(),blocked:o("blocked"),broken:o("broken"),rejected:o("rejected"),rows:n}}function gu(e){let t=e.filter(s=>s.state==="spec"),n=e.filter(s=>s.state==="captured"||s.state==="verified"),o=[],r=t.filter(s=>!s.mutates);r.length>1&&o.push({kind:"all",action:"capture",label:`Capture all ${r.length} ready specs`,ids:r.map(s=>s.id),depth:0}),yr(o,t,"capture"),pi(o,e.filter(s=>s.state==="blocked"),e.filter(s=>s.state==="broken"),e.filter(s=>s.state==="rejected"));for(let s of[...n].sort((i,c)=>i.id.localeCompare(c.id))){let i=s.state==="verified"?" \u2714 verified \u2014 re-capture":" \u25CF to verify \u2014 re-capture";o.push({kind:"shot",action:"capture",label:s.id,ids:[s.id],depth:1,note:i})}return o}function na(e,t){let n=new Set(t.filter(r=>r.state!=="placeholder").map(r=>r.id)),o=t.filter(r=>r.state==="rejected").map(r=>r.id);return[...new Set([..._l(e).filter(r=>n.has(r)),...o])].sort()}function oa(e,t){let n=e.length;if(n===0)return null;let o=no(e).length;return[`\u2298 ${n} shot${n===1?"":"s"} excluded \u2014 ${n===1?"its data prerequisite wasn't":"their data prerequisites weren't"} met last run. Stage the data, then:`,` ${t}`,` /screenshot list shows what to stage (${o} action${o===1?"":"s"}).`].join(`
357
357
  `)}function By(e,t){let n=_y(e)?.outputDir??My;return Uy(Fy(e,n),t)}function ra(e,t,n,o){let r=By(e,t);return ea(r)&&Iy(r).mtimeMs>=o?"captured":n===0?"skipped":"failed"}function yu(e){let t=/⊘[^\n—]*—\s*(.+)/.exec(e);return t?t[1].trim():null}var Hy=/element is not (?:enabled|visible|stable|editable)|intercepts pointer events|outside of the viewport|element is not attached/i;function wu(e){let t=e.split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=t.filter(s=>/\b\w*Error\b|Cannot navigate|Timeout \d+m?s (exceeded|waiting)|net::|browser has been closed/i.test(s)&&!/^Error Context:/i.test(s)),o=[...new Set(t.filter(s=>Hy.test(s)).map(s=>s.replace(/^-\s*/,"")))].slice(0,2),r=t.find(s=>/locator resolved to/i.test(s))?.replace(/^-\s*/,"");return n.length>0||o.length>0?[...new Set([...n].slice(0,2)),...o,...r&&o.length>0?[r.length>120?`${r.slice(0,119)}\u2026`:r]:[]].join(`
358
358
  `):sa(e)}function sa(e,t=4){let n=/captured\s+·|^▶ Annotating|^✓ \d+ captured|^\s*[⊘✗]\s+\S+\s*$|^▶ Running \d+ spec/;return e.split(/\r?\n/).map(r=>r.trimEnd()).filter(r=>r.trim().length>0&&!n.test(r.trim())).slice(-t).join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document360-writer",
3
- "version": "0.5.193",
3
+ "version": "0.5.195",
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.181",
37
+ "document360-engine": "^0.2.183",
38
38
  "ink": "^5.2.1",
39
39
  "picocolors": "^1.1.1",
40
40
  "react": "^18.3.1",