document360-writer 0.5.135 → 0.5.136

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
@@ -311,7 +311,7 @@ ${s}${m}`).join("")}).join(`
311
311
  `+ls(e.text,n);case"note":return`
312
312
  `+Bp[e.tone](Sn(us(e.text)));case"done":{let o=e.inTokens&&e.inTokens>0?` \xB7 ${An(e.inTokens)} in${e.cachedPct!=null?` (${e.cachedPct}% cached)`:""}`:"";return`
313
313
  `+(e.ok?Z.magenta("\u2736 "):Z.red("\u2736 "))+Z.gray(`Cooked for ${jn(e.seconds)} \xB7 ${An(e.tokens)} tokens out${o}`+(e.costUsd>0?` \xB7 ${xe(e.costUsd)}`:""))}}}function $l(e,t){return e.map(n=>ps(n,t)).join(`
314
- `)}import{resolveActiveProfile as Vp,loadTokens as Gp,isExpired as Yp,decodeJwtClaims as xl}from"document360-engine";function zp(e){return e.length>12?`${e.slice(0,8)}\u2026`:e}function Ie(e,t){try{let n=Vp(e,t),o=Gp(n.name);if(!o)return{ok:!1,reason:`Not signed in to Document360 (profile "${n.name}"). Run /login first.`};if(Yp(o)&&!o.refreshToken)return{ok:!1,reason:`Your Document360 session expired (profile "${n.name}"). Run /login.`};let r=n.project.projectId;if(r){let l={...xl(o.idToken)??{},...xl(o.accessToken)??{}}.doc360_project_id;if(typeof l=="string"&&l&&l!==r)return{ok:!1,reason:`Your Document360 session is signed into a different project than profile "${n.name}" (this profile's project is ${zp(r)}). Run /login and pick this project.`}}return{ok:!0}}catch{return{ok:!0}}}var En=3;function hs(e){return[`Run the publish-to-d360 skill for the article at: ${e}`,"","Steps you must follow:","1. Read the article markdown \u2014 use the repo-relative path exactly as given above (it resolves against the working directory; do not reconstruct an absolute path).","2. Compute the D360 publish form (strip the YAML frontmatter and the H1 title, strip every <!-- SCREENSHOT ... --> block while keeping the visible [Screenshot: ...] line, normalize cross-article links).","3. Look up the article ID in <repo>/d360-category-map.json. If present, call the document360 MCP update-article tool. If absent, call create-article with local_path set to the repo-relative .md path (the tool records the new ID + sync base in the category map itself \u2014 do not edit the articles map by hand).","4. Report what changed and remind me to publish the draft manually in the Document360 portal."].join(`
314
+ `)}import{resolveActiveProfile as Vp,loadTokens as Gp,isExpired as Yp,decodeJwtClaims as xl}from"document360-engine";function zp(e){return e.length>12?`${e.slice(0,8)}\u2026`:e}function Ie(e,t){try{let n=Vp(e,t),o=Gp(n.name);if(!o)return{ok:!1,reason:`Not signed in to Document360 (profile "${n.name}"). Run /login first.`};if(Yp(o)&&!o.refreshToken)return{ok:!1,reason:`Your Document360 session expired (profile "${n.name}"). Run /login.`};let r=n.project.projectId;if(r){let l={...xl(o.idToken)??{},...xl(o.accessToken)??{}}.doc360_project_id;if(typeof l=="string"&&l&&l!==r)return{ok:!1,reason:`Your Document360 session is signed into a different project than profile "${n.name}" (this profile's project is ${zp(r)}). Run /login and pick this project.`}}return{ok:!0}}catch{return{ok:!0}}}var En=3;function hs(e){return[`Run the publish-to-d360 skill for the article at: ${e}`,"","Steps you must follow:","1. Read the article markdown \u2014 use the repo-relative path exactly as given above (it resolves against the working directory; do not reconstruct an absolute path).","2. Compute the D360 publish form (strip the YAML frontmatter and the H1 title, normalize cross-article links). For each <!-- SCREENSHOT ... --> block, read its id BEFORE stripping the block: when <outputDir>/<id>.png exists (the annotated capture), upload it (d360_search_drive first to reuse, else d360_upload_drive_file) and replace the visible [Screenshot: ...] line with the image markdown; only when no PNG exists does the visible line stay as text.","3. Look up the article ID in <repo>/d360-category-map.json. If present, call the document360 MCP update-article tool. If absent, call create-article with local_path set to the repo-relative .md path (the tool records the new ID + sync base in the category map itself \u2014 do not edit the articles map by hand).","4. Report what changed and remind me to publish the draft manually in the Document360 portal."].join(`
315
315
  `)}var vl={"local-ahead":"modified locally","untracked-local":"new (never published)",conflict:"\u26A0 conflict \u2014 publishing overwrites the portal edit"};function fs(e){return e.filter(t=>t.path!==null&&t.status in vl&&!t.link).map(t=>({path:t.path,label:vl[t.status]}))}function Oo(e){let t=fs(e),n=new Set(e.filter(o=>o.status==="conflict"&&o.path).map(o=>o.path));return{clean:t.filter(o=>!n.has(o.path)),conflicts:t.filter(o=>n.has(o.path))}}function Mo(e){let t=e!==1;return`\u26A0 ${e} conflict${t?"s":""} NOT published (portal edit${t?"s":""} kept safe) \u2014 ${t?"these were":"it was"} edited both here and on the portal, so ${t?"they are":"it is"} left untouched. Settle ${t?"them":"it"}: /resolve (or /publish <path> to keep yours, /pull <path> to take the portal's).`}function Uo(e){return e.filter(t=>t.link).length}function ms(e,t){try{let n=nh(e,oh(e,t).name);if(!n)return null;let o=0;for(let r of Object.keys(n.articles))try{o+=rh(Kp(Xp(e,r),"utf8"),r,n).unresolved}catch{}return o===0?null:`\u2139 ${o} cross-link${o===1?"":"s"} point to pages not published yet \u2014 they show as plain text for now and turn into live links once those pages are published.`}catch{return null}}function sh(e){let t=e.replace(/\\/g,"/").split("/").pop()??e,n=t.replace(/^\d+[-_]/,"").replace(/[-_]+/g," ").trim();return n?n.charAt(0).toUpperCase()+n.slice(1):t}function Tl(e,t=[]){let n=[];e.length>1&&n.push({kind:"all",label:`Publish 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("/"),l=o.get(s);l?l.push(r):o.set(s,[r])}for(let r of[...o.keys()].sort()){let s=o.get(r).sort((l,u)=>l.path.localeCompare(u.path));n.push({kind:"category",label:`${sh(r)} (${s.length})`,paths:s.map(l=>l.path),indent:!1});for(let l of s){let u=l.path.replace(/\\/g,"/").split("/").pop()??l.path;n.push({kind:"article",label:u,paths:[l.path],indent:!0,note:l.label})}}for(let r of[...t].sort((s,l)=>s.path.localeCompare(l.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}function Wo(e){return[`Run the publish-to-d360 skill for ALL of these ${e.length} articles, one by one:`,"",...e.map(t=>`- ${t}`),"","Use the repo-relative paths exactly as listed (they resolve against the working directory; do not reconstruct absolute paths). Apply the normal per-article publish flow (frontmatter/H1/screenshot-comment strips, link normalization; update when mapped, create with local_path when not). Compute each publish form IN MEMORY and pass it straight to the tool \u2014 NEVER write it to disk (no *.publish.md siblings, no temp copies anywhere in the docs tree; the local .md files stay untouched). If one article fails, note it and continue. Finish with a summary table (article | created/updated | failures) and remind me drafts need review in the portal."].join(`
316
316
  `)}function gs(e,t,n){let o=$=>t[$.index]?.paths.length??0,r=e.filter($=>$.ok),s=e.filter($=>!$.ok),l=e.reduce(($,_)=>$+o(_),0),u=r.reduce(($,_)=>$+o(_),0),m=n==="api"?`${xe(e.reduce(($,_)=>$+_.costUsd,0))} total`:at(e.reduce(($,_)=>$+_.outputTokens,0)),y=`${r.length}/${e.length} partition${e.length===1?"":"s"}`,k=$=>`${$} article${$===1?"":"s"}`,P=[];if(s.length===0)P.push(`Published ${k(l)} (${y}) successfully \xB7 ${m}.`),P.push("All are DRAFTS \u2014 review and publish them in the Document360 portal.");else{P.push(`Published ${u}/${k(l)} (${y}) \xB7 ${m}.`),P.push(`${s.length} partition${s.length===1?"":"s"} failed \u2014 re-run /publish --all to retry just the rest (published articles are skipped):`);for(let $ of s)P.push(` \u2717 ${$.label}${$.error?` \u2014 ${$.error}`:""}`)}return P}async function Rt(e,t){let{model:n}=eh(e.cwd,"standard"),o=async(r,s)=>{let l=Jp(r);console.log(g(s?`Resolving cross-links in ${r.length} article${r.length===1?"":"s"} whose targets published this run (second pass)\u2026`:`Publishing ${r.length} article${r.length===1?"":"s"} across ${l.length} partition${l.length===1?"":"s"} (\u2264${En} agents at once) on ${n}\u2026`)),s||console.log(g(" (mid-run abort is TUI-only \u2014 Ctrl+C exits the REPL.)"));let u=!1;for await(let m of th({cwd:e.cwd,partitions:l,promptFor:y=>Wo(y.paths),concurrency:En,profileName:e.profileName,allowProdWrites:e.allowProdWrites(),model:n}))if(m.type==="partition_status")m.status==="running"?console.log(g(` \u25B8 ${m.label} \u2014 publishing\u2026`)):m.status==="done"?console.log(E(` \u2713 ${m.label}`)):console.log(v(` \u2717 ${m.label}`));else if(m.type==="run_done"){u=m.aborted,console.log("");let y=process.env.ANTHROPIC_API_KEY?"api":"subscription";for(let P of gs(m.results,l,y))console.log(m.ok?E(P):O(P));let k=ms(e.cwd,e.profileName);k&&console.log(g(k));try{let P=Rl(await Cl({cwd:e.cwd,profileName:e.profileName}));P&&console.log(g(P))}catch{}try{let P=Pl(await Sl({cwd:e.cwd,profileName:e.profileName}));P&&console.log(g(P))}catch{}}return u};try{if(!await o(t,!1)){let s=await ws(e.cwd,e.profileName,t);s.length>0&&await o(s,!0)}}catch(r){console.log(v(`Publish run failed: ${r.message}`))}return console.log(""),{kind:"continue"}}async function Fo(e,t,n){let o=Zp(e,n);if(o.length===0)return[];let r=new Set(fs((await Lo({cwd:e,profileName:t})).entries).map(s=>s.path));return o.filter(s=>r.has(s))}async function ws(e,t,n){try{let o=new Set(n),r=await Lo({cwd:e,profileName:t});return fs(r.entries).map(s=>s.path).filter(s=>o.has(s))}catch{return[]}}async function Dl(e,t){let n=Ie(t?.cwd??process.cwd(),t?.profileName);if(!n.ok)return console.log(v(n.reason)),{kind:"continue"};let o=e[0];if(o==="--related"&&e[1]){if(!t)return{kind:"continue"};let r=await Fo(t.cwd,t.profileName,e[1].replace(/\\/g,"/"));return r.length===0?(console.log(E("\u2713 All related articles are already published.")),{kind:"continue"}):await Rt(t,r)}if(o==="--all"){console.log(g("Checking what needs publishing\u2026"));try{let r=await Lo({cwd:t?.cwd??process.cwd()}),{clean:s,conflicts:l}=Oo(r.entries),u=Uo(r.entries),m=e.includes("--force")&&l.length>0;u>0&&console.log(O(`\u26A0 ${u} article(s) already exist on Document360 but aren't linked \u2014 left out (publishing would duplicate them). Run /catch-up or ask me to link them first.`)),l.length>0&&!m&&console.log(O(Mo(l.length)));let y=m?[...s,...l]:s;if(y.length===0){if(l.length>0)return console.log(g("Resolve the conflicts: /resolve")),{kind:"continue"};if(console.log(E(u>0?"\u2713 Nothing else is ahead of Document360 to publish.":"\u2713 Nothing is ahead of Document360 \u2014 no publish candidates.")),t)try{let k=Rl(await Cl({cwd:t.cwd,profileName:t.profileName}));k&&console.log(g(k));let P=Pl(await Sl({cwd:t.cwd,profileName:t.profileName}));P&&console.log(g(P))}catch{}return{kind:"continue"}}return t?await Rt(t,y.map(k=>k.path)):{kind:"forward-to-agent",prompt:Wo(y.map(k=>k.path)),display:"/publish --all"}}catch(r){return console.log(v(`Could not compute sync status: ${r.message}`)),{kind:"continue"}}}if(o&&o.startsWith("--")&&(o=e.find(r=>!r.startsWith("--"))??""),!o){console.log(g("Checking what needs publishing\u2026"));let r;try{let s=await Lo({cwd:t?.cwd??process.cwd()}),{clean:l,conflicts:u}=Oo(s.entries);r=l;let m=Uo(s.entries);if(m>0&&console.log(O(`\u26A0 ${m} article(s) already exist on Document360 but aren't linked \u2014 left out (publishing would duplicate them). Run /catch-up or ask me to link them first.`)),u.length>0&&console.log(O(Mo(u.length))),r.length===0&&u.length>0)return console.log(g("Resolve the conflicts: /resolve")),{kind:"continue"}}catch(s){return console.log(v(`Could not compute sync status: ${s.message}`)),console.log(g("Publish a specific article: /publish <article-path>")),{kind:"continue"}}if(r.length===0)return console.log(E("\u2713 Nothing is ahead of Document360 \u2014 no publish candidates.")),console.log(g(" (Articles without a sync base are unverified \u2014 publish those by path if needed.)")),{kind:"continue"};if(!process.stdin.isTTY||!t){for(let s of r)console.log(` ${s.path} ${g(`(${s.label})`)}`);return console.log(g("Run: /publish <article-path>")),{kind:"continue"}}try{o=await t.withPausedInput(()=>Qp({message:"Publish which article?",choices:[...r.length>1?[{name:`All ${r.length} candidates`,value:"--all",description:"one agent run over every candidate"}]:[],...r.map(s=>({name:s.path,value:s.path,description:s.label}))]}))}catch{return console.log(g("Cancelled.")),{kind:"continue"}}if(o==="--all")return await Rt(t,r.map(s=>s.path))}return{kind:"forward-to-agent",prompt:hs(o),display:`/publish ${o}`}}async function Nn(){return{kind:"forward-to-agent",prompt:["Run the gap-analysis skill against this repo.","","Follow its stages in order:","1. d360_sync_status first \u2014 stop and report if any article is in conflict or remote-ahead (the local copy cannot be trusted until resolved).","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 \u2014 the source agent's own notes on what it shipped. Verify each (sources exist, commit is an ancestor of HEAD, source shows the surface) before trusting; fold verified ones into the table, flag the rest.","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 \u2014 backfill them.","4. Output the proposal table: | Article (path or proposed title) | Action (create | update | retire | link) | Reason | Evidence | Scope |, then advance lastAnalyzedCommit and stamp consumed hints.","","Do not start writing or updating any article yet \u2014 the proposal table is the deliverable."].join(`
317
317
  `),display:"/audit"}}q();import{readProjectConfig as ih}from"document360-engine";function ys(){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 \u2014 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 \u2014 every claim cited to its grounding \u2014 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 \u2014 the'," 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(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document360-writer",
3
- "version": "0.5.135",
3
+ "version": "0.5.136",
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.138",
37
+ "document360-engine": "^0.2.140",
38
38
  "ink": "^5.2.1",
39
39
  "picocolors": "^1.1.1",
40
40
  "react": "^18.3.1",