document360-writer 0.5.103 → 0.5.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -310,7 +310,7 @@ ${i}${m}`).join("")}).join(`
|
|
|
310
310
|
`+sp[e.tone](kn(ap(e.text)));case"done":return`
|
|
311
311
|
`+(e.ok?J.magenta("\u2736 "):J.red("\u2736 "))+J.gray(`Cooked for ${xn(e.seconds)} \xB7 ${e.tokens} tokens`+(e.costUsd>0?` \xB7 ${ye(e.costUsd)}`:""))}}function Ya(e,t){return e.map(n=>Kr(n,t)).join(`
|
|
312
312
|
`)}import{resolveActiveProfile as lp,loadTokens as cp,isExpired as dp,decodeJwtClaims as za}from"document360-engine";function up(e){return e.length>12?`${e.slice(0,8)}\u2026`:e}function Ee(e,t){try{let n=lp(e,t),o=cp(n.name);if(!o)return{ok:!1,reason:`Not signed in to Document360 (profile "${n.name}"). Run /login first.`};if(dp(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={...za(o.idToken)??{},...za(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 ${up(r)}). Run /login and pick this project.`}}return{ok:!0}}catch{return{ok:!0}}}var Cn=3;function Xr(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(`
|
|
313
|
-
`)}var Ka={"local-ahead":"modified locally","untracked-local":"new (never published)",conflict:"\u26A0 conflict \u2014 publishing overwrites the portal edit"};function Qr(e){return e.filter(t=>t.path!==null&&t.status in Ka&&!t.link).map(t=>({path:t.path,label:Ka[t.status]}))}function Ro(e){let t=Qr(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 Po(e){return`\u26A0 ${e} conflict${
|
|
313
|
+
`)}var Ka={"local-ahead":"modified locally","untracked-local":"new (never published)",conflict:"\u26A0 conflict \u2014 publishing overwrites the portal edit"};function Qr(e){return e.filter(t=>t.path!==null&&t.status in Ka&&!t.link).map(t=>({path:t.path,label:Ka[t.status]}))}function Ro(e){let t=Qr(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 Po(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 So(e){return e.filter(t=>t.link).length}function Jr(e,t){try{let n=kp(e,bp(e,t).name);if(!n)return null;let o=0;for(let r of Object.keys(n.articles))try{o+=vp(pp(fp(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 $p(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 el(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 i=r.path.replace(/\\/g,"/").split("/").slice(0,-1).join("/"),l=o.get(i);l?l.push(r):o.set(i,[r])}for(let r of[...o.keys()].sort()){let i=o.get(r).sort((l,p)=>l.path.localeCompare(p.path));n.push({kind:"category",label:`${$p(r)} (${i.length})`,paths:i.map(l=>l.path),indent:!1});for(let l of i){let p=l.path.replace(/\\/g,"/").split("/").pop()??l.path;n.push({kind:"article",label:p,paths:[l.path],indent:!0,note:l.label})}}for(let r of[...t].sort((i,l)=>i.path.localeCompare(l.path))){let i=r.path.replace(/\\/g,"/").split("/").pop()??r.path;n.push({kind:"article",label:i,paths:[r.path],indent:!1,note:r.label,conflict:!0})}return n}function To(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). 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(`
|
|
314
314
|
`)}function Zr(e,t,n){let o=v=>t[v.index]?.paths.length??0,r=e.filter(v=>v.ok),i=e.filter(v=>!v.ok),l=e.reduce((v,_)=>v+o(_),0),p=r.reduce((v,_)=>v+o(_),0),m=n==="api"?`${ye(e.reduce((v,_)=>v+_.costUsd,0))} total`:rt(e.reduce((v,_)=>v+_.outputTokens,0)),k=`${r.length}/${e.length} partition${e.length===1?"":"s"}`,g=v=>`${v} article${v===1?"":"s"}`,S=[];if(i.length===0)S.push(`Published ${g(l)} (${k}) successfully \xB7 ${m}.`),S.push("All are DRAFTS \u2014 review and publish them in the Document360 portal.");else{S.push(`Published ${p}/${g(l)} (${k}) \xB7 ${m}.`),S.push(`${i.length} partition${i.length===1?"":"s"} failed \u2014 re-run /publish --all to retry just the rest (published articles are skipped):`);for(let v of i)S.push(` \u2717 ${v.label}${v.error?` \u2014 ${v.error}`:""}`)}return S}async function kt(e,t){let{model:n}=wp(e.cwd,"standard"),o=async(r,i)=>{let l=mp(r);console.log(f(i?`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${Cn} agents at once) on ${n}\u2026`)),i||console.log(f(" (mid-run abort is TUI-only \u2014 Ctrl+C exits the REPL.)"));let p=!1;for await(let m of yp({cwd:e.cwd,partitions:l,promptFor:k=>To(k.paths),concurrency:Cn,profileName:e.profileName,allowProdWrites:e.allowProdWrites(),model:n}))if(m.type==="partition_status")m.status==="running"?console.log(f(` \u25B8 ${m.label} \u2014 publishing\u2026`)):m.status==="done"?console.log(j(` \u2713 ${m.label}`)):console.log(C(` \u2717 ${m.label}`));else if(m.type==="run_done"){p=m.aborted,console.log("");let k=process.env.ANTHROPIC_API_KEY?"api":"subscription";for(let S of Zr(m.results,l,k))console.log(m.ok?j(S):L(S));let g=Jr(e.cwd,e.profileName);g&&console.log(f(g));try{let S=Qa(await Xa({cwd:e.cwd,profileName:e.profileName}));S&&console.log(f(S))}catch{}try{let S=Za(await Ja({cwd:e.cwd,profileName:e.profileName}));S&&console.log(f(S))}catch{}}return p};try{if(!await o(t,!1)){let i=await ei(e.cwd,e.profileName,t);i.length>0&&await o(i,!0)}}catch(r){console.log(C(`Publish run failed: ${r.message}`))}return console.log(""),{kind:"continue"}}async function Do(e,t,n){let o=gp(e,n);if(o.length===0)return[];let r=new Set(Qr((await Co({cwd:e,profileName:t})).entries).map(i=>i.path));return o.filter(i=>r.has(i))}async function ei(e,t,n){try{let o=new Set(n),r=await Co({cwd:e,profileName:t});return Qr(r.entries).map(i=>i.path).filter(i=>o.has(i))}catch{return[]}}async function tl(e,t){let n=Ee(t?.cwd??process.cwd(),t?.profileName);if(!n.ok)return console.log(C(n.reason)),{kind:"continue"};let o=e[0];if(o==="--related"&&e[1]){if(!t)return{kind:"continue"};let r=await Do(t.cwd,t.profileName,e[1].replace(/\\/g,"/"));return r.length===0?(console.log(j("\u2713 All related articles are already published.")),{kind:"continue"}):await kt(t,r)}if(o==="--all"){console.log(f("Checking what needs publishing\u2026"));try{let r=await Co({cwd:t?.cwd??process.cwd()}),{clean:i,conflicts:l}=Ro(r.entries),p=So(r.entries),m=e.includes("--force")&&l.length>0;p>0&&console.log(L(`\u26A0 ${p} 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(L(Po(l.length)));let k=m?[...i,...l]:i;if(k.length===0){if(l.length>0)return console.log(f("Resolve the conflicts: /resolve")),{kind:"continue"};if(console.log(j(p>0?"\u2713 Nothing else is ahead of Document360 to publish.":"\u2713 Nothing is ahead of Document360 \u2014 no publish candidates.")),t)try{let g=Qa(await Xa({cwd:t.cwd,profileName:t.profileName}));g&&console.log(f(g));let S=Za(await Ja({cwd:t.cwd,profileName:t.profileName}));S&&console.log(f(S))}catch{}return{kind:"continue"}}return t?await kt(t,k.map(g=>g.path)):{kind:"forward-to-agent",prompt:To(k.map(g=>g.path)),display:"/publish --all"}}catch(r){return console.log(C(`Could not compute sync status: ${r.message}`)),{kind:"continue"}}}if(o&&o.startsWith("--")&&(o=e.find(r=>!r.startsWith("--"))??""),!o){console.log(f("Checking what needs publishing\u2026"));let r;try{let i=await Co({cwd:t?.cwd??process.cwd()}),{clean:l,conflicts:p}=Ro(i.entries);r=l;let m=So(i.entries);if(m>0&&console.log(L(`\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.`)),p.length>0&&console.log(L(Po(p.length))),r.length===0&&p.length>0)return console.log(f("Resolve the conflicts: /resolve")),{kind:"continue"}}catch(i){return console.log(C(`Could not compute sync status: ${i.message}`)),console.log(f("Publish a specific article: /publish <article-path>")),{kind:"continue"}}if(r.length===0)return console.log(j("\u2713 Nothing is ahead of Document360 \u2014 no publish candidates.")),console.log(f(" (Articles without a sync base are unverified \u2014 publish those by path if needed.)")),{kind:"continue"};if(!process.stdin.isTTY||!t){for(let i of r)console.log(` ${i.path} ${f(`(${i.label})`)}`);return console.log(f("Run: /publish <article-path>")),{kind:"continue"}}try{o=await t.withPausedInput(()=>hp({message:"Publish which article?",choices:[...r.length>1?[{name:`All ${r.length} candidates`,value:"--all",description:"one agent run over every candidate"}]:[],...r.map(i=>({name:i.path,value:i.path,description:i.label}))]}))}catch{return console.log(f("Cancelled.")),{kind:"continue"}}if(o==="--all")return await kt(t,r.map(i=>i.path))}return{kind:"forward-to-agent",prompt:Xr(o),display:`/publish ${o}`}}async function Rn(){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(`
|
|
315
315
|
`),display:"/audit"}}W();import{readProjectConfig as xp}from"document360-engine";function ti(){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(`
|
|
316
316
|
`)}async function nl(e,t){let n=t?.cwd??process.cwd();return xp(n)?{kind:"forward-to-agent",prompt:ti(),display:"/discover"}:(console.log(C("No d360-writer config here. Run /init first.")),{kind:"continue"})}W();import{checkbox as Cp}from"@inquirer/prompts";import{inventoryRepo as Rp,readProjectConfig as ol,writeProjectConfig as Pp}from"document360-engine";function ni(e,t){let n=ol(e);n&&(n.authoritativeSourceFiles=t,Pp(n,e))}function jo(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 rl(e,t){let n=t?.cwd??process.cwd();if(!ol(n))return console.log(C("No .d360-writer/config.json here. Run /init first.")),{kind:"continue"};let o=Rp(n);if(o.length===0)return console.log(f('No candidate source folders found. Set "authoritativeSourceFiles" in .d360-writer/config.json manually.')),{kind:"continue"};if(!process.stdin.isTTY||!t){console.log(""),console.log(O("Recommended documentation scope (run /scope in the REPL to choose):"));for(let i of o)console.log(` ${i.recommended?"\u25C9":"\u25CB"} ${i.path} ${f(jo(i))}`);return console.log(""),{kind:"continue"}}let r;try{r=await t.withPausedInput(()=>Cp({message:"Which folders back the user docs? (space toggles, enter confirms)",choices:o.map(i=>({name:`${i.path} (${jo(i)})`,value:i.path,checked:i.recommended})),pageSize:20}))}catch{return console.log(f("Cancelled.")),{kind:"continue"}}if(r.length===0)return console.log(f("Nothing selected \u2014 scope unchanged.")),{kind:"continue"};ni(n,r),console.log(j(`\u2713 Scoped to ${r.length} folder(s) \u2014 written to .d360-writer/config.json`));for(let i of r)console.log(` ${i}`);return console.log(""),{kind:"continue"}}W();import{confirm as Op}from"@inquirer/prompts";import{applyPull as ii,computeSyncStatus as fl,compareRemoteSources as Lp,planPull as hl,planImport as Mp,applyImport as Up,establishBaselines as Wp,rebaselineServerMeta as Fp,D360AuthError as Hp}from"document360-engine";W();var il=[{status:"conflict",header:"Changed in both places \u2014 pick a direction (/pull <path> or /publish <path>):",paint:C,mark:"!"},{status:"local-ahead",header:"Changed here \u2014 push with /publish <path>:",paint:L,mark:"M"},{status:"remote-ahead",header:"Changed on Document360 \u2014 pull with /pull <path> (or just /pull):",paint:O,mark:"M"},{status:"deleted-local",header:"Removed locally \u2014 still on Document360 (pull to restore, or leave it):",paint:C,mark:"D"},{status:"deleted-remote",header:"Gone from Document360 \u2014 still here (publish to recreate, or delete the file):",paint:C,mark:"D"},{status:"orphaned",header:"Gone from both sides \u2014 these tracking entries point at nothing now:",paint:ae,mark:"X"},{status:"untracked-local",header:"New here \u2014 not on Document360 yet (publish to add them):",paint:f,mark:"?"},{status:"untracked-remote",header:"On Document360 \u2014 not in your local docs yet (run /pull to import them):",paint:f,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:ae,mark:"\xB7"}];function Sp(e){return e.path?e.path:`${e.title??"(untitled)"} ${ae(`[${e.articleId}]`)}`}function Tp(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 Ao(e,t){let n=[],o=e.entries.filter(g=>g.link);if(o.length>0){n.push(""),n.push(we("Already on Document360, just not linked \u2014 ask me to link these (a republish would duplicate them):"));for(let g of o)n.push(` ${L("~")} ${L(g.path??"")} ${ae(`\u2192 "${g.link.title}"`)}`)}for(let g of il){let S=e.entries.filter(v=>v.status===g.status&&!(g.status==="untracked-local"&&v.link));if(S.length!==0){n.push(""),n.push(we(g.header));for(let v of S)n.push(` ${g.paint(g.mark)} ${g.paint(Sp(v))}${v.detail?ae(` (${v.detail})`):""}`)}}let r=e.counts["in-sync"]??0,i=e.entries.length;n.push("");let l={"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"},p=o.length,m=i===r?j("\u2713 everything up to date"):[r>0?j(`\u2713 ${r} up to date`):null,p>0?`${p} to link`:null,...il.map(g=>{let S=(e.counts[g.status]??0)-(g.status==="untracked-local"?p:0);return S>0?`${S} ${l[g.status]}`:null})].filter(Boolean).join(ae(" \xB7 ")),k=t!==void 0?` \xB7 synced in ${Tp(t)}`:"";return n.push(m+ae(` \xB7 ${i} tracked+seen \xB7 profile "${e.profile}" \xB7 docs root ${e.docsRoot}/${k}`)),n}import{structuredPatch as Dp}from"diff";import sl from"picocolors";var al=80,jp="\x1B[48;2;74;28;28m",Ap="\x1B[48;2;24;66;24m",ll="\x1B[49m",oi=e=>String(e).padStart(5);function cl(e,t){let n=Math.min(e.length,t.length),o=0;for(;o<n&&e[o]===t[o];)o++;return o}function dl(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 ze(e,t,n){let o=v=>{let _=v.replace(/\r\n/g,`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document360-writer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.105",
|
|
4
4
|
"description": "Standalone documentation agent CLI. Reads your code, writes your docs. Specialized for Document360 publishing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@inquirer/prompts": "^8.4.3",
|
|
35
35
|
"commander": "^14.0.3",
|
|
36
36
|
"diff": "^8.0.4",
|
|
37
|
-
"document360-engine": "^0.2.
|
|
37
|
+
"document360-engine": "^0.2.118",
|
|
38
38
|
"ink": "^5.2.1",
|
|
39
39
|
"picocolors": "^1.1.1",
|
|
40
40
|
"react": "^18.3.1",
|