document360-engine 0.2.6 → 0.2.8

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/index.d.ts CHANGED
@@ -16,3 +16,4 @@ export * from './scope/inventory.js';
16
16
  export * from './orchestrate/partition.js';
17
17
  export * from './orchestrate/run.js';
18
18
  export * from './cost/estimate.js';
19
+ export * from './lib/modelPolicy.js';
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
- import{readdirSync as ln,readFileSync as Ot,existsSync as $e,statSync as un}from"node:fs";import{join as Ue}from"node:path";import{query as pn}from"@anthropic-ai/claude-agent-sdk";import{existsSync as Jt}from"node:fs";import{isAbsolute as Gt,relative as Vt,resolve as Fe}from"node:path";var We=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function me(e,t,r){let n=Gt(t)?t:Fe(e,t),o=Vt(e,n).replace(/\\/g,"/");return o===""||o.startsWith("..")?!1:/\.(md|markdown)$/i.test(o)||o===".d360-writer.json"||o==="d360-category-map.json"||o===".d360-writer"||o.startsWith(".d360-writer/")?!0:[r?.captureDir,r?.outputDir].filter(i=>typeof i=="string"&&i.length>0).map(i=>i.replace(/\\/g,"/").replace(/\/+$/,"")).some(i=>o===i||o.startsWith(`${i}/`))}var Kt=/^(\/dev\/null|nul|null)$/i,re=e=>e.replace(/^['"]/,"").replace(/['"]$/,""),Yt=new Set(["rm","rmdir","mv","truncate","touch","mkdir","chmod","chown"]),Qt=new Set(["cp","ln","install"]);function He(e,t,r){let n=i=>{let c=re(i);return!c||c.startsWith("&")||c.startsWith("-")||Kt.test(c)?!1:!me(t,c,r)},o=/(?:^|[^0-9>])>>?\s*("[^"]+"|'[^']+'|[^\s|&;<>]+)/g,s;for(;s=o.exec(e);)if(n(s[1]))return re(s[1]);for(let i of e.split(/&&|\|\||[;|]/)){let c=i.trim().split(/\s+/).filter(Boolean);if(c.length===0)continue;let p=c[0].replace(/.*[/\\]/,""),u=c.slice(1),l=u.filter(f=>!f.startsWith("-")),d=[];Yt.has(p)?d=l:Qt.has(p)?d=l.slice(-1):p==="tee"?d=l:p==="dd"?d=u.filter(f=>f.startsWith("of=")).map(f=>f.slice(3)):(p==="sed"||p==="perl")&&u.some(f=>/^-i/.test(f)||f==="--in-place")&&(d=l.filter(f=>Jt(Fe(t,re(f)))));for(let f of d)if(n(f))return re(f)}return null}function ge(e){return`Writer mode: "${e}" is outside the documentation scope (markdown, capture specs, d360 config). Note the needed change for the engineering team instead of making it. Repos that want the agent to modify source code set "mode": "engineer" in .d360-writer.json.`}import{homedir as Zt}from"node:os";import{join as O,dirname as Je}from"node:path";import{fileURLToPath as Xt}from"node:url";import{existsSync as Ge,mkdirSync as er}from"node:fs";var tr=Xt(import.meta.url),ze=Je(tr);function rr(){let e=ze;for(let t=0;t<4;t++){if(Ge(O(e,"package.json")))return e;e=Je(e)}return O(ze,"..","..")}function Ve(){return O(rr(),"skills")}function E(){return O(Zt(),".document360-writer")}function he(){return O(E(),"config.json")}function ye(){return O(E(),"mcp.json")}function we(){return O(E(),"sessions.json")}function ke(e=process.cwd()){return O(e,".d360-writer.json")}function Ke(e=process.cwd()){return O(e,".d360-writer","skills")}function $(e){Ge(e)||er(e,{recursive:!0})}import{readFileSync as ne,writeFileSync as be,existsSync as _e}from"node:fs";import{homedir as nr}from"node:os";import{join as or}from"node:path";var Z=class extends Error{};function Ye(e,t){if(!e?.profiles||Object.keys(e.profiles).length===0)throw new Z("No connection profiles in .d360-writer.json. This version requires a `profiles` map + `defaultProfile` (the old d360.environment/projectId shape is no longer supported). Run `d360-writer init` to scaffold the new shape.");let r=t??e.defaultProfile;if(!r)throw new Z(`No profile specified and no defaultProfile set. Available: ${Object.keys(e.profiles).join(", ")}`);let n=e.profiles[r];if(!n)throw new Z(`Unknown profile "${r}". Available: ${Object.keys(e.profiles).join(", ")}`);return{name:r,profile:n}}function W(e=process.cwd()){let t=ke(e);return _e(t)?JSON.parse(ne(t,"utf8")):null}function Qe(e,t=process.cwd()){be(ke(t),JSON.stringify(e,null,2)+`
2
- `,"utf8")}function ve(){let e=he();if(!_e(e))return{};try{return JSON.parse(ne(e,"utf8"))}catch{return{}}}function so(e){$(E()),be(he(),JSON.stringify(e,null,2)+`
3
- `,"utf8")}function sr(e,t,r,n){return e?{model:e,source:"project"}:t?{model:t,source:"user"}:r?{model:r,source:"env"}:n?{model:n,source:"claude-settings"}:{model:null,source:"claude-default"}}function ir(){try{let e=JSON.parse(ne(or(nr(),".claude","settings.json"),"utf8"));return typeof e.model=="string"&&e.model?e.model:void 0}catch{return}}function io(e=process.cwd()){return sr(W(e)?.defaultModel,ve().defaultModel,process.env.ANTHROPIC_MODEL,ir())}function Ze(){let e=ye();if(!_e(e))return{servers:{}};try{return JSON.parse(ne(e,"utf8"))}catch{return{servers:{}}}}function ao(e){$(E()),be(ye(),JSON.stringify(e,null,2)+`
4
- `,"utf8")}var oe=class{resolvers=[];buffer=[];closed=!1;push(t){if(this.closed)return;let r={type:"user",message:{role:"user",content:t},parent_tool_use_id:null},n=this.resolvers.shift();n?n(r):this.buffer.push(r)}close(){this.closed=!0;for(let t of this.resolvers)t(null);this.resolvers=[]}async*[Symbol.asyncIterator](){for(;;){if(this.buffer.length>0){yield this.buffer.shift();continue}if(this.closed)return;let t=await new Promise(r=>this.resolvers.push(r));if(t===null)return;yield t}}};import{existsSync as nn,readFileSync as Et,statSync as on}from"node:fs";import{basename as sn,isAbsolute as an,resolve as Oe}from"node:path";import{createSdkMcpServer as cn,tool as P}from"@anthropic-ai/claude-agent-sdk";import{z as m}from"zod";import{createHash as ar,randomBytes as Xe}from"node:crypto";import{createServer as cr}from"node:http";import{spawn as Se}from"node:child_process";var et=600*1e3;function Pe(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function lr(e){let t=Pe(Xe(32)),r=Pe(ar("sha256").update(t).digest()),n=Pe(Xe(16)),o=new URL(e.authorizationUrl);return o.searchParams.set("client_id",e.clientId),o.searchParams.set("response_type","code"),o.searchParams.set("redirect_uri",e.redirectUri),o.searchParams.set("scope",e.scopes.join(" ")),o.searchParams.set("state",n),o.searchParams.set("code_challenge",r),o.searchParams.set("code_challenge_method","S256"),e.acrValues&&o.searchParams.set("acr_values",e.acrValues),e.prompt&&o.searchParams.set("prompt",e.prompt),{url:o.toString(),verifier:t,state:n}}function ur(e){if(process.env.D360_NO_BROWSER)return;let t=process.platform;if(t==="win32"){let r=e.replace(/'/g,"''");Se("powershell",["-NoProfile","-NonInteractive","-Command",`Start-Process '${r}'`],{detached:!0,stdio:"ignore"}).unref()}else t==="darwin"?Se("open",[e],{detached:!0,stdio:"ignore"}).unref():Se("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}function pr(e,t){let r=new URL(e),n,o=new Promise((s,i)=>{let c=setTimeout(()=>{i(new Error(`Timed out after ${et/6e4} minutes waiting for the browser login.`)),n.close()},et);n=cr((p,u)=>{let l=new URL(p.url??"/",`http://${r.host}`);if(l.pathname!==r.pathname){u.writeHead(404).end();return}let d=l.searchParams.get("error"),f=l.searchParams.get("code"),y=l.searchParams.get("state"),h=w=>{u.writeHead(400,{"content-type":"text/html"}),u.end(`<html><body><h3>Login failed</h3><p>${w}</p><p>Return to the terminal.</p></body></html>`),clearTimeout(c),i(new Error(w)),n.close()};if(d)return h(`${d}: ${l.searchParams.get("error_description")??""}`);if(!f)return h("No authorization code in the callback.");if(y!==t)return h("State mismatch \u2014 possible CSRF; try again.");u.writeHead(200,{"content-type":"text/html"}),u.end("<html><body><h3>\u2713 Document360 login complete</h3><p>You can close this tab and return to the terminal.</p></body></html>"),clearTimeout(c),s(f),n.close()}),n.on("error",p=>{clearTimeout(c),i(new Error(`Could not listen on ${r.host} (${p.message}). Another process may hold the port \u2014 set D360_REDIRECT_URI to a free port and retry.`))}),n.listen(Number(r.port)||80,r.hostname)});return{server:n,result:o}}async function rt(e,t){let r=await fetch(e.tokenUrl,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t}),n=await r.text();if(!r.ok)throw new Error(`Token endpoint ${r.status}: ${n.slice(0,400)}`);return JSON.parse(n)}async function tt(e,t,r){return rt(e,new URLSearchParams({grant_type:"authorization_code",code:t,redirect_uri:e.redirectUri,client_id:e.clientId,code_verifier:r}))}async function nt(e,t){return rt(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:e.clientId}))}function ot(e,t){let r=Date.now();return{profile:e,accessToken:t.access_token,refreshToken:t.refresh_token,idToken:t.id_token,scope:t.scope,obtainedAt:new Date(r).toISOString(),expiresAt:new Date(r+(t.expires_in??3600)*1e3).toISOString()}}async function mo(e,t,r){let{url:n,verifier:o,state:s}=lr(e);if(t.manual){r("Open this URL in a browser and sign in:"),r(n);let p=await t.promptForRedirect("Paste the full URL you were redirected to (it contains ?code=...):"),u=new URL(p.trim()),l=u.searchParams.get("code"),d=u.searchParams.get("state");if(!l)throw new Error("No code parameter found in the pasted URL.");if(d!==s)throw new Error("State mismatch in the pasted URL \u2014 restart the login.");return tt(e,l,o)}let{result:i}=pr(e.redirectUri,s);r("Opening your browser for Document360 sign-in\u2026"),r(`If it did not open, visit:
5
- ${n}`),ur(n);let c=await i;return tt(e,c,o)}import{existsSync as st,readFileSync as dr,unlinkSync as fr,writeFileSync as mr}from"node:fs";import{join as it}from"node:path";function at(){return it(E(),"auth")}function xe(e){return it(at(),`${e}.json`)}function ct(e){$(at()),mr(xe(e.profile),JSON.stringify(e,null,2))}function se(e){let t=xe(e);if(!st(t))return null;try{return JSON.parse(dr(t,"utf8"))}catch{return null}}function ko(e){let t=xe(e);return st(t)?(fr(t),!0):!1}function Te(e,t=60){return Date.now()>=new Date(e.expiresAt).getTime()-t*1e3}function lt(e){if(!e)return null;let t=e.split(".");if(t.length!==3)return null;try{let r=Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8");return JSON.parse(r)}catch{return null}}function ut(e,t,r){let n=e.get(t);if(n)return n;let o=r().finally(()=>{e.get(t)===o&&e.delete(t)});return e.set(t,o),o}import{appendFileSync as gr,readdirSync as hr,unlinkSync as yr}from"node:fs";import{join as Re}from"node:path";var wr=14,kr=4096;function br(){return Re(E(),"logs")}function _r(e){return e.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/g,"Bearer [redacted]").replace(/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+/g,"[redacted-jwt]")}function vr(e,t=kr){return e.length<=t?e:`${e.slice(0,t)}\u2026 [+${e.length-t} chars]`}function ie(e){return e===void 0?void 0:vr(_r(e))}var Ce=()=>process.env.D360_LOG_BODIES==="1",Sr=/^d360-api-(\d{4}-\d{2}-\d{2})\.jsonl$/;function Pr(e){let t=new Date(Date.now()-wr*24*60*60*1e3).toISOString().slice(0,10);for(let r of hr(e)){let n=r.match(Sr);if(n&&n[1]<t)try{yr(Re(e,r))}catch{}}}var pt=!1;function je(e){try{let t=br();$(t),pt||(pt=!0,Pr(t));let r=Re(t,`d360-api-${e.ts.slice(0,10)}.jsonl`);gr(r,JSON.stringify(e)+`
6
- `,"utf8")}catch{}}var xr=new Map,U=class extends Error{},ae=class extends Error{constructor(r,n,o){super(r);this.status=n;this.requestId=o}status;requestId};async function dt(e){let t=se(e.profile);if(!t)throw new U(`Not logged in to Document360 (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);if(!Te(t))return t.accessToken;if(!t.refreshToken)throw new U(`Document360 session expired (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);return ut(xr,e.profile,async()=>{let r=se(e.profile);if(r&&!Te(r))return r.accessToken;let n=r?.refreshToken??t.refreshToken;try{let o=ot(e.profile,await nt(e.connection,n));return ct(o),o.accessToken}catch(o){throw new U(`Document360 session expired and refresh failed (${o.message.slice(0,120)}). Run /login (or: d360-writer login --profile ${e.profile})`)}})}function G(e,t){if(t)return t;let r=se(e.profile),o=(r?lt(r.accessToken)??{}:{}).doc360_project_id;if(typeof o=="string"&&o)return o;throw new U("No Document360 project resolved. Log in (the project is chosen during login) or set the profile's project.projectId in .d360-writer.json.")}async function ce(e,t,r,n={}){let o=await dt(e),s=new URL(r.startsWith("http")?r:`${e.connection.apiUrl}${r}`);for(let[f,y]of Object.entries(n.query??{}))y!==void 0&&s.searchParams.set(f,String(y));let i=n.body!==void 0?JSON.stringify(n.body):void 0,c=Date.now(),p=f=>je({ts:new Date().toISOString(),profile:e.profile,method:t,url:s.toString(),ms:Date.now()-c,ok:f.ok,status:f.status,requestId:f.requestId,error:f.error,...f.withBodies?{requestBody:ie(i),responseBody:ie(f.responseBody)}:{}}),u;try{u=await fetch(s,{method:t,headers:{authorization:`Bearer ${o}`,accept:"application/json",...i!==void 0?{"content-type":"application/json"}:{}},body:i})}catch(f){throw p({ok:!1,error:`network: ${f.message}`,withBodies:!0}),f}let l=await u.text(),d={};if(l)try{d=JSON.parse(l)}catch{}if(!u.ok||d.success===!1){let f=d.errors?.map(h=>h.message??h.code).filter(Boolean).join("; ")||l.slice(0,300)||u.statusText;if(p({ok:!1,status:u.status,requestId:d.request_id,error:f,responseBody:l,withBodies:!0}),u.status===401)throw new U(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let y=d.request_id?` [request_id: ${d.request_id}]`:"";throw new ae(`Document360 API ${u.status}: ${f}${y}`,u.status,d.request_id)}return p({ok:!0,status:u.status,requestId:d.request_id,responseBody:l,withBodies:Ce()}),d}async function I(e,t,r){return(await ce(e,"GET",t,r)).data}async function V(e,t,r){return(await ce(e,"POST",t,r)).data}async function ft(e,t,r){return(await ce(e,"PATCH",t,r)).data}async function mt(e,t,r){let n=await dt(e),o=`${e.connection.apiUrl}${t}`,s=Date.now(),i=l=>je({ts:new Date().toISOString(),profile:e.profile,method:"POST",url:o,ms:Date.now()-s,ok:l.ok,status:l.status,requestId:l.requestId,error:l.error,...l.withBodies?{requestBody:"[multipart form-data]",responseBody:ie(l.responseBody)}:{}}),c;try{c=await fetch(o,{method:"POST",headers:{authorization:`Bearer ${n}`,accept:"application/json"},body:r})}catch(l){throw i({ok:!1,error:`network: ${l.message}`,withBodies:!0}),l}let p=await c.text(),u={};if(p)try{u=JSON.parse(p)}catch{}if(!c.ok||u.success===!1){let l=u.errors?.map(f=>f.message??f.code).filter(Boolean).join("; ")||p.slice(0,300)||c.statusText;if(i({ok:!1,status:c.status,requestId:u.request_id,error:l,responseBody:p,withBodies:!0}),c.status===401)throw new U(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let d=u.request_id?` [request_id: ${u.request_id}]`:"";throw new ae(`Document360 upload ${c.status}: ${l}${d}`,c.status,u.request_id)}return i({ok:!0,status:c.status,requestId:u.request_id,responseBody:p,withBodies:Ce()}),u.data}async function N(e,t,r={}){let n=[],o;for(let s=0;s<50;s++){let i=await ce(e,"GET",t,{...r,query:{...r.query,cursor:o}});if(Array.isArray(i.data)&&n.push(...i.data),!i.pagination?.has_more||!i.pagination.next_cursor)break;o=i.pagination.next_cursor}return n}async function Eo(e,t){return N(e,`/v3/projects/${t}/workspaces`)}async function Do(e,t,r){return I(e,`/v3/projects/${t}/articles/${r}`,{query:{content_mode:"raw"}})}import{existsSync as Tr,readFileSync as Rr,writeFileSync as Cr}from"node:fs";import{join as jr}from"node:path";function gt(e){return jr(e,"d360-category-map.json")}function ht(e){let t=gt(e);if(!Tr(t))return null;try{return JSON.parse(Rr(t,"utf8"))}catch{return null}}function yt(e){return typeof e=="string"?{id:e}:{...e}}function M(e,t){let n=ht(e)?.[t];if(!n)return null;let o={};for(let[s,i]of Object.entries(n.articles??{}))o[s]=yt(i);return{environment:n.environment,projectId:n.projectId,workspaceId:n.workspaceId,lastAnalyzedCommit:n.lastAnalyzedCommit,categories:{...n.categories??{}},articles:o}}function wt(e,t){for(let[r,n]of Object.entries(e.articles))if(n.id===t)return r;return null}function le(e,t,r,n){let o=ht(e),s=o?.[t];if(!o||!s)return!1;s.articles??={};let i=s.articles[r],c=i!==void 0?yt(i):null;return s.articles[r]={...c??{},...n,lastSyncedAt:new Date().toISOString()},Cr(gt(e),JSON.stringify(o,null,2)+`
1
+ import{readdirSync as un,readFileSync as $t,existsSync as Ue,statSync as dn}from"node:fs";import{join as Ne}from"node:path";import{query as pn}from"@anthropic-ai/claude-agent-sdk";import{existsSync as Jt}from"node:fs";import{isAbsolute as Gt,relative as Vt,resolve as We}from"node:path";var He=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function he(e,t,r){let n=Gt(t)?t:We(e,t),o=Vt(e,n).replace(/\\/g,"/");return o===""||o.startsWith("..")?!1:/\.(md|markdown)$/i.test(o)||o===".d360-writer.json"||o==="d360-category-map.json"||o===".d360-writer"||o.startsWith(".d360-writer/")?!0:[r?.captureDir,r?.outputDir].filter(i=>typeof i=="string"&&i.length>0).map(i=>i.replace(/\\/g,"/").replace(/\/+$/,"")).some(i=>o===i||o.startsWith(`${i}/`))}var Kt=/^(\/dev\/null|nul|null)$/i,oe=e=>e.replace(/^['"]/,"").replace(/['"]$/,""),Yt=new Set(["rm","rmdir","mv","truncate","touch","mkdir","chmod","chown"]),Qt=new Set(["cp","ln","install"]);function ze(e,t,r){let n=i=>{let a=oe(i);return!a||a.startsWith("&")||a.startsWith("-")||Kt.test(a)?!1:!he(t,a,r)},o=/(?:^|[^0-9>])>>?\s*("[^"]+"|'[^']+'|[^\s|&;<>]+)/g,s;for(;s=o.exec(e);)if(n(s[1]))return oe(s[1]);for(let i of e.split(/&&|\|\||[;|]/)){let a=i.trim().split(/\s+/).filter(Boolean);if(a.length===0)continue;let d=a[0].replace(/.*[/\\]/,""),u=a.slice(1),l=u.filter(p=>!p.startsWith("-")),f=[];Yt.has(d)?f=l:Qt.has(d)?f=l.slice(-1):d==="tee"?f=l:d==="dd"?f=u.filter(p=>p.startsWith("of=")).map(p=>p.slice(3)):(d==="sed"||d==="perl")&&u.some(p=>/^-i/.test(p)||p==="--in-place")&&(f=l.filter(p=>Jt(We(t,oe(p)))));for(let p of f)if(n(p))return oe(p)}return null}function ye(e){return`Writer mode: "${e}" is outside the documentation scope (markdown, capture specs, d360 config). Note the needed change for the engineering team instead of making it. Repos that want the agent to modify source code set "mode": "engineer" in .d360-writer.json.`}import{homedir as Zt}from"node:os";import{join as N,dirname as Ge}from"node:path";import{fileURLToPath as Xt}from"node:url";import{existsSync as Ve,mkdirSync as er}from"node:fs";var tr=Xt(import.meta.url),Je=Ge(tr);function rr(){let e=Je;for(let t=0;t<4;t++){if(Ve(N(e,"package.json")))return e;e=Ge(e)}return N(Je,"..","..")}function Ke(){return N(rr(),"skills")}function A(){return N(Zt(),".document360-writer")}function we(){return N(A(),"config.json")}function ke(){return N(A(),"mcp.json")}function be(){return N(A(),"sessions.json")}function _e(e=process.cwd()){return N(e,".d360-writer.json")}function Ye(e=process.cwd()){return N(e,".d360-writer","skills")}function L(e){Ve(e)||er(e,{recursive:!0})}import{readFileSync as se,writeFileSync as ve,existsSync as Se}from"node:fs";import{homedir as nr}from"node:os";import{join as or}from"node:path";var ee=class extends Error{};function Qe(e,t){if(!e?.profiles||Object.keys(e.profiles).length===0)throw new ee("No connection profiles in .d360-writer.json. This version requires a `profiles` map + `defaultProfile` (the old d360.environment/projectId shape is no longer supported). Run `d360-writer init` to scaffold the new shape.");let r=t??e.defaultProfile;if(!r)throw new ee(`No profile specified and no defaultProfile set. Available: ${Object.keys(e.profiles).join(", ")}`);let n=e.profiles[r];if(!n)throw new ee(`Unknown profile "${r}". Available: ${Object.keys(e.profiles).join(", ")}`);return{name:r,profile:n}}function M(e=process.cwd()){let t=_e(e);return Se(t)?JSON.parse(se(t,"utf8")):null}function Ze(e,t=process.cwd()){ve(_e(t),JSON.stringify(e,null,2)+`
2
+ `,"utf8")}function te(){let e=we();if(!Se(e))return{};try{return JSON.parse(se(e,"utf8"))}catch{return{}}}function co(e){L(A()),ve(we(),JSON.stringify(e,null,2)+`
3
+ `,"utf8")}function sr(e,t,r,n){return e?{model:e,source:"project"}:t?{model:t,source:"user"}:r?{model:r,source:"env"}:n?{model:n,source:"claude-settings"}:{model:null,source:"claude-default"}}function ir(){try{let e=JSON.parse(se(or(nr(),".claude","settings.json"),"utf8"));return typeof e.model=="string"&&e.model?e.model:void 0}catch{return}}function lo(e=process.cwd()){return sr(M(e)?.defaultModel,te().defaultModel,process.env.ANTHROPIC_MODEL,ir())}function Xe(){let e=ke();if(!Se(e))return{servers:{}};try{return JSON.parse(se(e,"utf8"))}catch{return{servers:{}}}}function uo(e){L(A()),ve(ke(),JSON.stringify(e,null,2)+`
4
+ `,"utf8")}var ie=class{resolvers=[];buffer=[];closed=!1;push(t){if(this.closed)return;let r={type:"user",message:{role:"user",content:t},parent_tool_use_id:null},n=this.resolvers.shift();n?n(r):this.buffer.push(r)}close(){this.closed=!0;for(let t of this.resolvers)t(null);this.resolvers=[]}async*[Symbol.asyncIterator](){for(;;){if(this.buffer.length>0){yield this.buffer.shift();continue}if(this.closed)return;let t=await new Promise(r=>this.resolvers.push(r));if(t===null)return;yield t}}};import{existsSync as on,readFileSync as Dt,statSync as sn}from"node:fs";import{basename as an,isAbsolute as cn,resolve as $e}from"node:path";import{createSdkMcpServer as ln,tool as S}from"@anthropic-ai/claude-agent-sdk";import{z as m}from"zod";import{createHash as ar,randomBytes as et}from"node:crypto";import{createServer as cr}from"node:http";import{spawn as xe}from"node:child_process";var tt=600*1e3;function Pe(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function lr(e){let t=Pe(et(32)),r=Pe(ar("sha256").update(t).digest()),n=Pe(et(16)),o=new URL(e.authorizationUrl);return o.searchParams.set("client_id",e.clientId),o.searchParams.set("response_type","code"),o.searchParams.set("redirect_uri",e.redirectUri),o.searchParams.set("scope",e.scopes.join(" ")),o.searchParams.set("state",n),o.searchParams.set("code_challenge",r),o.searchParams.set("code_challenge_method","S256"),e.acrValues&&o.searchParams.set("acr_values",e.acrValues),e.prompt&&o.searchParams.set("prompt",e.prompt),{url:o.toString(),verifier:t,state:n}}function ur(e){if(process.env.D360_NO_BROWSER)return;let t=process.platform;if(t==="win32"){let r=e.replace(/'/g,"''");xe("powershell",["-NoProfile","-NonInteractive","-Command",`Start-Process '${r}'`],{detached:!0,stdio:"ignore"}).unref()}else t==="darwin"?xe("open",[e],{detached:!0,stdio:"ignore"}).unref():xe("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}function dr(e,t){let r=new URL(e),n,o=new Promise((s,i)=>{let a=setTimeout(()=>{i(new Error(`Timed out after ${tt/6e4} minutes waiting for the browser login.`)),n.close()},tt);n=cr((d,u)=>{let l=new URL(d.url??"/",`http://${r.host}`);if(l.pathname!==r.pathname){u.writeHead(404).end();return}let f=l.searchParams.get("error"),p=l.searchParams.get("code"),y=l.searchParams.get("state"),b=h=>{u.writeHead(400,{"content-type":"text/html"}),u.end(`<html><body><h3>Login failed</h3><p>${h}</p><p>Return to the terminal.</p></body></html>`),clearTimeout(a),i(new Error(h)),n.close()};if(f)return b(`${f}: ${l.searchParams.get("error_description")??""}`);if(!p)return b("No authorization code in the callback.");if(y!==t)return b("State mismatch \u2014 possible CSRF; try again.");u.writeHead(200,{"content-type":"text/html"}),u.end("<html><body><h3>\u2713 Document360 login complete</h3><p>You can close this tab and return to the terminal.</p></body></html>"),clearTimeout(a),s(p),n.close()}),n.on("error",d=>{clearTimeout(a),i(new Error(`Could not listen on ${r.host} (${d.message}). Another process may hold the port \u2014 set D360_REDIRECT_URI to a free port and retry.`))}),n.listen(Number(r.port)||80,r.hostname)});return{server:n,result:o}}async function nt(e,t){let r=await fetch(e.tokenUrl,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t}),n=await r.text();if(!r.ok)throw new Error(`Token endpoint ${r.status}: ${n.slice(0,400)}`);return JSON.parse(n)}async function rt(e,t,r){return nt(e,new URLSearchParams({grant_type:"authorization_code",code:t,redirect_uri:e.redirectUri,client_id:e.clientId,code_verifier:r}))}async function ot(e,t){return nt(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:e.clientId}))}function st(e,t){let r=Date.now();return{profile:e,accessToken:t.access_token,refreshToken:t.refresh_token,idToken:t.id_token,scope:t.scope,obtainedAt:new Date(r).toISOString(),expiresAt:new Date(r+(t.expires_in??3600)*1e3).toISOString()}}async function yo(e,t,r){let{url:n,verifier:o,state:s}=lr(e);if(t.manual){r("Open this URL in a browser and sign in:"),r(n);let d=await t.promptForRedirect("Paste the full URL you were redirected to (it contains ?code=...):"),u=new URL(d.trim()),l=u.searchParams.get("code"),f=u.searchParams.get("state");if(!l)throw new Error("No code parameter found in the pasted URL.");if(f!==s)throw new Error("State mismatch in the pasted URL \u2014 restart the login.");return rt(e,l,o)}let{result:i}=dr(e.redirectUri,s);r("Opening your browser for Document360 sign-in\u2026"),r(`If it did not open, visit:
5
+ ${n}`),ur(n);let a=await i;return rt(e,a,o)}import{existsSync as it,readFileSync as pr,unlinkSync as fr,writeFileSync as mr}from"node:fs";import{join as at}from"node:path";function ct(){return at(A(),"auth")}function Te(e){return at(ct(),`${e}.json`)}function lt(e){L(ct()),mr(Te(e.profile),JSON.stringify(e,null,2))}function ae(e){let t=Te(e);if(!it(t))return null;try{return JSON.parse(pr(t,"utf8"))}catch{return null}}function vo(e){let t=Te(e);return it(t)?(fr(t),!0):!1}function Re(e,t=60){return Date.now()>=new Date(e.expiresAt).getTime()-t*1e3}function ut(e){if(!e)return null;let t=e.split(".");if(t.length!==3)return null;try{let r=Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8");return JSON.parse(r)}catch{return null}}function dt(e,t,r){let n=e.get(t);if(n)return n;let o=r().finally(()=>{e.get(t)===o&&e.delete(t)});return e.set(t,o),o}import{appendFileSync as gr,readdirSync as hr,unlinkSync as yr}from"node:fs";import{join as Ce}from"node:path";var wr=14,kr=4096;function br(){return Ce(A(),"logs")}function _r(e){return e.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/g,"Bearer [redacted]").replace(/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+/g,"[redacted-jwt]")}function vr(e,t=kr){return e.length<=t?e:`${e.slice(0,t)}\u2026 [+${e.length-t} chars]`}function ce(e){return e===void 0?void 0:vr(_r(e))}var je=()=>process.env.D360_LOG_BODIES==="1",Sr=/^d360-api-(\d{4}-\d{2}-\d{2})\.jsonl$/;function xr(e){let t=new Date(Date.now()-wr*24*60*60*1e3).toISOString().slice(0,10);for(let r of hr(e)){let n=r.match(Sr);if(n&&n[1]<t)try{yr(Ce(e,r))}catch{}}}var pt=!1;function Ee(e){try{let t=br();L(t),pt||(pt=!0,xr(t));let r=Ce(t,`d360-api-${e.ts.slice(0,10)}.jsonl`);gr(r,JSON.stringify(e)+`
6
+ `,"utf8")}catch{}}var Pr=new Map,B=class extends Error{},le=class extends Error{constructor(r,n,o){super(r);this.status=n;this.requestId=o}status;requestId};async function ft(e){let t=ae(e.profile);if(!t)throw new B(`Not logged in to Document360 (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);if(!Re(t))return t.accessToken;if(!t.refreshToken)throw new B(`Document360 session expired (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);return dt(Pr,e.profile,async()=>{let r=ae(e.profile);if(r&&!Re(r))return r.accessToken;let n=r?.refreshToken??t.refreshToken;try{let o=st(e.profile,await ot(e.connection,n));return lt(o),o.accessToken}catch(o){throw new B(`Document360 session expired and refresh failed (${o.message.slice(0,120)}). Run /login (or: d360-writer login --profile ${e.profile})`)}})}function V(e,t){if(t)return t;let r=ae(e.profile),o=(r?ut(r.accessToken)??{}:{}).doc360_project_id;if(typeof o=="string"&&o)return o;throw new B("No Document360 project resolved. Log in (the project is chosen during login) or set the profile's project.projectId in .d360-writer.json.")}async function ue(e,t,r,n={}){let o=await ft(e),s=new URL(r.startsWith("http")?r:`${e.connection.apiUrl}${r}`);for(let[p,y]of Object.entries(n.query??{}))y!==void 0&&s.searchParams.set(p,String(y));let i=n.body!==void 0?JSON.stringify(n.body):void 0,a=Date.now(),d=p=>Ee({ts:new Date().toISOString(),profile:e.profile,method:t,url:s.toString(),ms:Date.now()-a,ok:p.ok,status:p.status,requestId:p.requestId,error:p.error,...p.withBodies?{requestBody:ce(i),responseBody:ce(p.responseBody)}:{}}),u;try{u=await fetch(s,{method:t,headers:{authorization:`Bearer ${o}`,accept:"application/json",...i!==void 0?{"content-type":"application/json"}:{}},body:i})}catch(p){throw d({ok:!1,error:`network: ${p.message}`,withBodies:!0}),p}let l=await u.text(),f={};if(l)try{f=JSON.parse(l)}catch{}if(!u.ok||f.success===!1){let p=f.errors?.map(b=>b.message??b.code).filter(Boolean).join("; ")||l.slice(0,300)||u.statusText;if(d({ok:!1,status:u.status,requestId:f.request_id,error:p,responseBody:l,withBodies:!0}),u.status===401)throw new B(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let y=f.request_id?` [request_id: ${f.request_id}]`:"";throw new le(`Document360 API ${u.status}: ${p}${y}`,u.status,f.request_id)}return d({ok:!0,status:u.status,requestId:f.request_id,responseBody:l,withBodies:je()}),f}async function O(e,t,r){return(await ue(e,"GET",t,r)).data}async function K(e,t,r){return(await ue(e,"POST",t,r)).data}async function mt(e,t,r){return(await ue(e,"PATCH",t,r)).data}async function gt(e,t,r){let n=await ft(e),o=`${e.connection.apiUrl}${t}`,s=Date.now(),i=l=>Ee({ts:new Date().toISOString(),profile:e.profile,method:"POST",url:o,ms:Date.now()-s,ok:l.ok,status:l.status,requestId:l.requestId,error:l.error,...l.withBodies?{requestBody:"[multipart form-data]",responseBody:ce(l.responseBody)}:{}}),a;try{a=await fetch(o,{method:"POST",headers:{authorization:`Bearer ${n}`,accept:"application/json"},body:r})}catch(l){throw i({ok:!1,error:`network: ${l.message}`,withBodies:!0}),l}let d=await a.text(),u={};if(d)try{u=JSON.parse(d)}catch{}if(!a.ok||u.success===!1){let l=u.errors?.map(p=>p.message??p.code).filter(Boolean).join("; ")||d.slice(0,300)||a.statusText;if(i({ok:!1,status:a.status,requestId:u.request_id,error:l,responseBody:d,withBodies:!0}),a.status===401)throw new B(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let f=u.request_id?` [request_id: ${u.request_id}]`:"";throw new le(`Document360 upload ${a.status}: ${l}${f}`,a.status,u.request_id)}return i({ok:!0,status:a.status,requestId:u.request_id,responseBody:d,withBodies:je()}),u.data}async function q(e,t,r={}){let n=[],o;for(let s=0;s<50;s++){let i=await ue(e,"GET",t,{...r,query:{...r.query,cursor:o}});if(Array.isArray(i.data)&&n.push(...i.data),!i.pagination?.has_more||!i.pagination.next_cursor)break;o=i.pagination.next_cursor}return n}async function Io(e,t){return q(e,`/v3/projects/${t}/workspaces`)}async function Mo(e,t,r){return O(e,`/v3/projects/${t}/articles/${r}`,{query:{content_mode:"raw"}})}import{existsSync as Tr,readFileSync as Rr,writeFileSync as Cr}from"node:fs";import{join as jr}from"node:path";function ht(e){return jr(e,"d360-category-map.json")}function yt(e){let t=ht(e);if(!Tr(t))return null;try{return JSON.parse(Rr(t,"utf8"))}catch{return null}}function wt(e){return typeof e=="string"?{id:e}:{...e}}function $(e,t){let n=yt(e)?.[t];if(!n)return null;let o={};for(let[s,i]of Object.entries(n.articles??{}))o[s]=wt(i);return{environment:n.environment,projectId:n.projectId,workspaceId:n.workspaceId,lastAnalyzedCommit:n.lastAnalyzedCommit,categories:{...n.categories??{}},articles:o}}function kt(e,t){for(let[r,n]of Object.entries(e.articles))if(n.id===t)return r;return null}function de(e,t,r,n){let o=yt(e),s=o?.[t];if(!o||!s)return!1;s.articles??={};let i=s.articles[r],a=i!==void 0?wt(i):null;return s.articles[r]={...a??{},...n,lastSyncedAt:new Date().toISOString()},Cr(ht(e),JSON.stringify(o,null,2)+`
7
7
  `,"utf8"),!0}import{createHash as Er}from"node:crypto";function Dr(e){let t=e.replace(/^\uFEFF/,"").replace(/\r\n/g,`
8
8
  `);return t=t.replace(/\n*$/,""),t===""?"":t+`
9
- `}function H(e){return"sha256:"+Er("sha256").update(Dr(e),"utf8").digest("hex")}function K(e,t){return H(`${e??""}
10
- ${t??""}`)}import{existsSync as Mr,readdirSync as Or,readFileSync as $r,statSync as Ur}from"node:fs";import{join as Ee}from"node:path";var kt={berlin:{apiUrl:"https://apihub.berlin.document360.net",portalUrl:"https://portal.berlin.document360.net",authorizationUrl:"https://identity.berlin.document360.net/connect/authorize",tokenUrl:"https://identity.berlin.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"},sharjah:{apiUrl:"https://apihub.sharjah.document360.net",portalUrl:"https://portal.sharjah.document360.net",authorizationUrl:"https://identity.sharjah.document360.net/connect/authorize",tokenUrl:"https://identity.sharjah.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"}};function Ar(){return Object.keys(kt)}function No(e="berlin"){return ue({environment:e})}function ue(e){let t=e.environment??"berlin",r=kt[t];if(!r)throw new Error(`Unknown Document360 environment "${t}". Known: ${Ar().join(", ")}`);let n=process.env.D360_SCOPES?.split(/[\s,]+/).filter(Boolean);return{name:t,apiUrl:process.env.D360_API_URL??e.apiUrl??r.apiUrl,portalUrl:process.env.D360_PORTAL_URL??e.portalUrl??r.portalUrl,authorizationUrl:process.env.D360_AUTHORIZATION_URL??e.authorizationUrl??r.authorizationUrl,tokenUrl:process.env.D360_TOKEN_URL??e.tokenUrl??r.tokenUrl,clientId:process.env.D360_CLIENT_ID??e.clientId??r.clientId,scopes:n??e.scopes??r.scopes,acrValues:process.env.D360_ACR_VALUES??e.acrValues??r.acrValues,redirectUri:process.env.D360_REDIRECT_URI??e.redirectUri??r.redirectUri,prompt:process.env.D360_PROMPT??e.prompt??r.prompt}}function B(e,t){let r=W(e);if(r===null){let s=t??"berlin";return{name:s,connection:ue({environment:s}),project:{},production:!1}}let{name:n,profile:o}=Ye(r,t);return{name:n,connection:ue(o.connection),project:o.project??{},production:o.production===!0}}function Fo(e,t,r){let n=W(e);n?.profiles?.[t]&&(n.profiles[t].project={...n.profiles[t].project,...r},Qe(n,e))}function bt(e,t){if(e?.docsDir)return e.docsDir.replace(/\\/g,"/").replace(/\/+$/,"");let r=new Set(Object.keys(t?.articles??{}).map(n=>n.split("/")[0]).filter(Boolean));if(r.size===1)return[...r][0];throw new Error(r.size===0?'Cannot locate the docs folder: no mapped articles yet. Set "docsDir" in .d360-writer.json.':`Cannot locate the docs folder: mapped articles span multiple roots (${[...r].join(", ")}). Set "docsDir" in .d360-writer.json.`)}var Ir=4;function _t(e,t,r,n={getAll:N,get:I}){let o=`/v3/projects/${t}`;return{async fetch(s){let[i,c]=await Promise.all([n.getAll(e,`${o}/workspaces/${r}/articles`),n.getAll(e,`${o}/workspaces/${r}/categories`)]),p=new Map;for(let y of i)p.set(y.id,{id:y.id,title:y.title,categoryId:y.category_id,hidden:y.hidden});let u=new Map;for(let y of c)u.set(y.id,{id:y.id,name:y.name,parentId:y.parent_category_id});let l=s.filter(y=>p.has(y)),d=0,f=async()=>{for(;d<l.length;){let y=l[d++],h=await n.get(e,`${o}/articles/${y}`,{query:{content_mode:"raw"}}),w=p.get(y);w.title=h.title??w.title,w.content=h.content,w.latestVersion=h.latest_version,w.modifiedAt=h.modified_at,w.contentHash=K(h.title??w.title,h.content)}};return await Promise.all(Array.from({length:Math.min(Ir,l.length)},f)),{articles:p,categories:u}}}}function Nr(e){if(!e.mapped)return e.localExists?"untracked-local":"untracked-remote";if(!e.localExists&&!e.remoteExists)return"orphaned";if(!e.localExists)return"deleted-local";if(!e.remoteExists)return"deleted-remote";if(!e.hasBase)return"unknown-base";let t=e.localHashNow!==e.baseLocalHash,r=e.remoteContentHashNow!==e.baseRemoteContentHash;return t&&r?"conflict":t?"local-ahead":r?"remote-ahead":"in-sync"}function Br(e,t){let r=Ee(e,t);if(!Mr(r))return[];let n=[],o=(s,i)=>{for(let c of Or(s)){let p=Ee(s,c),u=i?`${i}/${c}`:c;Ur(p).isDirectory()?o(p,u):c.endsWith(".md")&&n.push(u)}};return o(r,t.replace(/\\/g,"/")),n}function Lr(e,t){let r=new Set(Object.values(e.categories)),n=new Map;for(let i of t.categories.values()){if(!i.parentId)continue;let c=n.get(i.parentId)??[];c.push(i.id),n.set(i.parentId,c)}let o=new Set,s=[...r];for(;s.length>0;){let i=s.shift();o.has(i)||(o.add(i),s.push(...n.get(i)??[]))}return o}async function vt(e){let t=B(e.cwd,e.profileName),r=W(e.cwd),n=M(e.cwd,t.name);if(!n)throw new Error(`No d360-category-map.json section for profile "${t.name}". Publish at least one article first (/publish).`);let o={profile:t.name,connection:t.connection},s=n.projectId??t.project.projectId??G(o),i=n.workspaceId??t.project.workspaceId;if(!i)throw new Error(`No workspace recorded for profile "${t.name}". Run /workspace to select one.`);let c=bt(r,n),p=Object.values(n.articles).map(v=>v.id),l=await(e.provider??_t(o,s,i)).fetch(p),d=new Set(Br(e.cwd,c)),f=[];for(let[v,D]of Object.entries(n.articles)){let z=d.has(v);d.delete(v);let R=l.articles.get(D.id),L=!!(D.localHash&&D.remoteContentHash),q=Nr({mapped:!0,localExists:z,remoteExists:R!==void 0,hasBase:L,localHashNow:z?H($r(Ee(e.cwd,v),"utf8")):void 0,baseLocalHash:D.localHash,remoteContentHashNow:R?.contentHash,baseRemoteContentHash:D.remoteContentHash}),S=[];if(q==="remote-ahead"||q==="conflict"){let te=R?.modifiedAt?.slice(0,10);S.push(`remote edited${te?` ${te}`:""}${R?.latestVersion?` (v${R.latestVersion})`:""}`)}R?.hidden&&S.push("hidden on D360"),f.push({path:v,articleId:D.id,title:R?.title,status:q,detail:S.length>0?S.join("; "):void 0})}for(let v of[...d].sort())f.push({path:v,articleId:null,status:"untracked-local"});let y=new Set(p),h=Lr(n,l);for(let v of l.articles.values())y.has(v.id)||!v.categoryId||!h.has(v.categoryId)||f.push({path:null,articleId:v.id,title:v.title,status:"untracked-remote",detail:v.hidden?"hidden on D360":void 0});let w={};for(let v of f)w[v.status]=(w[v.status]??0)+1;return{profile:t.name,projectId:s,workspaceId:i,docsRoot:c,entries:f,counts:w,generatedAt:new Date().toISOString()}}import{posix as De}from"node:path";var qr=/\[([^\]]+)\]\(([^)\s]+)\)/g;function Ae(e,t,r){if(!r)return{content:e,resolved:0,unresolved:0};let n=De.dirname(t.replace(/\\/g,"/")),o=0,s=0;return{content:e.replace(qr,(c,p,u)=>{if(/^(https?:|mailto:|#|\/)/i.test(u)||!/\.(md|markdown)(#[^)]*)?$/i.test(u))return c;let l=u.indexOf("#"),d=l===-1?u:u.slice(0,l),f=l===-1?"":u.slice(l),y=De.normalize(De.join(n,d)).replace(/^\.\//,""),h=r.articles[y]?.url;return h?(o++,`[${p}](${h}${f})`):(s++,p)}),resolved:o,unresolved:s}}import{readdirSync as Fr}from"node:fs";import{join as pe}from"node:path";var Wr=new Set([".git","node_modules","dist","build","bin","obj","out","target",".vs",".vscode",".idea",".next","coverage",".turbo",".cache","packages"]),Hr=40,zr=3,Ie=5e3,Jr=/\.(csproj|vbproj|fsproj)$|^(package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|pom\.xml|build\.gradle(\.kts)?)$/i,Gr=/\.sln$|^(Directory\.(Build|Packages)\.props|global\.json|go\.work)$/i;function Vr(e){return/\.(csproj|vbproj|fsproj|sln)$/i.test(e)||/^(Directory\.(Build|Packages)\.props|global\.json)$/i.test(e)?".NET":/^(package\.json|angular\.json|tsconfig\.json)$/i.test(e)?"JS/TS":/^(pyproject\.toml|requirements\.txt|setup\.py)$/i.test(e)?"Python":/^(go\.mod|go\.work)$/i.test(e)?"Go":/^Cargo\.toml$/i.test(e)?"Rust":/^(pom\.xml|build\.gradle(\.kts)?)$/i.test(e)?"Java":null}var Kr=/(?:^|[.\-_])(web|portal|ui|app|apps|client|site|widget|admin|dashboard|frontend|api)(?:$|[.\-_])/i,Yr=/(?:^|[.\-_])(tests?|specs?|helm|docker|migrations?|functions?|ci|builds?|scripts|tools|proxy|coverage|release|libs?|redis|signalr|websocket|dataaccess|nuget|sdk|infra|infrastructure|deploy|deployment|e2e)(?:$|[.\-_])/i;function Me(e){let t=[],r=[];try{for(let n of Fr(e,{withFileTypes:!0}))n.isDirectory()?!n.name.startsWith(".")&&!Wr.has(n.name)&&t.push(n.name):n.isFile()&&r.push(n.name)}catch{}return{dirs:t,files:r}}function Qr(e){let t=new Set;for(let r of e){let n=Vr(r);n&&t.add(n)}return t}var St=e=>e.some(t=>Jr.test(t)),Zr=e=>e.some(t=>Gr.test(t));function Xr(e){let t=0,r=n=>{if(t>=Ie)return;let{dirs:o,files:s}=Me(n);t+=s.length;for(let i of o){if(t>=Ie)return;r(pe(n,i))}};return r(e),Math.min(t,Ie)}function en(e,t){let r=e.split("/").pop()??e,n=t.size?[...t].join("+"):"";return Yr.test(r)?{recommended:!1,reason:`tests/infrastructure${n?` \xB7 ${n}`:""}`}:Kr.test(r)?{recommended:!0,reason:`user-facing surface${n?` \xB7 ${n}`:""}`}:{recommended:!1,reason:n?`${n} project`:"source folder"}}function Pt(e){let t=[],r=(s,i)=>{let c=Qr(i.files),{recommended:p,reason:u}=en(s,c);t.push({path:s,fileCount:Xr(pe(e,s)),stacks:[...c],recommended:p,reason:u})},n=(s,i)=>{let c=s?pe(e,s):e,p=Me(c);if(s&&St(p.files)){r(s,p);return}if(s&&p.dirs.length===0){r(s,p);return}let u=d=>St(Me(pe(c,d)).files);if((Zr(p.files)||p.dirs.some(u)||s==="")&&i<zr){for(let d of p.dirs)n(s?`${s}/${d}`:d,i+1);return}s&&r(s,p)};n("",0),t.sort((s,i)=>Number(i.recommended)-Number(s.recommended)||s.path.localeCompare(i.path));let o=t.filter(s=>s.recommended);return t.slice(0,Math.max(Hr,o.length))}var X={"claude-fable-5":{inputPerMTok:10,outputPerMTok:50},"claude-opus-4-8":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-7":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-6":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-5":{inputPerMTok:5,outputPerMTok:25},"claude-sonnet-4-6":{inputPerMTok:3,outputPerMTok:15},"claude-sonnet-4-5":{inputPerMTok:3,outputPerMTok:15},"claude-haiku-4-5":{inputPerMTok:1,outputPerMTok:5}},xt="claude-opus-4-8";function Tt(e){if(e&&X[e])return{model:e,rate:X[e],assumed:!1};if(e){let t=Object.keys(X).filter(r=>e.startsWith(r)).sort((r,n)=>n.length-r.length)[0];if(t)return{model:t,rate:X[t],assumed:!1}}return{model:xt,rate:X[xt],assumed:!0}}function ee(e,t){return e/1e6*t}var tn=3.5,rn={convert:{in:1.3,out:1.1,outFloor:0},rewrite:{in:1.4,out:1.2,outFloor:0},publish:{in:1.1,out:.2,outFloor:200},audit:{in:1.2,out:.15,outFloor:0}},Rt=.7,Ct=1.5,de=e=>Math.round(e);function jt(e){let{op:t,model:r}=e,n=rn[t],{model:o,rate:s,assumed:i}=Tt(r),c=e.files.filter(w=>!(w.bytes>0)).map(w=>w.path),p=e.files.filter(w=>w.bytes>0),u=0,l=0;for(let w of p){let v=w.bytes/tn;u+=v*n.in,l+=Math.max(v*n.out,n.outFloor)}let d=[de(u*Rt),de(u*Ct)],f=[de(l*Rt),de(l*Ct)],y=[ee(d[0],s.inputPerMTok)+ee(f[0],s.outputPerMTok),ee(d[1],s.inputPerMTok)+ee(f[1],s.outputPerMTok)],h=`Rough estimate from file sizes, priced at ${o} list rates`+(i?" (model unset \u2014 assumed)":"")+". Actuals vary with cache reuse and reasoning depth; the live cost meter reconciles as the run proceeds.";return{model:o,modelAssumed:i,op:t,articles:p.length,skipped:c,inputTokens:d,outputTokens:f,usd:y,note:h}}var Dt="/v3/projects",T=e=>`${Dt}/${e}`;function x(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)??'{"success":true}'}]}}function _(e){return{content:[{type:"text",text:e instanceof Error?e.message:String(e)}],isError:!0}}function At(e,t){let r={profile:e.name,connection:e.connection},n=e.project.projectId,o=e.project.workspaceId,s=()=>G(r,n),i=a=>{let g=a??o;if(!g)throw new Error("No workspace_id given and none in the profile. Call d360_list_workspaces first.");return g},c=(a,g)=>{if(t.cwd)try{let k=Oe(t.cwd,a);le(t.cwd,e.name,a.replace(/\\/g,"/"),{...g,...nn(k)?{localHash:H(Et(k,"utf8"))}:{}})}catch{}},p=()=>t.writesAllowed?null:_(`Refusing to write to PRODUCTION profile "${e.name}". Authorize this session first: run /allow-prod in the REPL, or pass --yes in one-shot/CI.`),u=P("d360_context","Report the active Document360 connection: profile name, environment, project/workspace id, and whether it is a production profile. Call this before maintaining d360-category-map.json so you scope IDs to the right profile.",{},async()=>{try{return x({profile:e.name,environment:e.connection.name,production:e.production,projectId:n??G(r),workspaceId:o??null})}catch(a){return _(a)}}),l=P("d360_list_projects","List all Document360 projects the signed-in user can access (id, name, sub-domain, status).",{},async()=>{try{return x(await N(r,Dt))}catch(a){return _(a)}}),d=P("d360_list_workspaces","List workspaces (project versions) for the current project. Each has id, name, slug, is_default, workspace_type.",{project_id:m.string().optional().describe("Defaults to the logged-in/config project.")},async a=>{try{return x(await N(r,`${T(a.project_id??s())}/workspaces`))}catch(g){return _(g)}}),f=P("d360_list_categories","List categories in a workspace (the folder structure for articles).",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async a=>{try{let g=a.workspace_id??o;return g?x(await N(r,`${T(a.project_id??s())}/workspaces/${g}/categories`)):_("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return _(g)}}),y=P("d360_list_articles","List articles in a workspace (id, title, status, category). Use to see existing docs before writing.",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async a=>{try{let g=a.workspace_id??o;return g?x(await N(r,`${T(a.project_id??s())}/workspaces/${g}/articles`)):_("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return _(g)}}),h=P("d360_get_article","Get a single article including its content. content_mode=raw returns the stored markdown source (best for editing); display returns processed content.",{article_id:m.string(),content_mode:m.enum(["raw","display"]).optional().describe("raw = stored markdown source (default), display = processed."),published:m.boolean().optional().describe("Read the published version instead of the latest draft."),project_id:m.string().optional()},async a=>{try{return x(await I(r,`${T(a.project_id??s())}/articles/${a.article_id}`,{query:{content_mode:a.content_mode,published:a.published}}))}catch(g){return _(g)}}),w=P("d360_ai_query","Ask Document360's AI search over a workspace's published content. Returns an answer grounded in existing articles \u2014 use to check what's already documented.",{query:m.string(),workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async a=>{try{let g=a.workspace_id??o;return g?x(await V(r,`${T(a.project_id??s())}/workspaces/ai/query`,{body:{query:a.query,workspace_id:g}})):_("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return _(g)}}),v=P("d360_sync_status","Deterministic drift report: local docs tree vs Document360, classified against the sync bases in d360-category-map.json. Call this BEFORE any docs gap/coverage analysis \u2014 it proves whether the local docs tree can be trusted as the Document360 inventory (no need to fetch article content). Statuses: local-ahead (push pending), remote-ahead (portal edit to pull), conflict, untracked-local/remote, deleted-local/remote, unknown-base (no base recorded yet).",{},async()=>{try{if(!t.cwd)return _("Sync status needs a repo context (no cwd configured for this session).");let a=await vt({cwd:t.cwd,profileName:e.name});return x({profile:a.profile,docsRoot:a.docsRoot,generatedAt:a.generatedAt,counts:a.counts,attention:a.entries.filter(g=>g.status!=="in-sync")})}catch(a){return _(a)}}),D=P("d360_repo_inventory","Deterministic inventory of candidate source folders for documentation scope (no file reads of your own needed). Returns project-level folders with: path, fileCount, detected stacks, a `recommended` flag (user-facing surfaces pre-recommended; tests/infrastructure not), and a one-line reason. Use this when analyzing a large/multi-project repo to decide which folders back the docs \u2014 it is the same data the /scope picker pre-ticks, so your recommendation and the picker agree. Cheap; safe to call before reading any source.",{},async()=>{try{return t.cwd?x(Pt(t.cwd)):_("Repo inventory needs a repo context (no cwd configured for this session).")}catch(a){return _(a)}}),z=P("d360_estimate_cost",'Deterministic, no-network token/cost ESTIMATE for a bulk operation. Call this BEFORE you propose a repo-scale run (convert / rewrite / publish / audit MANY articles) so you can show the user the article count and a cost band and get an explicit go-ahead \u2014 see the "Confirm before bulk or irreversible actions" rule. It sizes work from local file BYTES (coarse \u2014 returns [low, high] bands, not a quote). Omit `paths` to estimate over every article tracked in d360-category-map.json; pass `paths` for a subset. Cheap and read-only; safe to call before any write.',{op:m.enum(["convert","rewrite","publish","audit"]).describe("What you intend to do to each article (drives the output-size heuristic)."),paths:m.array(m.string()).optional().describe("Repo-relative .md paths. Omit or leave empty to estimate over all articles in the category map.")},async({op:a,paths:g})=>{try{if(!t.cwd)return _("Cost estimate needs a repo context (no cwd configured for this session).");let k;if(g&&g.length>0)k=g.map(b=>b.replace(/\\/g,"/"));else{let b=M(t.cwd,e.name);if(k=b?Object.keys(b.articles):[],k.length===0)return _("No articles are tracked in d360-category-map.json for this profile, and no `paths` were given. Pass `paths` explicitly (the repo-relative .md files you intend to operate on).")}let C=k.map(b=>{let j=0;try{j=on(Oe(t.cwd,b)).size}catch{j=0}return{path:b,bytes:j}});return x(jt({files:C,op:a,model:t.model}))}catch(k){return _(k)}}),R=P("d360_create_category","Create a category (a docs folder). Returns the new category id.",{name:m.string(),workspace_id:m.string().optional(),parent_category_id:m.string().optional().describe("Omit for a top-level category."),content:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional()},async a=>{let g=p();if(g)return g;try{return x(await V(r,`${T(a.project_id??s())}/categories`,{body:{name:a.name,workspace_id:i(a.workspace_id),parent_category_id:a.parent_category_id,content:a.content,slug:a.slug,order:a.order,content_type:"markdown"}}))}catch(k){return _(k)}}),L=P("d360_create_article","Create a DRAFT article in a category. The body is always Markdown (product rule \u2014 we never create WYSIWYG/Block articles). Returns the new article id. Does not publish.",{title:m.string(),category_id:m.string(),content:m.string().optional().describe("Markdown body."),workspace_id:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional(),local_path:m.string().optional().describe("Repo-relative path of the local .md this article mirrors (e.g. user-docs/01-intro/01-a.md). When given, the new article id + sync base are recorded into d360-category-map.json automatically \u2014 do not edit the articles map yourself.")},async a=>{let g=p();if(g)return g;try{let k=a.project_id??s(),C=a.content;typeof C=="string"&&a.local_path&&t.cwd&&(C=Ae(C,a.local_path,M(t.cwd,e.name)).content);let b=await V(r,`${T(k)}/articles/bulk`,{body:{articles:[{title:a.title,category_id:a.category_id,workspace_id:i(a.workspace_id),content:C,slug:a.slug,order:a.order,content_type:"markdown"}]}}),j=Array.isArray(b)?b[0]:void 0;if(a.local_path&&typeof j?.id=="string"){let A;try{let F=await I(r,`${T(k)}/articles/${j.id}`,{});A=typeof F?.url=="string"?F.url:void 0}catch{}c(a.local_path,{id:j.id,remoteContentHash:K(a.title,C),remoteVersion:1,...A?{url:A}:{}})}return x(b)}catch(k){return _(k)}}),q=P("d360_update_article","Update an article's title/content/category. Edits the latest draft by default; set auto_fork to safely edit a published article (creates a new draft version).",{article_id:m.string(),title:m.string().optional(),content:m.string().optional().describe("Markdown body."),category_id:m.string().optional(),hidden:m.boolean().optional(),version_number:m.number().optional(),auto_fork:m.boolean().optional().describe("If the target version is published, fork a new draft instead of erroring."),project_id:m.string().optional()},async a=>{let g=p();if(g)return g;try{let{article_id:k,project_id:C,...b}=a,j=C??s(),A=typeof b.content=="string"&&t.cwd?M(t.cwd,e.name):null,F=A?wt(A,k):null;typeof b.content=="string"&&F&&A&&(b.content=Ae(b.content,F,A).content);let J=null;if(typeof b.content=="string"&&t.onUiEvent)try{J=await I(r,`${T(j)}/articles/${k}`,{query:{content_mode:"raw"}})}catch{}let Be=e.project.languageCode??J?.lang_code,Le=await ft(r,`${T(j)}/articles/${k}`,{body:b,...Be?{query:{lang_code:Be}}:{}});if(J&&typeof b.content=="string"&&(J.content??"")!==b.content&&t.onUiEvent?.({type:"article_diff",articleId:k,title:J.title??null,oldContent:J.content??"",newContent:b.content}),typeof b.content=="string"&&F)try{let Q=Le,qe=b.title??Q?.title??J?.title;typeof qe=="string"&&c(F,{id:k,remoteContentHash:K(qe,b.content),...typeof Q?.latest_version=="number"?{remoteVersion:Q.latest_version}:{},...typeof Q?.modified_at=="string"?{remoteModifiedAt:Q.modified_at}:{}})}catch{}return x(Le)}catch(k){return _(k)}}),S=P("d360_fork_article","Fork a published article into a new draft version \u2014 the safe way to start editing live content.",{article_id:m.string(),project_id:m.string().optional()},async a=>{let g=p();if(g)return g;try{return x(await V(r,`${T(a.project_id??s())}/articles/${a.article_id}/fork`))}catch(k){return _(k)}}),te=P("d360_publish_article","Publish an article version. This makes the draft live to readers \u2014 only call when the user explicitly asks to publish.",{article_id:m.string(),version_number:m.number(),workspace_id:m.string().optional(),message:m.string().optional(),project_id:m.string().optional()},async a=>{let g=p();if(g)return g;try{return x(await V(r,`${T(a.project_id??s())}/articles/${a.article_id}/publish`,{body:{workspace_id:i(a.workspace_id),version_number:a.version_number,message:a.message}}))}catch(k){return _(k)}}),Wt=P("d360_unpublish_article","Unpublish an article version, reverting it to draft (removes it from readers).",{article_id:m.string(),version_number:m.number().describe("The published version to unpublish (see d360_get_article)."),workspace_id:m.string().optional(),project_id:m.string().optional()},async a=>{let g=p();if(g)return g;try{return x(await V(r,`${T(a.project_id??s())}/articles/${a.article_id}/unpublish`,{body:{workspace_id:i(a.workspace_id),version_number:a.version_number}}))}catch(k){return _(k)}}),Ht=P("d360_upload_drive_file","Upload a local file (e.g. a captured screenshot PNG) to Drive and return its URL for embedding in an article. Uploads to the default folder unless folder_id is given.",{file_path:m.string().describe("Local path to the file (absolute, or relative to the repo)."),folder_id:m.string().optional(),title:m.string().optional(),project_id:m.string().optional()},async a=>{let g=p();if(g)return g;try{let k=a.project_id??s(),C=an(a.file_path)?a.file_path:Oe(process.cwd(),a.file_path),b=a.folder_id;if(!b&&(b=(await I(r,`${T(k)}/drive/folders/default`))?.id,!b))return _("Could not resolve the default Drive folder.");let j=Et(C),A=new FormData;return A.append("file",new Blob([j]),a.title??sn(C)),x(await mt(r,`${T(k)}/drive/folders/${b}/files`,A))}catch(k){return _(k)}}),zt=P("d360_search_drive","Search Drive files (e.g. to reuse an already-uploaded screenshot instead of uploading a duplicate).",{search_keyword:m.string().optional(),allow_images_only:m.boolean().optional(),project_id:m.string().optional()},async a=>{try{return x(await N(r,`${T(a.project_id??s())}/drive/search`,{query:{search_keyword:a.search_keyword,allow_images_only:a.allow_images_only}}))}catch(g){return _(g)}});return cn({name:"document360",version:"0.2.0",instructions:"First-party Document360 tools. The signed-in user's permissions apply server-side. Project/workspace default from the active profile; pass ids explicitly to override. Create articles as DRAFTS; only call d360_publish_article when the user explicitly asks to publish. All articles are Markdown \u2014 the tools enforce this; never attempt WYSIWYG/Block content. To edit a published article, fork it (d360_fork_article) or update with auto_fork. Before any docs gap/coverage analysis, call d360_sync_status to verify the local docs tree matches Document360. When scoping a large repo (which folders back the docs), call d360_repo_inventory for the candidate folders. Before a bulk run (converting/publishing/auditing many articles), call d360_estimate_cost and show the user the count + cost band first. Auth errors mean the session expired \u2014 tell the user to run /login (works inside this session).",tools:[u,l,d,f,y,h,w,v,D,z,R,L,q,S,te,Wt,Ht,zt]})}var $t="CLAUDE.md";function It(e){if(!$e(e))return[];let t=[];for(let r of ln(e)){let n=Ue(e,r);if(r===$t||!un(n).isDirectory())continue;let o=Ue(n,"SKILL.md");$e(o)&&t.push({name:r,body:Ot(o,"utf8")})}return t}function Mt(e){let t=Ue(e,$t);return $e(t)?Ot(t,"utf8"):null}function dn(e,t){let r=Ve(),n=Ke(e),o=Mt(r),s=Mt(n),i=It(r),c=It(n),p=new Set(c.map(d=>d.name)),u=[...i.filter(d=>!p.has(d.name)),...c],l=[];if(o&&l.push(o),s&&l.push(`# Project addendum
9
+ `}function H(e){return"sha256:"+Er("sha256").update(Dr(e),"utf8").digest("hex")}function Y(e,t){return H(`${e??""}
10
+ ${t??""}`)}import{existsSync as Mr,readdirSync as Or,readFileSync as $r,statSync as Ur}from"node:fs";import{join as De}from"node:path";var bt={berlin:{apiUrl:"https://apihub.berlin.document360.net",portalUrl:"https://portal.berlin.document360.net",authorizationUrl:"https://identity.berlin.document360.net/connect/authorize",tokenUrl:"https://identity.berlin.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"},sharjah:{apiUrl:"https://apihub.sharjah.document360.net",portalUrl:"https://portal.sharjah.document360.net",authorizationUrl:"https://identity.sharjah.document360.net/connect/authorize",tokenUrl:"https://identity.sharjah.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"}};function Ar(){return Object.keys(bt)}function qo(e="berlin"){return pe({environment:e})}function pe(e){let t=e.environment??"berlin",r=bt[t];if(!r)throw new Error(`Unknown Document360 environment "${t}". Known: ${Ar().join(", ")}`);let n=process.env.D360_SCOPES?.split(/[\s,]+/).filter(Boolean);return{name:t,apiUrl:process.env.D360_API_URL??e.apiUrl??r.apiUrl,portalUrl:process.env.D360_PORTAL_URL??e.portalUrl??r.portalUrl,authorizationUrl:process.env.D360_AUTHORIZATION_URL??e.authorizationUrl??r.authorizationUrl,tokenUrl:process.env.D360_TOKEN_URL??e.tokenUrl??r.tokenUrl,clientId:process.env.D360_CLIENT_ID??e.clientId??r.clientId,scopes:n??e.scopes??r.scopes,acrValues:process.env.D360_ACR_VALUES??e.acrValues??r.acrValues,redirectUri:process.env.D360_REDIRECT_URI??e.redirectUri??r.redirectUri,prompt:process.env.D360_PROMPT??e.prompt??r.prompt}}function F(e,t){let r=M(e);if(r===null){let s=t??"berlin";return{name:s,connection:pe({environment:s}),project:{},production:!1}}let{name:n,profile:o}=Qe(r,t);return{name:n,connection:pe(o.connection),project:o.project??{},production:o.production===!0}}function zo(e,t,r){let n=M(e);n?.profiles?.[t]&&(n.profiles[t].project={...n.profiles[t].project,...r},Ze(n,e))}function _t(e,t){if(e?.docsDir)return e.docsDir.replace(/\\/g,"/").replace(/\/+$/,"");let r=new Set(Object.keys(t?.articles??{}).map(n=>n.split("/")[0]).filter(Boolean));if(r.size===1)return[...r][0];throw new Error(r.size===0?'Cannot locate the docs folder: no mapped articles yet. Set "docsDir" in .d360-writer.json.':`Cannot locate the docs folder: mapped articles span multiple roots (${[...r].join(", ")}). Set "docsDir" in .d360-writer.json.`)}var Ir=4;function vt(e,t,r,n={getAll:q,get:O}){let o=`/v3/projects/${t}`;return{async fetch(s){let[i,a]=await Promise.all([n.getAll(e,`${o}/workspaces/${r}/articles`),n.getAll(e,`${o}/workspaces/${r}/categories`)]),d=new Map;for(let y of i)d.set(y.id,{id:y.id,title:y.title,categoryId:y.category_id,hidden:y.hidden});let u=new Map;for(let y of a)u.set(y.id,{id:y.id,name:y.name,parentId:y.parent_category_id});let l=s.filter(y=>d.has(y)),f=0,p=async()=>{for(;f<l.length;){let y=l[f++],b=await n.get(e,`${o}/articles/${y}`,{query:{content_mode:"raw"}}),h=d.get(y);h.title=b.title??h.title,h.content=b.content,h.latestVersion=b.latest_version,h.modifiedAt=b.modified_at,h.contentHash=Y(b.title??h.title,b.content)}};return await Promise.all(Array.from({length:Math.min(Ir,l.length)},p)),{articles:d,categories:u}}}}function Nr(e){if(!e.mapped)return e.localExists?"untracked-local":"untracked-remote";if(!e.localExists&&!e.remoteExists)return"orphaned";if(!e.localExists)return"deleted-local";if(!e.remoteExists)return"deleted-remote";if(!e.hasBase)return"unknown-base";let t=e.localHashNow!==e.baseLocalHash,r=e.remoteContentHashNow!==e.baseRemoteContentHash;return t&&r?"conflict":t?"local-ahead":r?"remote-ahead":"in-sync"}function Lr(e,t){let r=De(e,t);if(!Mr(r))return[];let n=[],o=(s,i)=>{for(let a of Or(s)){let d=De(s,a),u=i?`${i}/${a}`:a;Ur(d).isDirectory()?o(d,u):a.endsWith(".md")&&n.push(u)}};return o(r,t.replace(/\\/g,"/")),n}function Br(e,t){let r=new Set(Object.values(e.categories)),n=new Map;for(let i of t.categories.values()){if(!i.parentId)continue;let a=n.get(i.parentId)??[];a.push(i.id),n.set(i.parentId,a)}let o=new Set,s=[...r];for(;s.length>0;){let i=s.shift();o.has(i)||(o.add(i),s.push(...n.get(i)??[]))}return o}async function St(e){let t=F(e.cwd,e.profileName),r=M(e.cwd),n=$(e.cwd,t.name);if(!n)throw new Error(`No d360-category-map.json section for profile "${t.name}". Publish at least one article first (/publish).`);let o={profile:t.name,connection:t.connection},s=n.projectId??t.project.projectId??V(o),i=n.workspaceId??t.project.workspaceId;if(!i)throw new Error(`No workspace recorded for profile "${t.name}". Run /workspace to select one.`);let a=_t(r,n),d=Object.values(n.articles).map(w=>w.id),l=await(e.provider??vt(o,s,i)).fetch(d),f=new Set(Lr(e.cwd,a)),p=[];for(let[w,R]of Object.entries(n.articles)){let z=f.has(w);f.delete(w);let C=l.articles.get(R.id),Q=!!(R.localHash&&R.remoteContentHash),j=Nr({mapped:!0,localExists:z,remoteExists:C!==void 0,hasBase:Q,localHashNow:z?H($r(De(e.cwd,w),"utf8")):void 0,baseLocalHash:R.localHash,remoteContentHashNow:C?.contentHash,baseRemoteContentHash:R.remoteContentHash}),U=[];if(j==="remote-ahead"||j==="conflict"){let J=C?.modifiedAt?.slice(0,10);U.push(`remote edited${J?` ${J}`:""}${C?.latestVersion?` (v${C.latestVersion})`:""}`)}C?.hidden&&U.push("hidden on D360"),p.push({path:w,articleId:R.id,title:C?.title,status:j,detail:U.length>0?U.join("; "):void 0})}for(let w of[...f].sort())p.push({path:w,articleId:null,status:"untracked-local"});let y=new Set(d),b=Br(n,l);for(let w of l.articles.values())y.has(w.id)||!w.categoryId||!b.has(w.categoryId)||p.push({path:null,articleId:w.id,title:w.title,status:"untracked-remote",detail:w.hidden?"hidden on D360":void 0});let h={};for(let w of p)h[w.status]=(h[w.status]??0)+1;return{profile:t.name,projectId:s,workspaceId:i,docsRoot:a,entries:p,counts:h,generatedAt:new Date().toISOString()}}import{posix as Ae}from"node:path";var qr=/\[([^\]]+)\]\(([^)\s]+)\)/g;function Ie(e,t,r){if(!r)return{content:e,resolved:0,unresolved:0};let n=Ae.dirname(t.replace(/\\/g,"/")),o=0,s=0;return{content:e.replace(qr,(a,d,u)=>{if(/^(https?:|mailto:|#|\/)/i.test(u)||!/\.(md|markdown)(#[^)]*)?$/i.test(u))return a;let l=u.indexOf("#"),f=l===-1?u:u.slice(0,l),p=l===-1?"":u.slice(l),y=Ae.normalize(Ae.join(n,f)).replace(/^\.\//,""),b=r.articles[y]?.url;return b?(o++,`[${d}](${b}${p})`):(s++,d)}),resolved:o,unresolved:s}}import{readdirSync as Fr}from"node:fs";import{join as fe}from"node:path";var Wr=new Set([".git","node_modules","dist","build","bin","obj","out","target",".vs",".vscode",".idea",".next","coverage",".turbo",".cache","packages"]),Hr=40,zr=3,Me=5e3,Jr=/\.(csproj|vbproj|fsproj)$|^(package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|pom\.xml|build\.gradle(\.kts)?)$/i,Gr=/\.sln$|^(Directory\.(Build|Packages)\.props|global\.json|go\.work)$/i;function Vr(e){return/\.(csproj|vbproj|fsproj|sln)$/i.test(e)||/^(Directory\.(Build|Packages)\.props|global\.json)$/i.test(e)?".NET":/^(package\.json|angular\.json|tsconfig\.json)$/i.test(e)?"JS/TS":/^(pyproject\.toml|requirements\.txt|setup\.py)$/i.test(e)?"Python":/^(go\.mod|go\.work)$/i.test(e)?"Go":/^Cargo\.toml$/i.test(e)?"Rust":/^(pom\.xml|build\.gradle(\.kts)?)$/i.test(e)?"Java":null}var Kr=/(?:^|[.\-_])(web|portal|ui|app|apps|client|site|widget|admin|dashboard|frontend|api)(?:$|[.\-_])/i,Yr=/(?:^|[.\-_])(tests?|specs?|helm|docker|migrations?|functions?|ci|builds?|scripts|tools|proxy|coverage|release|libs?|redis|signalr|websocket|dataaccess|nuget|sdk|infra|infrastructure|deploy|deployment|e2e)(?:$|[.\-_])/i;function Oe(e){let t=[],r=[];try{for(let n of Fr(e,{withFileTypes:!0}))n.isDirectory()?!n.name.startsWith(".")&&!Wr.has(n.name)&&t.push(n.name):n.isFile()&&r.push(n.name)}catch{}return{dirs:t,files:r}}function Qr(e){let t=new Set;for(let r of e){let n=Vr(r);n&&t.add(n)}return t}var xt=e=>e.some(t=>Jr.test(t)),Zr=e=>e.some(t=>Gr.test(t));function Xr(e){let t=0,r=n=>{if(t>=Me)return;let{dirs:o,files:s}=Oe(n);t+=s.length;for(let i of o){if(t>=Me)return;r(fe(n,i))}};return r(e),Math.min(t,Me)}function en(e,t){let r=e.split("/").pop()??e,n=t.size?[...t].join("+"):"";return Yr.test(r)?{recommended:!1,reason:`tests/infrastructure${n?` \xB7 ${n}`:""}`}:Kr.test(r)?{recommended:!0,reason:`user-facing surface${n?` \xB7 ${n}`:""}`}:{recommended:!1,reason:n?`${n} project`:"source folder"}}function Pt(e){let t=[],r=(s,i)=>{let a=Qr(i.files),{recommended:d,reason:u}=en(s,a);t.push({path:s,fileCount:Xr(fe(e,s)),stacks:[...a],recommended:d,reason:u})},n=(s,i)=>{let a=s?fe(e,s):e,d=Oe(a);if(s&&xt(d.files)){r(s,d);return}if(s&&d.dirs.length===0){r(s,d);return}let u=f=>xt(Oe(fe(a,f)).files);if((Zr(d.files)||d.dirs.some(u)||s==="")&&i<zr){for(let f of d.dirs)n(s?`${s}/${f}`:f,i+1);return}s&&r(s,d)};n("",0),t.sort((s,i)=>Number(i.recommended)-Number(s.recommended)||s.path.localeCompare(i.path));let o=t.filter(s=>s.recommended);return t.slice(0,Math.max(Hr,o.length))}var re={"claude-fable-5":{inputPerMTok:10,outputPerMTok:50},"claude-opus-4-8":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-7":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-6":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-5":{inputPerMTok:5,outputPerMTok:25},"claude-sonnet-4-6":{inputPerMTok:3,outputPerMTok:15},"claude-sonnet-4-5":{inputPerMTok:3,outputPerMTok:15},"claude-haiku-4-5":{inputPerMTok:1,outputPerMTok:5}},tn={opus:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5"},Tt="claude-opus-4-8";function Rt(e){let t=e&&(tn[e.toLowerCase()]??e);if(t&&re[t])return{model:t,rate:re[t],assumed:!1};if(t){let r=Object.keys(re).filter(n=>t.startsWith(n)).sort((n,o)=>o.length-n.length)[0];if(r)return{model:r,rate:re[r],assumed:!1}}return{model:Tt,rate:re[Tt],assumed:!0}}function ne(e,t){return e/1e6*t}var rn=3.5,nn={convert:{inFactor:1.3,outFactor:1.1,overheadInput:12e4,overheadOutput:24e3},rewrite:{inFactor:1.4,outFactor:1.2,overheadInput:13e4,overheadOutput:28e3},publish:{inFactor:1.1,outFactor:.2,overheadInput:5e4,overheadOutput:6e3},audit:{inFactor:1.2,outFactor:.15,overheadInput:1e5,overheadOutput:12e3}},Ct=.7,jt=1.6,me=e=>Math.round(e);function Et(e){let{op:t,model:r}=e,n=nn[t],{model:o,rate:s,assumed:i}=Rt(r),a=e.files.filter(h=>!(h.bytes>0)).map(h=>h.path),d=e.files.filter(h=>h.bytes>0),u=0,l=0;for(let h of d){let w=h.bytes/rn;u+=n.overheadInput+w*n.inFactor,l+=n.overheadOutput+w*n.outFactor}let f=[me(u*Ct),me(u*jt)],p=[me(l*Ct),me(l*jt)],y=[ne(f[0],s.inputPerMTok)+ne(p[0],s.outputPerMTok),ne(f[1],s.inputPerMTok)+ne(p[1],s.outputPerMTok)],b=`Rough estimate (per-article agent overhead + content), priced at ${o} list rates`+(i?" (model unset \u2014 assumed)":"")+". Actuals vary with cache reuse and reasoning depth; the live cost meter reconciles as the run proceeds.";return{model:o,modelAssumed:i,op:t,articles:d.length,skipped:a,inputTokens:f,outputTokens:p,usd:y,note:b}}var At="/v3/projects",P=e=>`${At}/${e}`;function x(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)??'{"success":true}'}]}}function v(e){return{content:[{type:"text",text:e instanceof Error?e.message:String(e)}],isError:!0}}function It(e,t){let r={profile:e.name,connection:e.connection},n=e.project.projectId,o=e.project.workspaceId,s=()=>V(r,n),i=c=>{let g=c??o;if(!g)throw new Error("No workspace_id given and none in the profile. Call d360_list_workspaces first.");return g},a=(c,g)=>{if(t.cwd)try{let k=$e(t.cwd,c);de(t.cwd,e.name,c.replace(/\\/g,"/"),{...g,...on(k)?{localHash:H(Dt(k,"utf8"))}:{}})}catch{}},d=()=>t.writesAllowed?null:v(`Refusing to write to PRODUCTION profile "${e.name}". Authorize this session first: run /allow-prod in the REPL, or pass --yes in one-shot/CI.`),u=S("d360_context","Report the active Document360 connection: profile name, environment, project/workspace id, and whether it is a production profile. Call this before maintaining d360-category-map.json so you scope IDs to the right profile.",{},async()=>{try{return x({profile:e.name,environment:e.connection.name,production:e.production,projectId:n??V(r),workspaceId:o??null})}catch(c){return v(c)}}),l=S("d360_list_projects","List all Document360 projects the signed-in user can access (id, name, sub-domain, status).",{},async()=>{try{return x(await q(r,At))}catch(c){return v(c)}}),f=S("d360_list_workspaces","List workspaces (project versions) for the current project. Each has id, name, slug, is_default, workspace_type.",{project_id:m.string().optional().describe("Defaults to the logged-in/config project.")},async c=>{try{return x(await q(r,`${P(c.project_id??s())}/workspaces`))}catch(g){return v(g)}}),p=S("d360_list_categories","List categories in a workspace (the folder structure for articles).",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await q(r,`${P(c.project_id??s())}/workspaces/${g}/categories`)):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),y=S("d360_list_articles","List articles in a workspace (id, title, status, category). Use to see existing docs before writing.",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await q(r,`${P(c.project_id??s())}/workspaces/${g}/articles`)):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),b=S("d360_get_article","Get a single article including its content. content_mode=raw returns the stored markdown source (best for editing); display returns processed content.",{article_id:m.string(),content_mode:m.enum(["raw","display"]).optional().describe("raw = stored markdown source (default), display = processed."),published:m.boolean().optional().describe("Read the published version instead of the latest draft."),project_id:m.string().optional()},async c=>{try{return x(await O(r,`${P(c.project_id??s())}/articles/${c.article_id}`,{query:{content_mode:c.content_mode,published:c.published}}))}catch(g){return v(g)}}),h=S("d360_ai_query","Ask Document360's AI search over a workspace's published content. Returns an answer grounded in existing articles \u2014 use to check what's already documented.",{query:m.string(),workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await K(r,`${P(c.project_id??s())}/workspaces/ai/query`,{body:{query:c.query,workspace_id:g}})):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),w=S("d360_sync_status","Deterministic drift report: local docs tree vs Document360, classified against the sync bases in d360-category-map.json. Call this BEFORE any docs gap/coverage analysis \u2014 it proves whether the local docs tree can be trusted as the Document360 inventory (no need to fetch article content). Statuses: local-ahead (push pending), remote-ahead (portal edit to pull), conflict, untracked-local/remote, deleted-local/remote, unknown-base (no base recorded yet).",{},async()=>{try{if(!t.cwd)return v("Sync status needs a repo context (no cwd configured for this session).");let c=await St({cwd:t.cwd,profileName:e.name});return x({profile:c.profile,docsRoot:c.docsRoot,generatedAt:c.generatedAt,counts:c.counts,attention:c.entries.filter(g=>g.status!=="in-sync")})}catch(c){return v(c)}}),R=S("d360_repo_inventory","Deterministic inventory of candidate source folders for documentation scope (no file reads of your own needed). Returns project-level folders with: path, fileCount, detected stacks, a `recommended` flag (user-facing surfaces pre-recommended; tests/infrastructure not), and a one-line reason. Use this when analyzing a large/multi-project repo to decide which folders back the docs \u2014 it is the same data the /scope picker pre-ticks, so your recommendation and the picker agree. Cheap; safe to call before reading any source.",{},async()=>{try{return t.cwd?x(Pt(t.cwd)):v("Repo inventory needs a repo context (no cwd configured for this session).")}catch(c){return v(c)}}),z=S("d360_estimate_cost",'Deterministic, no-network token/cost ESTIMATE for a bulk operation. Call this BEFORE you propose a repo-scale run (convert / rewrite / publish / audit MANY articles) so you can show the user the article count and a cost band and get an explicit go-ahead \u2014 see the "Confirm before bulk or irreversible actions" rule. It sizes work from local file BYTES (coarse \u2014 returns [low, high] bands, not a quote). Omit `paths` to estimate over every article tracked in d360-category-map.json; pass `paths` for a subset. Cheap and read-only; safe to call before any write.',{op:m.enum(["convert","rewrite","publish","audit"]).describe("What you intend to do to each article (drives the output-size heuristic)."),paths:m.array(m.string()).optional().describe("Repo-relative .md paths. Omit or leave empty to estimate over all articles in the category map.")},async({op:c,paths:g})=>{try{if(!t.cwd)return v("Cost estimate needs a repo context (no cwd configured for this session).");let k;if(g&&g.length>0)k=g.map(_=>_.replace(/\\/g,"/"));else{let _=$(t.cwd,e.name);if(k=_?Object.keys(_.articles):[],k.length===0)return v("No articles are tracked in d360-category-map.json for this profile, and no `paths` were given. Pass `paths` explicitly (the repo-relative .md files you intend to operate on).")}let E=k.map(_=>{let D=0;try{D=sn($e(t.cwd,_)).size}catch{D=0}return{path:_,bytes:D}});return x(Et({files:E,op:c,model:t.model}))}catch(k){return v(k)}}),C=S("d360_create_category","Create a category (a docs folder). Returns the new category id.",{name:m.string(),workspace_id:m.string().optional(),parent_category_id:m.string().optional().describe("Omit for a top-level category."),content:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/categories`,{body:{name:c.name,workspace_id:i(c.workspace_id),parent_category_id:c.parent_category_id,content:c.content,slug:c.slug,order:c.order,content_type:"markdown"}}))}catch(k){return v(k)}}),Q=S("d360_create_article","Create a DRAFT article in a category. The body is always Markdown (product rule \u2014 we never create WYSIWYG/Block articles). Returns the new article id. Does not publish.",{title:m.string(),category_id:m.string(),content:m.string().optional().describe("Markdown body."),workspace_id:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional(),local_path:m.string().optional().describe("Repo-relative path of the local .md this article mirrors (e.g. user-docs/01-intro/01-a.md). When given, the new article id + sync base are recorded into d360-category-map.json automatically \u2014 do not edit the articles map yourself.")},async c=>{let g=d();if(g)return g;try{let k=c.project_id??s(),E=c.content;typeof E=="string"&&c.local_path&&t.cwd&&(E=Ie(E,c.local_path,$(t.cwd,e.name)).content);let _=await K(r,`${P(k)}/articles/bulk`,{body:{articles:[{title:c.title,category_id:c.category_id,workspace_id:i(c.workspace_id),content:E,slug:c.slug,order:c.order,content_type:"markdown"}]}}),D=Array.isArray(_)?_[0]:void 0;if(c.local_path&&typeof D?.id=="string"){let I;try{let W=await O(r,`${P(k)}/articles/${D.id}`,{});I=typeof W?.url=="string"?W.url:void 0}catch{}a(c.local_path,{id:D.id,remoteContentHash:Y(c.title,E),remoteVersion:1,...I?{url:I}:{}})}return x(_)}catch(k){return v(k)}}),j=S("d360_update_article","Update an article's title/content/category. Edits the latest draft by default; set auto_fork to safely edit a published article (creates a new draft version).",{article_id:m.string(),title:m.string().optional(),content:m.string().optional().describe("Markdown body."),category_id:m.string().optional(),hidden:m.boolean().optional(),version_number:m.number().optional(),auto_fork:m.boolean().optional().describe("If the target version is published, fork a new draft instead of erroring."),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{let{article_id:k,project_id:E,..._}=c,D=E??s(),I=typeof _.content=="string"&&t.cwd?$(t.cwd,e.name):null,W=I?kt(I,k):null;typeof _.content=="string"&&W&&I&&(_.content=Ie(_.content,W,I).content);let G=null;if(typeof _.content=="string"&&t.onUiEvent)try{G=await O(r,`${P(D)}/articles/${k}`,{query:{content_mode:"raw"}})}catch{}let Be=e.project.languageCode??G?.lang_code,qe=await mt(r,`${P(D)}/articles/${k}`,{body:_,...Be?{query:{lang_code:Be}}:{}});if(G&&typeof _.content=="string"&&(G.content??"")!==_.content&&t.onUiEvent?.({type:"article_diff",articleId:k,title:G.title??null,oldContent:G.content??"",newContent:_.content}),typeof _.content=="string"&&W)try{let X=qe,Fe=_.title??X?.title??G?.title;typeof Fe=="string"&&a(W,{id:k,remoteContentHash:Y(Fe,_.content),...typeof X?.latest_version=="number"?{remoteVersion:X.latest_version}:{},...typeof X?.modified_at=="string"?{remoteModifiedAt:X.modified_at}:{}})}catch{}return x(qe)}catch(k){return v(k)}}),U=S("d360_fork_article","Fork a published article into a new draft version \u2014 the safe way to start editing live content.",{article_id:m.string(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/fork`))}catch(k){return v(k)}}),J=S("d360_publish_article","Publish an article version. This makes the draft live to readers \u2014 only call when the user explicitly asks to publish.",{article_id:m.string(),version_number:m.number(),workspace_id:m.string().optional(),message:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/publish`,{body:{workspace_id:i(c.workspace_id),version_number:c.version_number,message:c.message}}))}catch(k){return v(k)}}),T=S("d360_unpublish_article","Unpublish an article version, reverting it to draft (removes it from readers).",{article_id:m.string(),version_number:m.number().describe("The published version to unpublish (see d360_get_article)."),workspace_id:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/unpublish`,{body:{workspace_id:i(c.workspace_id),version_number:c.version_number}}))}catch(k){return v(k)}}),Ht=S("d360_upload_drive_file","Upload a local file (e.g. a captured screenshot PNG) to Drive and return its URL for embedding in an article. Uploads to the default folder unless folder_id is given.",{file_path:m.string().describe("Local path to the file (absolute, or relative to the repo)."),folder_id:m.string().optional(),title:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{let k=c.project_id??s(),E=cn(c.file_path)?c.file_path:$e(process.cwd(),c.file_path),_=c.folder_id;if(!_&&(_=(await O(r,`${P(k)}/drive/folders/default`))?.id,!_))return v("Could not resolve the default Drive folder.");let D=Dt(E),I=new FormData;return I.append("file",new Blob([D]),c.title??an(E)),x(await gt(r,`${P(k)}/drive/folders/${_}/files`,I))}catch(k){return v(k)}}),zt=S("d360_search_drive","Search Drive files (e.g. to reuse an already-uploaded screenshot instead of uploading a duplicate).",{search_keyword:m.string().optional(),allow_images_only:m.boolean().optional(),project_id:m.string().optional()},async c=>{try{return x(await q(r,`${P(c.project_id??s())}/drive/search`,{query:{search_keyword:c.search_keyword,allow_images_only:c.allow_images_only}}))}catch(g){return v(g)}});return ln({name:"document360",version:"0.2.0",instructions:"First-party Document360 tools. The signed-in user's permissions apply server-side. Project/workspace default from the active profile; pass ids explicitly to override. Create articles as DRAFTS; only call d360_publish_article when the user explicitly asks to publish. All articles are Markdown \u2014 the tools enforce this; never attempt WYSIWYG/Block content. To edit a published article, fork it (d360_fork_article) or update with auto_fork. Before any docs gap/coverage analysis, call d360_sync_status to verify the local docs tree matches Document360. When scoping a large repo (which folders back the docs), call d360_repo_inventory for the candidate folders. Before a bulk run (converting/publishing/auditing many articles), call d360_estimate_cost and show the user the count + cost band first. Auth errors mean the session expired \u2014 tell the user to run /login (works inside this session).",tools:[u,l,f,p,y,b,h,w,R,z,C,Q,j,U,J,T,Ht,zt]})}var Ut="CLAUDE.md";function Mt(e){if(!Ue(e))return[];let t=[];for(let r of un(e)){let n=Ne(e,r);if(r===Ut||!dn(n).isDirectory())continue;let o=Ne(n,"SKILL.md");Ue(o)&&t.push({name:r,body:$t(o,"utf8")})}return t}function Ot(e){let t=Ne(e,Ut);return Ue(t)?$t(t,"utf8"):null}function fn(e,t){let r=Ke(),n=Ye(e),o=Ot(r),s=Ot(n),i=Mt(r),a=Mt(n),d=new Set(a.map(f=>f.name)),u=[...i.filter(f=>!d.has(f.name)),...a],l=[];if(o&&l.push(o),s&&l.push(`# Project addendum
11
11
 
12
- ${s}`),t&&(l.push("# Project configuration"),l.push("```json"),l.push(JSON.stringify(t,null,2)),l.push("```")),u.length>0){l.push("# Capabilities"),l.push('You have the following specialized documentation skills. Activate the one whose preconditions match the user request. If multiple match, run them in order. These skills are INLINE INSTRUCTIONS in this prompt \u2014 "activating" one means following its steps directly. They are NOT registered with any Skill tool; never attempt to invoke them via a Skill/skill-runner tool.');for(let d of u)l.push(`## Skill: ${d.name}`),l.push(d.body)}return l.join(`
12
+ ${s}`),t&&(l.push("# Project configuration"),l.push("```json"),l.push(JSON.stringify(t,null,2)),l.push("```")),u.length>0){l.push("# Capabilities"),l.push('You have the following specialized documentation skills. Activate the one whose preconditions match the user request. If multiple match, run them in order. These skills are INLINE INSTRUCTIONS in this prompt \u2014 "activating" one means following its steps directly. They are NOT registered with any Skill tool; never attempt to invoke them via a Skill/skill-runner tool.');for(let f of u)l.push(`## Skill: ${f.name}`),l.push(f.body)}return l.join(`
13
13
 
14
- `)}function fn(){let e=Ze(),t={};for(let[r,n]of Object.entries(e.servers))n.type==="stdio"?t[r]={type:"stdio",command:n.command,args:n.args,env:n.env}:n.type==="http"?t[r]={type:"http",url:n.url,headers:n.headers}:n.type==="sse"&&(t[r]={type:"sse",url:n.url,headers:n.headers});return t}var mn=/auth|api key|login|credential|401|forbidden/i,Ne=class{queue;iterator;sdkQuery;uiEvents=[];sessionId=null;constructor(t={}){let r=t.cwd??process.cwd(),n=W(r),o=dn(r,n),s=fn(),i=n?.defaultModel??ve().defaultModel;try{let d=B(r,t.profileName),f=!d.production||t.allowProdWrites===!0;s.document360=At(d,{writesAllowed:f,cwd:r,model:i,onUiEvent:y=>this.uiEvents.push(y)})}catch{}this.queue=new oe;let c=async(d,f)=>{if(We.has(d)){let y=f.file_path??f.notebook_path;if(y&&!me(r,y,n))return{behavior:"deny",message:ge(y)}}if(d==="Bash"||d==="PowerShell"){let y=f.command;if(typeof y=="string"){let h=He(y,r,n);if(h)return{behavior:"deny",message:ge(h)}}}return{behavior:"allow",updatedInput:f}},p=n?.mode==="engineer",u={cwd:r,systemPrompt:o,mcpServers:s,...p?{permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0}:{permissionMode:"default",canUseTool:c},disallowedTools:["Skill","AskUserQuestion"],includePartialMessages:!0,settingSources:[],model:i,resume:t.resume},l=pn({prompt:this.queue,options:u});this.sdkQuery=l,this.iterator=l[Symbol.asyncIterator]()}async interrupt(){try{await this.sdkQuery.interrupt()}catch{}}async setModel(t){await this.sdkQuery.setModel(t)}async*send(t){for(this.queue.push(t);;){let r;try{r=await this.iterator.next()}catch(s){let i=s.message;yield{type:"error",kind:mn.test(i)?"auth":"agent",message:i};return}if(r.done)return;for(;this.uiEvents.length>0;)yield this.uiEvents.shift();let n=r.value?.session_id;n&&!this.sessionId&&(this.sessionId=n,yield{type:"session",sessionId:n});let o=hn(r.value);if(o){for(let s of o)yield s;if(o.some(s=>s.type==="result"))return}}}close(){this.queue.close()}};function Ut(e={}){return new Ne(e)}function gn(e){return typeof e=="string"?e:Array.isArray(e)?e.map(t=>{let r=t;return r.type==="text"&&typeof r.text=="string"?r.text:""}).filter(Boolean).join(`
15
- `):""}function hn(e){if(!e||typeof e!="object")return null;let t=e;if(t.type==="stream_event"){let r=t.event;return r?.delta&&typeof r.delta.text=="string"?[{type:"text",delta:r.delta.text}]:null}if(t.type==="assistant"){let r=t.message,n=[];for(let o of r?.content??[]){let s=o;s.type==="tool_use"&&n.push({type:"tool",id:s.id??"",name:s.name??"tool",input:s.input??{}})}return n.length>0?n:null}if(t.type==="user"){let r=t.message;if(!Array.isArray(r?.content))return null;let n=[];for(let o of r.content){let s=o;s.type==="tool_result"&&n.push({type:"tool_result",id:s.tool_use_id??"",output:gn(s.content),isError:s.is_error===!0})}return n.length>0?n:null}if(t.type==="result"){let r=t;return[{type:"result",inputTokens:r.usage?.input_tokens??0,outputTokens:r.usage?.output_tokens??0,cacheReadTokens:r.usage?.cache_read_input_tokens??0,cacheCreationTokens:r.usage?.cache_creation_input_tokens??0,costUsd:r.total_cost_usd??0,ok:r.subtype==="success"}]}return null}import{existsSync as yn}from"node:fs";import{homedir as wn}from"node:os";import{join as kn}from"node:path";var Us=["auto","api","subscription"];function Nt(){return process.env.CLAUDE_CODE_OAUTH_TOKEN?!0:yn(kn(wn(),".claude",".credentials.json"))}function Ns(e){let t=!!process.env.ANTHROPIC_API_KEY;return e==="api"?t?{kind:"api"}:{kind:"none"}:e==="subscription"?(delete process.env.ANTHROPIC_API_KEY,{kind:"subscription",stored:Nt()}):t?{kind:"api"}:{kind:"subscription",stored:Nt()}}import{existsSync as bn,readFileSync as _n,writeFileSync as vn}from"node:fs";var Sn=100;function Y(){let e=we();if(!bn(e))return[];try{let t=JSON.parse(_n(e,"utf8"));return Array.isArray(t)?t:[]}catch{return[]}}function fe(e){$(E());let t=[...e].sort((r,n)=>n.updatedAt.localeCompare(r.updatedAt)).slice(0,Sn);vn(we(),JSON.stringify(t,null,2))}function Fs(e){let t=Y().filter(r=>r.uuid!==e.uuid);t.push(e),fe(t)}function Ws(e){return Y().find(t=>t.uuid===e)}function Pn(e){return Y().filter(t=>t.cwd===e).sort((t,r)=>r.updatedAt.localeCompare(t.updatedAt))}function Hs(e,t){let r=Pn(e),n=t.toLowerCase(),o=r.find(c=>c.name.toLowerCase()===n);if(o)return o;let s=r.filter(c=>c.name.toLowerCase().startsWith(n));if(s.length===1)return s[0];let i=r.filter(c=>c.uuid.startsWith(t));if(i.length===1)return i[0]}function zs(e){let t=Y(),r=t.find(n=>n.uuid===e);r&&(r.updatedAt=new Date().toISOString(),fe(t))}function Js(e,t){let r=Y(),n=r.find(o=>o.uuid===e);return n?(n.name=t,n.renamed=!0,n.updatedAt=new Date().toISOString(),fe(r),!0):!1}function Gs(e,t){let r=Y(),n=r.find(o=>o.uuid===e);!n||n.renamed||(n.name=t,n.titled=!0,fe(r))}function Vs(e,t=6){return e.toLowerCase().replace(/[^a-z0-9\s-]/g,"").split(/\s+/).filter(Boolean).slice(0,t).join("-")||"session"}function Ks(e){let t=Date.now()-new Date(e).getTime(),r=Math.floor(t/6e4);if(r<1)return"just now";if(r<60)return`${r}m ago`;let n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}import{query as xn}from"@anthropic-ai/claude-agent-sdk";var Tn=6e4;async function Zs(e,t){try{return await Promise.race([Rn(e,t),Cn()])}catch{return null}}async function Rn(e,t){let r=xn({prompt:["Generate a short session title for a documentation-writing session that started with this request:","",e.slice(0,500),"","Reply with ONLY the title: 3-6 words, lowercase, kebab-case, no quotes, no punctuation."].join(`
16
- `),options:{cwd:t,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You generate terse kebab-case session titles. Output only the title, nothing else.",allowedTools:[]}});for await(let n of r){let o=n;if(o.type==="result")return o.subtype==="success"&&typeof o.result=="string"?jn(o.result):null}return null}function Cn(){return new Promise(e=>setTimeout(()=>e(null),Tn).unref())}function jn(e){let t=e.trim().toLowerCase().replace(/[^a-z0-9\s-]/g,"").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").split("-").slice(0,8).join("-").slice(0,60);return t.length>=3?t:null}import{query as En}from"@anthropic-ai/claude-agent-sdk";var Dn=3e4;async function ti(e,t,r){try{return await Promise.race([An(e,t,r),In()])}catch{return null}}async function An(e,t,r){let n=En({prompt:["In a documentation-writing session, the user asked:","",e.slice(0,800),"","The assistant's reply ended with:","",t.slice(-4e3),"","Suggest the user's most likely next prompt. Reply with ONLY that prompt:","imperative, specific, at most 15 words, no quotes \u2014 a complete, grammatical","sentence the user could send verbatim. If the assistant asked a question,",'answer it. If the assistant offered options AND recommended one ("the right','approach", "recommended", "I suggest"), your suggestion must accept the',"RECOMMENDED option \u2014 never an alternative the assistant advised against or","deferred. If there is no clear next action, reply with exactly: NONE"].join(`
17
- `),options:{cwd:r,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You propose the next user prompt for a docs-writing CLI. Output only the prompt, or NONE.",allowedTools:[]}});for await(let o of n){let s=o;if(s.type==="result")return s.subtype==="success"&&typeof s.result=="string"?Mn(s.result):null}return null}function In(){return new Promise(e=>setTimeout(()=>e(null),Dn).unref())}function Mn(e){let t=e.trim().split(`
18
- `)[0].replace(/^["'`]+|["'`]+$/g,"").trim();return!t||/^none\b/i.test(t)||t.length<4||t.length>100?null:t}var On=/^---\r?\n[\s\S]*?\r?\n---\r?\n/;function Bt(e){let t=e.match(On);return t?{frontmatter:t[0],body:e.slice(t[0].length)}:{frontmatter:null,body:e}}import{existsSync as $n,mkdirSync as Un,readFileSync as Nn,writeFileSync as Bn}from"node:fs";import{dirname as Ln,join as Lt}from"node:path";var qt=/^\[Screenshot: [^\]]+\]\s*$/;function qn(e){let t=e.replace(/\r\n/g,`
14
+ `)}function mn(){let e=Xe(),t={};for(let[r,n]of Object.entries(e.servers))n.type==="stdio"?t[r]={type:"stdio",command:n.command,args:n.args,env:n.env}:n.type==="http"?t[r]={type:"http",url:n.url,headers:n.headers}:n.type==="sse"&&(t[r]={type:"sse",url:n.url,headers:n.headers});return t}var gn=/auth|api key|login|credential|401|forbidden/i,Le=class{queue;iterator;sdkQuery;uiEvents=[];sessionId=null;constructor(t={}){let r=t.cwd??process.cwd(),n=M(r),o=fn(r,n),s=mn(),i=t.model??n?.defaultModel??te().defaultModel,a=i==="auto"?void 0:i;try{let p=F(r,t.profileName),y=!p.production||t.allowProdWrites===!0;s.document360=It(p,{writesAllowed:y,cwd:r,model:a,onUiEvent:b=>this.uiEvents.push(b)})}catch{}this.queue=new ie;let d=async(p,y)=>{if(He.has(p)){let b=y.file_path??y.notebook_path;if(b&&!he(r,b,n))return{behavior:"deny",message:ye(b)}}if(p==="Bash"||p==="PowerShell"){let b=y.command;if(typeof b=="string"){let h=ze(b,r,n);if(h)return{behavior:"deny",message:ye(h)}}}return{behavior:"allow",updatedInput:y}},u=n?.mode==="engineer",l={cwd:r,systemPrompt:o,mcpServers:s,...u?{permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0}:{permissionMode:"default",canUseTool:d},disallowedTools:["Skill","AskUserQuestion"],includePartialMessages:!0,settingSources:[],model:a,resume:t.resume},f=pn({prompt:this.queue,options:l});this.sdkQuery=f,this.iterator=f[Symbol.asyncIterator]()}async interrupt(){try{await this.sdkQuery.interrupt()}catch{}}async setModel(t){await this.sdkQuery.setModel(t)}async*send(t){for(this.queue.push(t);;){let r;try{r=await this.iterator.next()}catch(s){let i=s.message;yield{type:"error",kind:gn.test(i)?"auth":"agent",message:i};return}if(r.done)return;for(;this.uiEvents.length>0;)yield this.uiEvents.shift();let n=r.value?.session_id;n&&!this.sessionId&&(this.sessionId=n,yield{type:"session",sessionId:n});let o=yn(r.value);if(o){for(let s of o)yield s;if(o.some(s=>s.type==="result"))return}}}close(){this.queue.close()}};function Nt(e={}){return new Le(e)}function hn(e){return typeof e=="string"?e:Array.isArray(e)?e.map(t=>{let r=t;return r.type==="text"&&typeof r.text=="string"?r.text:""}).filter(Boolean).join(`
15
+ `):""}function yn(e){if(!e||typeof e!="object")return null;let t=e;if(t.type==="stream_event"){let r=t.event;return r?.delta&&typeof r.delta.text=="string"?[{type:"text",delta:r.delta.text}]:null}if(t.type==="assistant"){let r=t.message,n=[];for(let o of r?.content??[]){let s=o;s.type==="tool_use"&&n.push({type:"tool",id:s.id??"",name:s.name??"tool",input:s.input??{}})}return n.length>0?n:null}if(t.type==="user"){let r=t.message;if(!Array.isArray(r?.content))return null;let n=[];for(let o of r.content){let s=o;s.type==="tool_result"&&n.push({type:"tool_result",id:s.tool_use_id??"",output:hn(s.content),isError:s.is_error===!0})}return n.length>0?n:null}if(t.type==="result"){let r=t;return[{type:"result",inputTokens:r.usage?.input_tokens??0,outputTokens:r.usage?.output_tokens??0,cacheReadTokens:r.usage?.cache_read_input_tokens??0,cacheCreationTokens:r.usage?.cache_creation_input_tokens??0,costUsd:r.total_cost_usd??0,ok:r.subtype==="success"}]}return null}import{existsSync as wn}from"node:fs";import{homedir as kn}from"node:os";import{join as bn}from"node:path";var Bs=["auto","api","subscription"];function Lt(){return process.env.CLAUDE_CODE_OAUTH_TOKEN?!0:wn(bn(kn(),".claude",".credentials.json"))}function qs(e){let t=!!process.env.ANTHROPIC_API_KEY;return e==="api"?t?{kind:"api"}:{kind:"none"}:e==="subscription"?(delete process.env.ANTHROPIC_API_KEY,{kind:"subscription",stored:Lt()}):t?{kind:"api"}:{kind:"subscription",stored:Lt()}}import{existsSync as _n,readFileSync as vn,writeFileSync as Sn}from"node:fs";var xn=100;function Z(){let e=be();if(!_n(e))return[];try{let t=JSON.parse(vn(e,"utf8"));return Array.isArray(t)?t:[]}catch{return[]}}function ge(e){L(A());let t=[...e].sort((r,n)=>n.updatedAt.localeCompare(r.updatedAt)).slice(0,xn);Sn(be(),JSON.stringify(t,null,2))}function zs(e){let t=Z().filter(r=>r.uuid!==e.uuid);t.push(e),ge(t)}function Js(e){return Z().find(t=>t.uuid===e)}function Pn(e){return Z().filter(t=>t.cwd===e).sort((t,r)=>r.updatedAt.localeCompare(t.updatedAt))}function Gs(e,t){let r=Pn(e),n=t.toLowerCase(),o=r.find(a=>a.name.toLowerCase()===n);if(o)return o;let s=r.filter(a=>a.name.toLowerCase().startsWith(n));if(s.length===1)return s[0];let i=r.filter(a=>a.uuid.startsWith(t));if(i.length===1)return i[0]}function Vs(e){let t=Z(),r=t.find(n=>n.uuid===e);r&&(r.updatedAt=new Date().toISOString(),ge(t))}function Ks(e,t){let r=Z(),n=r.find(o=>o.uuid===e);return n?(n.name=t,n.renamed=!0,n.updatedAt=new Date().toISOString(),ge(r),!0):!1}function Ys(e,t){let r=Z(),n=r.find(o=>o.uuid===e);!n||n.renamed||(n.name=t,n.titled=!0,ge(r))}function Qs(e,t=6){return e.toLowerCase().replace(/[^a-z0-9\s-]/g,"").split(/\s+/).filter(Boolean).slice(0,t).join("-")||"session"}function Zs(e){let t=Date.now()-new Date(e).getTime(),r=Math.floor(t/6e4);if(r<1)return"just now";if(r<60)return`${r}m ago`;let n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}import{query as Tn}from"@anthropic-ai/claude-agent-sdk";var Rn=6e4;async function ti(e,t){try{return await Promise.race([Cn(e,t),jn()])}catch{return null}}async function Cn(e,t){let r=Tn({prompt:["Generate a short session title for a documentation-writing session that started with this request:","",e.slice(0,500),"","Reply with ONLY the title: 3-6 words, lowercase, kebab-case, no quotes, no punctuation."].join(`
16
+ `),options:{cwd:t,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You generate terse kebab-case session titles. Output only the title, nothing else.",allowedTools:[]}});for await(let n of r){let o=n;if(o.type==="result")return o.subtype==="success"&&typeof o.result=="string"?En(o.result):null}return null}function jn(){return new Promise(e=>setTimeout(()=>e(null),Rn).unref())}function En(e){let t=e.trim().toLowerCase().replace(/[^a-z0-9\s-]/g,"").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").split("-").slice(0,8).join("-").slice(0,60);return t.length>=3?t:null}import{query as Dn}from"@anthropic-ai/claude-agent-sdk";var An=3e4;async function oi(e,t,r){try{return await Promise.race([In(e,t,r),Mn()])}catch{return null}}async function In(e,t,r){let n=Dn({prompt:["In a documentation-writing session, the user asked:","",e.slice(0,800),"","The assistant's reply ended with:","",t.slice(-4e3),"","Suggest the user's most likely next prompt. Reply with ONLY that prompt:","imperative, specific, at most 15 words, no quotes \u2014 a complete, grammatical","sentence the user could send verbatim. If the assistant asked a question,",'answer it. If the assistant offered options AND recommended one ("the right','approach", "recommended", "I suggest"), your suggestion must accept the',"RECOMMENDED option \u2014 never an alternative the assistant advised against or","deferred. If there is no clear next action, reply with exactly: NONE"].join(`
17
+ `),options:{cwd:r,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You propose the next user prompt for a docs-writing CLI. Output only the prompt, or NONE.",allowedTools:[]}});for await(let o of n){let s=o;if(s.type==="result")return s.subtype==="success"&&typeof s.result=="string"?On(s.result):null}return null}function Mn(){return new Promise(e=>setTimeout(()=>e(null),An).unref())}function On(e){let t=e.trim().split(`
18
+ `)[0].replace(/^["'`]+|["'`]+$/g,"").trim();return!t||/^none\b/i.test(t)||t.length<4||t.length>100?null:t}var $n=/^---\r?\n[\s\S]*?\r?\n---\r?\n/;function Bt(e){let t=e.match($n);return t?{frontmatter:t[0],body:e.slice(t[0].length)}:{frontmatter:null,body:e}}import{existsSync as Un,mkdirSync as Nn,readFileSync as Ln,writeFileSync as Bn}from"node:fs";import{dirname as qn,join as qt}from"node:path";var Ft=/^\[Screenshot: [^\]]+\]\s*$/;function Fn(e){let t=e.replace(/\r\n/g,`
19
19
  `).split(`
20
- `),r=new Map;for(let n=0;n<t.length;n++){if(!t[n].startsWith("<!-- SCREENSHOT"))continue;let o=n;for(;n<t.length&&!t[n].includes("-->");)n++;if(n>=t.length)break;let s=t.slice(o,n+1),i=n+1;for(;i<t.length&&t[i].trim()==="";)i++;i<t.length&&qt.test(t[i])&&r.set(t[i].trim(),[...s,...t.slice(n+1,i)])}return r}function Fn(e,t,r){let n=[],o=t.replace(/\r\n/g,`
21
- `).replace(/^\n+/,""),s=r?Bt(r).frontmatter:null,i=o,c=0;if(r){let u=qn(r);u.size>0&&(i=o.split(`
22
- `).flatMap(l=>{let d=u.get(l.trim());return d&&qt.test(l)?(c++,[...d,l]):[l]}).join(`
23
- `))}return c>0&&n.push(`${c} screenshot spec comment(s) re-attached from the existing local file.`),r&&/\]\((\.\.?\/)[^)]+\.md\)/.test(r)&&n.push("The existing local file has relative cross-article links; publishing flattened them to plain text on Document360, so this pull does not restore them."),/!\[[^\]]*\]\(https?:\/\/[^)]*\)/.test(o)&&n.push("Remote content embeds uploaded images (Drive URLs); they are kept as-is locally."),s&&n.push("Existing frontmatter preserved."),{content:`${s??""}# ${e}
20
+ `),r=new Map;for(let n=0;n<t.length;n++){if(!t[n].startsWith("<!-- SCREENSHOT"))continue;let o=n;for(;n<t.length&&!t[n].includes("-->");)n++;if(n>=t.length)break;let s=t.slice(o,n+1),i=n+1;for(;i<t.length&&t[i].trim()==="";)i++;i<t.length&&Ft.test(t[i])&&r.set(t[i].trim(),[...s,...t.slice(n+1,i)])}return r}function Wn(e,t,r){let n=[],o=t.replace(/\r\n/g,`
21
+ `).replace(/^\n+/,""),s=r?Bt(r).frontmatter:null,i=o,a=0;if(r){let u=Fn(r);u.size>0&&(i=o.split(`
22
+ `).flatMap(l=>{let f=u.get(l.trim());return f&&Ft.test(l)?(a++,[...f,l]):[l]}).join(`
23
+ `))}return a>0&&n.push(`${a} screenshot spec comment(s) re-attached from the existing local file.`),r&&/\]\((\.\.?\/)[^)]+\.md\)/.test(r)&&n.push("The existing local file has relative cross-article links; publishing flattened them to plain text on Document360, so this pull does not restore them."),/!\[[^\]]*\]\(https?:\/\/[^)]*\)/.test(o)&&n.push("Remote content embeds uploaded images (Drive URLs); they are kept as-is locally."),s&&n.push("Existing frontmatter preserved."),{content:`${s??""}# ${e}
24
24
 
25
25
  ${i}`.replace(/\n*$/,`
26
- `),notes:n}}async function pi(e,t=I){let r=B(e.cwd,e.profileName),n=M(e.cwd,r.name),o=n?.articles[e.relPath];if(!n||!o)throw new Error(`"${e.relPath}" is not in d360-category-map.json for profile "${r.name}". Run /sync to see tracked articles.`);let s={profile:r.name,connection:r.connection},i=n.projectId??r.project.projectId??G(s),c=await t(s,`/v3/projects/${i}/articles/${o.id}`,{query:{content_mode:"raw"}}),p=Lt(e.cwd,e.relPath),u=$n(p)?Nn(p,"utf8"):null,l=c.title??"",{content:d,notes:f}=Fn(l,c.content??"",u);return{path:e.relPath,articleId:o.id,title:l,oldContent:u??"",newContent:d,remoteContentHash:K(c.title,c.content),latestVersion:c.latest_version,modifiedAt:c.modified_at,overwritesLocalChanges:u!==null&&o.localHash!==void 0&&H(u)!==o.localHash,notes:f}}function di(e,t){let r=B(e.cwd,e.profileName),n=Lt(e.cwd,t.path),o=t.oldContent.includes(`\r
26
+ `),notes:n}}async function mi(e,t=O){let r=F(e.cwd,e.profileName),n=$(e.cwd,r.name),o=n?.articles[e.relPath];if(!n||!o)throw new Error(`"${e.relPath}" is not in d360-category-map.json for profile "${r.name}". Run /sync to see tracked articles.`);let s={profile:r.name,connection:r.connection},i=n.projectId??r.project.projectId??V(s),a=await t(s,`/v3/projects/${i}/articles/${o.id}`,{query:{content_mode:"raw"}}),d=qt(e.cwd,e.relPath),u=Un(d)?Ln(d,"utf8"):null,l=a.title??"",{content:f,notes:p}=Wn(l,a.content??"",u);return{path:e.relPath,articleId:o.id,title:l,oldContent:u??"",newContent:f,remoteContentHash:Y(a.title,a.content),latestVersion:a.latest_version,modifiedAt:a.modified_at,overwritesLocalChanges:u!==null&&o.localHash!==void 0&&H(u)!==o.localHash,notes:p}}function gi(e,t){let r=F(e.cwd,e.profileName),n=qt(e.cwd,t.path),o=t.oldContent.includes(`\r
27
27
  `)?t.newContent.replace(/\n/g,`\r
28
- `):t.newContent;Un(Ln(n),{recursive:!0}),Bn(n,o,"utf8"),le(e.cwd,r.name,t.path,{id:t.articleId,localHash:H(o),remoteContentHash:t.remoteContentHash,remoteVersion:t.latestVersion,remoteModifiedAt:t.modifiedAt})}var Wn=12,Ft=e=>e.replace(/\\/g,"/");function Hn(e){let t=new Map;for(let r of e){let n=Ft(r),o=n.lastIndexOf("/"),s=o>=0?n.slice(0,o):"(root)",i=t.get(s);i||(i=[],t.set(s,i)),i.push(n)}return[...t.entries()].map(([r,n])=>({label:r,paths:n.sort()})).sort((r,n)=>r.label.localeCompare(n.label))}function zn(e,t){let r=Math.max(1,t),n=e.map(Ft).sort(),o=[];for(let s=0;s<n.length;s+=r)o.push(n.slice(s,s+r));return o.map((s,i)=>({label:`chunk ${i+1}/${o.length}`,paths:s}))}function Ti(e,t){let r=t?.maxPerPartition??Wn,n=[];for(let o of Hn(e)){if(o.paths.length<=r){n.push(o);continue}let s=zn(o.paths,r);s.forEach((i,c)=>n.push({label:`${o.label} (${c+1}/${s.length})`,paths:i.paths}))}return n}function Ri(e,t){let r;try{r=B(e,t).name}catch{return[]}let n=M(e,r);return n?Object.keys(n.articles):[]}var Jn=e=>Ut(e);async function*Ei(e){let{cwd:t,partitions:r,promptFor:n}=e,o=e.spawn??Jn,s=Math.max(1,Math.min(e.concurrency??3,Math.max(1,r.length))),i=[],c=[],p=null,u=h=>{c.push(h);let w=p;p=null,w?.()},l=async h=>{let w=r[h];u({type:"partition_status",index:h,label:w.label,status:"running"});let v=o({cwd:t,profileName:e.profileName,allowProdWrites:e.allowProdWrites}),D=0,z=0,R=!1,L;try{for await(let S of v.send(n(w)))u({type:"partition_event",index:h,label:w.label,event:S}),S.type==="result"?(D+=S.costUsd,z+=S.outputTokens,R=S.ok):S.type==="error"&&(L=S.message)}catch(S){L=S instanceof Error?S.message:String(S)}finally{try{v.close()}catch{}}let q=R&&!L;i.push({index:h,label:w.label,ok:q,costUsd:D,outputTokens:z,error:L}),u({type:"partition_status",index:h,label:w.label,status:q?"done":"failed"})},d=0,f=(async()=>{let h=async()=>{for(;;){let w=d++;if(w>=r.length)return;await l(w)}};await Promise.all(Array.from({length:s},()=>h()))})(),y=!1;for(f.then(()=>{y=!0;let h=p;p=null,h?.()});;){if(c.length>0){yield c.shift();continue}if(y)break;await new Promise(h=>{p=h})}i.sort((h,w)=>h.index-w.index),yield{type:"run_done",ok:i.length>0&&i.every(h=>h.ok),totalCostUsd:i.reduce((h,w)=>h+w.costUsd,0),results:i}}export{Us as AUTH_MODES,ae as D360ApiError,U as D360AuthError,Wn as DEFAULT_MAX_PER_PARTITION,Ne as EngineSession,Z as ProfileConfigError,br as apiLogDir,di as applyPull,lr as buildAuthorizationUrl,gt as categoryMapPath,Nr as classify,ko as clearTokens,vt as computeSyncStatus,Ut as createSession,_t as createV3RemoteProvider,I as d360Get,N as d360GetAll,ft as d360Patch,V as d360Post,mt as d360Upload,lt as decodeJwtClaims,$ as ensureDir,jt as estimateBulkCost,tt as exchangeCode,Hs as findByName,wt as findPathByArticleId,Zs as generateTitle,dt as getAccessToken,Do as getArticle,Ws as getSession,H as hashContent,K as hashRemote,Pt as inventoryRepo,Te as isExpired,Ar as knownEnvironments,Pn as listSessions,Eo as listWorkspaces,M as loadProfileMap,Y as loadSessions,se as loadTokens,je as logApiCall,Ce as logBodiesAlways,ie as loggableBody,mo as loginPkce,Dr as normalizeMarkdown,rr as packageRoot,Ve as packageSkillsDir,Hn as partitionByCategory,zn as partitionEvenly,sr as pickModel,Ti as planPartitions,pi as planPull,ke as projectConfigPath,Ke as projectSkillsDir,Ze as readMcpConfig,W as readProjectConfig,ve as readUserConfig,Fn as reconstructLocalMarkdown,le as recordSyncBase,_r as redactSecrets,nt as refreshTokens,Ks as relativeTime,Js as renameSession,B as resolveActiveProfile,Ns as resolveAuth,ue as resolveConnection,Ae as resolveCrossLinks,bt as resolveDocsRoot,No as resolveEnvironment,io as resolveModelSetting,Ye as resolveProfile,G as resolveProjectId,Ei as runPartitioned,ct as saveTokens,we as sessionsFilePath,Fo as setProfileProject,Gs as setTitle,Vs as slugify,Bt as splitFrontmatter,ti as suggestNextAction,ot as toStoredTokens,zs as touchSession,Ri as trackedArticlePaths,vr as truncateBody,Fs as upsertSession,he as userConfigPath,E as userDir,ye as userMcpConfigPath,ao as writeMcpConfig,Qe as writeProjectConfig,so as writeUserConfig};
28
+ `):t.newContent;Nn(qn(n),{recursive:!0}),Bn(n,o,"utf8"),de(e.cwd,r.name,t.path,{id:t.articleId,localHash:H(o),remoteContentHash:t.remoteContentHash,remoteVersion:t.latestVersion,remoteModifiedAt:t.modifiedAt})}var Hn=12,Wt=e=>e.replace(/\\/g,"/");function zn(e){let t=new Map;for(let r of e){let n=Wt(r),o=n.lastIndexOf("/"),s=o>=0?n.slice(0,o):"(root)",i=t.get(s);i||(i=[],t.set(s,i)),i.push(n)}return[...t.entries()].map(([r,n])=>({label:r,paths:n.sort()})).sort((r,n)=>r.label.localeCompare(n.label))}function Jn(e,t){let r=Math.max(1,t),n=e.map(Wt).sort(),o=[];for(let s=0;s<n.length;s+=r)o.push(n.slice(s,s+r));return o.map((s,i)=>({label:`chunk ${i+1}/${o.length}`,paths:s}))}function ji(e,t){let r=t?.maxPerPartition??Hn,n=[];for(let o of zn(e)){if(o.paths.length<=r){n.push(o);continue}let s=Jn(o.paths,r);s.forEach((i,a)=>n.push({label:`${o.label} (${a+1}/${s.length})`,paths:i.paths}))}return n}function Ei(e,t){let r;try{r=F(e,t).name}catch{return[]}let n=$(e,r);return n?Object.keys(n.articles):[]}var Gn=e=>Nt(e);async function*Ii(e){let{cwd:t,partitions:r,promptFor:n,signal:o}=e,s=e.spawn??Gn,i=Math.max(1,Math.min(e.concurrency??3,Math.max(1,r.length))),a=[],d=[],u=null,l=h=>{d.push(h);let w=u;u=null,w?.()},f=async h=>{let w=r[h];l({type:"partition_status",index:h,label:w.label,status:"running"});let R=s({cwd:t,profileName:e.profileName,allowProdWrites:e.allowProdWrites,model:e.model}),z=0,C=0,Q=!1,j,U=()=>{R.interrupt?.()};o?.addEventListener("abort",U,{once:!0});try{for await(let T of R.send(n(w)))l({type:"partition_event",index:h,label:w.label,event:T}),T.type==="result"?(z+=T.costUsd,C+=T.outputTokens,Q=T.ok):T.type==="error"&&(j=T.message)}catch(T){j=T instanceof Error?T.message:String(T)}finally{o?.removeEventListener("abort",U);try{R.close()}catch{}}o?.aborted&&!j&&(j="aborted");let J=Q&&!j;a.push({index:h,label:w.label,ok:J,costUsd:z,outputTokens:C,error:j}),l({type:"partition_status",index:h,label:w.label,status:J?"done":"failed"})},p=0,y=(async()=>{let h=async()=>{for(;;){if(o?.aborted)return;let w=p++;if(w>=r.length)return;await f(w)}};await Promise.all(Array.from({length:i},()=>h()))})(),b=!1;for(y.then(()=>{b=!0;let h=u;u=null,h?.()});;){if(d.length>0){yield d.shift();continue}if(b)break;await new Promise(h=>{u=h})}a.sort((h,w)=>h.index-w.index),yield{type:"run_done",ok:!o?.aborted&&a.length>0&&a.every(h=>h.ok),aborted:o?.aborted??!1,totalCostUsd:a.reduce((h,w)=>h+w.costUsd,0),results:a}}var Vn={light:"claude-sonnet-4-6",standard:"claude-sonnet-4-6",deep:"claude-opus-4-8",background:"claude-haiku-4-5"};function Kn(e,t){let r=e&&e!=="auto"?e:void 0;return r?{model:r,tier:t,forced:!0}:{model:Vn[t],tier:t,forced:!1}}function $i(e,t){let r=M(e)?.defaultModel??te().defaultModel;return Kn(r,t)}export{Bs as AUTH_MODES,le as D360ApiError,B as D360AuthError,Hn as DEFAULT_MAX_PER_PARTITION,Le as EngineSession,ee as ProfileConfigError,Vn as TIER_MODEL,br as apiLogDir,gi as applyPull,lr as buildAuthorizationUrl,ht as categoryMapPath,Nr as classify,vo as clearTokens,St as computeSyncStatus,Nt as createSession,vt as createV3RemoteProvider,O as d360Get,q as d360GetAll,mt as d360Patch,K as d360Post,gt as d360Upload,ut as decodeJwtClaims,L as ensureDir,Et as estimateBulkCost,rt as exchangeCode,Gs as findByName,kt as findPathByArticleId,ti as generateTitle,ft as getAccessToken,Mo as getArticle,Js as getSession,H as hashContent,Y as hashRemote,Pt as inventoryRepo,Re as isExpired,Ar as knownEnvironments,Pn as listSessions,Io as listWorkspaces,$ as loadProfileMap,Z as loadSessions,ae as loadTokens,Ee as logApiCall,je as logBodiesAlways,ce as loggableBody,yo as loginPkce,Dr as normalizeMarkdown,rr as packageRoot,Ke as packageSkillsDir,zn as partitionByCategory,Jn as partitionEvenly,sr as pickModel,Kn as pickOperationModel,ji as planPartitions,mi as planPull,_e as projectConfigPath,Ye as projectSkillsDir,Xe as readMcpConfig,M as readProjectConfig,te as readUserConfig,Wn as reconstructLocalMarkdown,de as recordSyncBase,_r as redactSecrets,ot as refreshTokens,Zs as relativeTime,Ks as renameSession,F as resolveActiveProfile,qs as resolveAuth,pe as resolveConnection,Ie as resolveCrossLinks,_t as resolveDocsRoot,qo as resolveEnvironment,$i as resolveModelForOperation,lo as resolveModelSetting,Qe as resolveProfile,V as resolveProjectId,Ii as runPartitioned,lt as saveTokens,be as sessionsFilePath,zo as setProfileProject,Ys as setTitle,Qs as slugify,Bt as splitFrontmatter,oi as suggestNextAction,st as toStoredTokens,Vs as touchSession,Ei as trackedArticlePaths,vr as truncateBody,zs as upsertSession,we as userConfigPath,A as userDir,ke as userMcpConfigPath,uo as writeMcpConfig,Ze as writeProjectConfig,co as writeUserConfig};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Model right-sizing (plans/model-right-sizing.md) — the engine picks the model per OPERATION
3
+ * so mechanical work (DFM conversion) runs on Sonnet and reasoning work (gap analysis) on Opus,
4
+ * instead of one global session model that overpays on everything.
5
+ *
6
+ * Precedence: a d360-writer model force (project/user `defaultModel`, set via /model) wins; otherwise
7
+ * the operation's tier default. env / Claude-settings / unset are NOT a force here — operations
8
+ * right-size unless the user explicitly forced a model. (Free-form chat still uses the session
9
+ * default; this only governs engine-launched operations — see the plan's per-session constraint.)
10
+ */
11
+ export type OperationTier = 'light' | 'standard' | 'deep' | 'background';
12
+ /** Tier → model id. Full ids (not aliases) so cost pricing + the SDK agree. Sonnet is the
13
+ reader-facing floor; Haiku is reserved for `background` only. */
14
+ export declare const TIER_MODEL: Record<OperationTier, string>;
15
+ export type OperationModel = {
16
+ /** The model id to run this operation at. */
17
+ model: string;
18
+ tier: OperationTier;
19
+ /** True when a user `/model` force was applied (overriding the tier); false when right-sized. */
20
+ forced: boolean;
21
+ };
22
+ /**
23
+ * Pure resolution: a forced model (anything other than empty/`auto`) wins; else the tier default.
24
+ * `auto` and empty mean "not forced — right-size."
25
+ */
26
+ export declare function pickOperationModel(forcedModel: string | undefined | null, tier: OperationTier): OperationModel;
27
+ /** Resolve the model an operation of `tier` should run at, reading the d360-writer model force from config. */
28
+ export declare function resolveModelForOperation(cwd: string, tier: OperationTier): OperationModel;
@@ -19,12 +19,15 @@ import type { Partition } from './partition.js';
19
19
  /** The slice of EngineSession the orchestrator needs — keeps the session factory injectable for tests. */
20
20
  export interface PartitionWorker {
21
21
  send(text: string): AsyncIterable<EngineEvent>;
22
+ /** Interrupt the in-flight turn (for Esc-abort). Optional; EngineSession provides it. */
23
+ interrupt?(): void | Promise<void>;
22
24
  close(): void;
23
25
  }
24
26
  export type SpawnWorker = (opts: {
25
27
  cwd: string;
26
28
  profileName?: string;
27
29
  allowProdWrites?: boolean;
30
+ model?: string;
28
31
  }) => PartitionWorker;
29
32
  export type PartitionStatus = 'running' | 'done' | 'failed';
30
33
  export type PartitionResult = {
@@ -50,6 +53,7 @@ export type PartitionedRunEvent = {
50
53
  } | {
51
54
  type: 'run_done';
52
55
  ok: boolean;
56
+ aborted: boolean;
53
57
  totalCostUsd: number;
54
58
  results: PartitionResult[];
55
59
  };
@@ -62,6 +66,10 @@ export type RunPartitionedOptions = {
62
66
  concurrency?: number;
63
67
  profileName?: string;
64
68
  allowProdWrites?: boolean;
69
+ /** Model each partition session runs at (right-sized by the caller, e.g. /convert → Sonnet). */
70
+ model?: string;
71
+ /** Abort the run (Esc): stop launching new partitions and interrupt the active ones. */
72
+ signal?: AbortSignal;
65
73
  /** Test seam: spawn a worker. Defaults to a real engine session. */
66
74
  spawn?: SpawnWorker;
67
75
  };
package/dist/session.d.ts CHANGED
@@ -43,6 +43,9 @@ export type CreateSessionOptions = {
43
43
  profileName?: string;
44
44
  /** Authorize writes on a production profile (terminal: /allow-prod, one-shot: --yes). */
45
45
  allowProdWrites?: boolean;
46
+ /** Explicit model for this session — overrides the config-resolved model. Callers that
47
+ right-size per operation (model-policy) pass the resolved model here; the REPL omits it. */
48
+ model?: string;
46
49
  };
47
50
  /**
48
51
  * A persistent conversation with the documentation agent. `send()` streams typed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document360-engine",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Headless documentation agent engine for document360-writer / -desktop. Emits a typed event stream; no UI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",