@atoms-tech/atoms-mcp 0.12.17 → 0.13.2

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.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import e,{readFile as t,mkdir as r,writeFile as i,unlink as n}from"fs/promises";import{homedir as a}from"os";import o,{join as s}from"path";import{createClient as d}from"@supabase/supabase-js";import{createServer as c}from"http";import l,{randomUUID as m,randomBytes as p,createHash as u}from"crypto";import{fileURLToPath as _,URL as f}from"url";import{z as h}from"zod";import{registerAppTool as g,registerAppResource as y,RESOURCE_MIME_TYPE as b}from"@modelcontextprotocol/ext-apps/server";import{McpServer as w}from"@modelcontextprotocol/sdk/server/mcp.js";import{createRequire as v}from"module";import{StdioServerTransport as j}from"@modelcontextprotocol/sdk/server/stdio.js";var x,S,k,I,E,O,T,A=Object.defineProperty,H=Object.getOwnPropertyNames,D=(e,t)=>function(){return e&&(t=(0,e[H(e)[0]])(e=0)),t},U=(e,t)=>{for(var r in t)A(e,r,{get:t[r],enumerable:!0})};function C(e){return T.get(e)}var R=D({"src/core/tool-catalog.ts"(){x=new Set(["atoms_status","atoms_list_projects"]),S=new Set(["atoms_create_item","atoms_update_item","atoms_delete_item","atoms_restore_item","atoms_link_items","atoms_bulk_import","atoms_record_test_result","atoms_record_test_results","atoms_create_variable","atoms_update_variable","atoms_delete_variable","atoms_create_domain","atoms_update_domain","atoms_delete_domain","atoms_create_baseline","atoms_lock_baseline","atoms_restore_baseline"]),k=new Set(["atoms_delete_item","atoms_delete_variable","atoms_delete_domain","atoms_restore_baseline"]),I=new Set(["atoms_bulk_import"]),E={items:["atoms_list_items","atoms_get_item","atoms_search","atoms_semantic_search","atoms_browse","atoms_get_history","atoms_create_item","atoms_update_item","atoms_delete_item","atoms_restore_item","atoms_bulk_import"],traceability:["atoms_trace","atoms_link_items","atoms_export_mermaid","atoms_impact_analysis"],coverage:["atoms_get_coverage","atoms_record_test_result","atoms_record_test_results","atoms_project_summary","atoms_list_assets"],variables:["atoms_list_variables","atoms_get_variable","atoms_create_variable","atoms_update_variable","atoms_delete_variable"],domains:["atoms_list_domains","atoms_create_domain","atoms_update_domain","atoms_delete_domain"],baselines:["atoms_list_baselines","atoms_create_baseline","atoms_lock_baseline","atoms_diff_baselines","atoms_restore_baseline"]},O=Object.keys(E),T=new Map;for(const[e,t]of Object.entries(E))for(const r of t)T.set(r,e)}});function P(e,t){if(x.has(t))return{allowed:!0};if(e.lockdown&&!new Set(["atoms_list_items","atoms_get_item","atoms_search","atoms_browse"]).has(t))return{allowed:!1,reason:"lockdown"};if(e.forbidden_tools.includes(t))return{allowed:!1,reason:"forbidden_tool"};if(e.read_only&&S.has(t))return{allowed:!1,reason:"read_only"};if(e.block_destructive&&k.has(t))return{allowed:!1,reason:"block_destructive"};if(e.block_bulk_import&&I.has(t))return{allowed:!1,reason:"block_bulk_import"};const r=C(t);return void 0===r||e.allowed_toolsets.includes(r)?{allowed:!0}:{allowed:!1,reason:"toolset_disabled"}}function M(e,t){return P(e,t).allowed}function $(e){switch(e){case"lockdown":return"MCP is in lockdown mode for your role — only read-only browse is available.";case"read_only":return"Your org policy puts MCP in read-only mode. Mutating tools are not exposed.";case"block_destructive":return"Your org policy blocks destructive operations (delete tools).";case"block_bulk_import":return"Your org policy blocks bulk import.";case"forbidden_tool":return"This tool is on your org's forbidden list.";case"toolset_disabled":return"This tool's toolset is not enabled in your org policy."}}var q,N,W,L=D({"src/core/policy.ts"(){R()}}),V=D({"src/core/types.ts"(){q={read_only:!1,lockdown:!1,block_destructive:!1,block_bulk_import:!1,cross_project_browse:!0,allowed_toolsets:["items","traceability","coverage","variables","domains","baselines"],forbidden_tools:[],project_scope:null,require_confirmation_for:[]}}});function B(){return N}var F=D({"src/middleware/session-policy.ts"(){V(),N=q,W=null}});function z(){const e=process.env.ATOMS_MCP_TOOLSETS?.trim();if(!e)return null;const t=e.split(",").map(e=>e.trim()).filter(Boolean);if(0===t.length)return null;const r=new Set,i=[];for(const e of t)O.includes(e)?r.add(e):i.push(e);return{allowed:r,unknown:i}}function J(){const e=process.env.ATOMS_MCP_READ_ONLY;return"1"===e||"true"===e?.toLowerCase()}function Q(e){if(J()&&S.has(e))return!1;if(x.has(e))return!0;const t=z();if(null===t)return M(B(),e);const r=C(e);return!!r&&!!t.allowed.has(r)&&M(B(),e)}var G,Y,K=D({"src/middleware/tool-registry.ts"(){R(),L(),F()}}),Z={};async function X(){try{const e=await t(Y,"utf-8"),r=JSON.parse(e);return r.access_token&&r.refresh_token?r:null}catch{return null}}async function ee(e){await r(G,{recursive:!0,mode:448}),await i(Y,JSON.stringify(e,null,2),{mode:384})}async function te(){const e=await X();return!!e&&e.expires_at>Date.now()+6e4}function re(e){return e.expires_at>Date.now()+6e4}async function ie(){try{await n(Y)}catch{}}function ne(){return Y}U(Z,{clearCredentials:()=>ie,getCredentialsPath:()=>ne,hasValidCredentials:()=>te,isTokenValid:()=>re,readCredentials:()=>X,writeCredentials:()=>ee});var ae,oe,se,de=D({"src/auth/token-store.ts"(){G=s(a(),".atoms"),Y=s(G,"credentials.json")}}),ce={};U(ce,{ATOMS_APP_URL:()=>se,ATOMS_SUPABASE_ANON_KEY:()=>oe,ATOMS_SUPABASE_URL:()=>ae});var le=D({"src/config.ts"(){ae="https://gmebjyhomsbvhrxffzre.supabase.co",oe="sb_publishable_b49MUAB8XCrQiF7x5b9lUA_w1aplSBH",se=process.env.ATOMS_APP_URL??"https://atoms.tech"}}),me={};async function pe(){const e=process.env.ATOMS_ACCESS_TOKEN;if(e){const t=ue(e);return{access_token:e,refresh_token:"",user_id:t.sub,email:t.email??""}}const t=await X();if(!t)throw new Error("Not authenticated. Run 'npx @atoms-tech/atoms-mcp login' to connect your ATOMS account.");if(t.expires_at>Date.now()+6e4){const e=ue(t.access_token);return{access_token:t.access_token,refresh_token:t.refresh_token,user_id:e.sub,email:e.email??t.user_email??""}}process.stderr.write("[atoms-mcp] Refreshing access token...\n");const r=d(ae,oe),{data:i,error:n}=await r.auth.refreshSession({refresh_token:t.refresh_token});if(n||!i.session)throw new Error(`Token refresh failed: ${n?.message??"No session returned"}. Re-run 'npx @atoms-tech/atoms-mcp login'.`);const a=i.session;return await ee({access_token:a.access_token,refresh_token:a.refresh_token,expires_at:Date.now()+1e3*(a.expires_in??3600),user_email:a.user?.email}),{access_token:a.access_token,refresh_token:a.refresh_token,user_id:a.user?.id??"",email:a.user?.email??""}}function ue(e){const t=e.split(".");if(3!==t.length)throw new Error("Invalid JWT format");const r=t[1].replace(/-/g,"+").replace(/_/g,"/").padEnd(t[1].length+(4-t[1].length%4)%4,"="),i=JSON.parse(Buffer.from(r,"base64").toString("utf-8"));if(!i.sub)throw new Error("JWT missing 'sub' claim");return i}U(me,{getValidToken:()=>pe});var _e,fe,he,ge,ye,be=D({"src/auth/refresh.ts"(){de(),le()}}),we={};async function ve(){if(_e)try{const e=await X();e&&e.expires_at>ye&&(process.stderr.write("[atoms-mcp] Credentials file refreshed elsewhere — rebuilding client.\n"),je())}catch{}if(_e&&ge>Date.now()+6e4)return _e;_e&&process.stderr.write("[atoms-mcp] Token expiring, refreshing client...\n");const{access_token:e,user_id:t,email:r}=await pe(),i=e.split(".");if(3===i.length)try{const e=JSON.parse(Buffer.from(i[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString());ge=1e3*(e.exp??0)}catch{ge=Date.now()+36e5}try{const e=await X();ye=e?.expires_at??0}catch{ye=0}const n=d(ae,oe,{global:{headers:{Authorization:`Bearer ${e}`}}});return fe=t,he=r,_e=n}function je(){_e=null,fe=null,he=null,ge=0,ye=0}function xe(e){let t="";if(e instanceof Error)t=e.message;else if(e&&"object"==typeof e){const r=e;t="string"==typeof r.message?r.message:"string"==typeof r.code?r.code:String(e)}else null!=e&&(t=String(e));return/jwt\s*expired|invalid.?jwt|token.*(?:has\s+)?expired|jwt.*invalid|pgrst301/i.test(t)}async function Se(e){try{return await e()}catch(t){if(!xe(t))throw t;return process.stderr.write("[atoms-mcp] Data tier rejected JWT — invalidating client and retrying once.\n"),je(),await e()}}function ke(){if(!fe)throw new Error("Not authenticated. Call getClient() first.");return fe}function Ie(){return he??""}async function Ee(e,t){const r=ke(),{data:i,error:n}=await e.from("projects").select("org_id").eq("id",t).maybeSingle();if(n||!i)throw new Error(`Project '${t}' not found`);const{data:a,error:o}=await e.from("org_members").select("role").eq("user_id",r).eq("org_id",i.org_id).maybeSingle();if(o||!a){const{data:t}=await e.rpc("is_platform_admin");if(!0===t)return"admin";throw new Error("Not a member of this project's organization")}if("viewer"===a.role)throw new Error("VIEWER_ROLE");return a.role}U(we,{getClient:()=>ve,getUserEmail:()=>Ie,getUserId:()=>ke,invalidateClient:()=>je,isJwtExpiredError:()=>xe,requireWriteAccess:()=>Ee,withJwtRetry:()=>Se});var Oe,Te,Ae=D({"src/db/client.ts"(){be(),de(),le(),_e=null,fe=null,he=null,ge=0,ye=0}}),He={};async function De(){const e=p(32).toString("base64url"),t=u("sha256").update(e).digest("base64url"),r=`http://localhost:${Oe}${Te}`,i=new f(`${se}/auth/mcp-consent`);return i.searchParams.set("redirect_uri",r),i.searchParams.set("code_challenge",t),i.searchParams.set("code_challenge_method","S256"),new Promise((t,r)=>{let n;function a(){clearTimeout(n)}const o=c(async(i,n)=>{const s=new f(i.url??"/",`http://localhost:${Oe}`);if(s.pathname!==Te)return n.writeHead(404),void n.end("Not found");try{if("access_denied"===s.searchParams.get("error"))return n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ce("Authorization was cancelled.")),a(),o.close(),void r(new Error("Authorization cancelled by user."));const i=s.searchParams.get("code");if(i){const s=await fetch(`${ae}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{"Content-Type":"application/json",apikey:oe},body:JSON.stringify({auth_code:i,code_verifier:e})}),d=await s.json();if(!s.ok||!d.access_token){const e=d.error_description??d.msg??"Unknown error";return n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ce(`Code exchange failed: ${e}`)),a(),o.close(),void r(new Error(`Code exchange failed: ${e}`))}let c="authenticated";try{c=JSON.parse(Buffer.from(d.access_token.split(".")[1],"base64").toString()).email??c}catch{}const{createClient:l}=await import("@supabase/supabase-js"),m=l(ae,oe,{global:{headers:{Authorization:`Bearer ${d.access_token}`}}}),{data:p,error:u}=await m.from("org_members").select("org_id").limit(1);return u||!p||0===p.length?(n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ce('No ATOMS account found for this Google account.<br><br>Please sign up at <a href="https://atoms.tech">atoms.tech</a> first, then run this login command again.')),a(),o.close(),void r(new Error("No ATOMS account found. Sign up at atoms.tech first."))):(await ee({access_token:d.access_token,refresh_token:d.refresh_token,expires_at:Date.now()+1e3*(d.expires_in??3600),user_email:c}),n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ue(c)),a(),o.close(),void t({email:c}))}const d=s.searchParams.get("access_token"),c=s.searchParams.get("refresh_token"),l=parseInt(s.searchParams.get("expires_in")??"3600",10);d&&c?(await ee({access_token:d,refresh_token:c,expires_at:Date.now()+1e3*l}),n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ue("authenticated")),a(),o.close(),t({email:"authenticated"})):(n.writeHead(200,{"Content-Type":"text/html"}),n.end("<!DOCTYPE html>\n<html>\n<head><title>ATOMS MCP Login</title></head>\n<body>\n <h1>Completing login...</h1>\n <script>\n const hash = window.location.hash.substring(1);\n const params = new URLSearchParams(hash);\n const data = {\n access_token: params.get('access_token'),\n refresh_token: params.get('refresh_token'),\n expires_in: params.get('expires_in'),\n user_email: ''\n };\n if (data.access_token) {\n // Decode JWT to get email\n try {\n const payload = JSON.parse(atob(data.access_token.split('.')[1]));\n data.user_email = payload.email || '';\n } catch(e) {}\n fetch('/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then(() => {\n document.body.innerHTML = '<h1>Login successful! You can close this tab.</h1>';\n }).catch(() => {\n document.body.innerHTML = '<h1>Login failed. Please try again.</h1>';\n });\n } else {\n document.body.innerHTML = '<h1>Login failed — no tokens received.</h1>';\n }\n <\/script>\n</body>\n</html>"))}catch(e){n.writeHead(500),n.end("Authentication failed"),a(),o.close(),r(e)}});o.on("request",async(e,i)=>{if("POST"===e.method&&"/token"===e.url){let n="";e.on("data",e=>{n+=e.toString()}),e.on("end",async()=>{try{const e=JSON.parse(n);await ee({access_token:e.access_token,refresh_token:e.refresh_token,expires_at:Date.now()+1e3*(e.expires_in??3600),user_email:e.user_email}),i.writeHead(200,{"Content-Type":"application/json"}),i.end(JSON.stringify({ok:!0})),a(),o.close(),t({email:e.user_email??"authenticated"})}catch(e){i.writeHead(500),i.end("Failed to store credentials"),a(),o.close(),r(e)}})}}),o.listen(Oe,"127.0.0.1",async()=>{process.stderr.write("\nOpening browser for ATOMS login...\n"),process.stderr.write(`If the browser doesn't open, visit:\n${i.toString()}\n\n`);try{const{default:e}=await import("open");await e(i.toString())}catch{process.stderr.write("Could not open browser automatically. Please open the URL above.\n")}}),n=setTimeout(()=>{o.close(),r(new Error("Login timed out after 60 seconds. Run 'npx @atoms-tech/atoms-mcp login' to try again."))},6e4)})}function Ue(e){return`<!DOCTYPE html>\n<html>\n<head>\n <title>ATOMS MCP — Authorized</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n background: #0a0a0a;\n color: #fafafa;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n background: #171717;\n border: 1px solid #262626;\n border-radius: 16px;\n padding: 48px;\n max-width: 480px;\n width: 100%;\n text-align: center;\n }\n .icon {\n width: 56px;\n height: 56px;\n background: #052e16;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 24px;\n }\n .icon svg { width: 28px; height: 28px; }\n h1 {\n font-size: 22px;\n font-weight: 600;\n margin-bottom: 8px;\n color: #22c55e;\n }\n .email {\n font-size: 14px;\n color: #a3a3a3;\n margin-bottom: 32px;\n }\n .permissions {\n text-align: left;\n background: #0a0a0a;\n border: 1px solid #262626;\n border-radius: 12px;\n padding: 20px;\n margin-bottom: 24px;\n }\n .permissions h3 {\n font-size: 12px;\n font-weight: 600;\n color: #737373;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 12px;\n }\n .perm-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 6px 0;\n font-size: 14px;\n color: #d4d4d4;\n }\n .perm-item .dot {\n width: 6px;\n height: 6px;\n background: #22c55e;\n border-radius: 50%;\n flex-shrink: 0;\n }\n .closing {\n font-size: 13px;\n color: #525252;\n }\n .closing span { color: #a3a3a3; font-variant-numeric: tabular-nums; }\n </style>\n</head>\n<body>\n <div class="card">\n <div class="icon">\n <svg fill="none" viewBox="0 0 24 24" stroke="#22c55e" stroke-width="2.5">\n <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>\n </svg>\n </div>\n <h1>Authorization Granted</h1>\n <p class="email">${e}</p>\n\n <div class="permissions">\n <h3>ATOMS MCP can now</h3>\n <div class="perm-item"><span class="dot"></span> Read your projects and requirements</div>\n <div class="perm-item"><span class="dot"></span> Create and edit requirements and test cases</div>\n <div class="perm-item"><span class="dot"></span> Record test results (pass/fail)</div>\n <div class="perm-item"><span class="dot"></span> Manage traceability relationships</div>\n <div class="perm-item"><span class="dot"></span> Export coverage reports and diagrams</div>\n </div>\n\n <p class="closing">This tab will close in <span id="countdown">5</span>s</p>\n </div>\n\n <script>\n let seconds = 5;\n const el = document.getElementById('countdown');\n const timer = setInterval(() => {\n seconds--;\n el.textContent = seconds;\n if (seconds <= 0) {\n clearInterval(timer);\n window.close();\n // If window.close() is blocked (not opened by script), update message\n setTimeout(() => {\n document.querySelector('.closing').textContent = 'You can close this tab now.';\n }, 500);\n }\n }, 1000);\n <\/script>\n</body>\n</html>`}function Ce(e){return`<!DOCTYPE html>\n<html>\n<head>\n <title>ATOMS MCP — Failed</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n background: #0a0a0a;\n color: #fafafa;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n background: #171717;\n border: 1px solid #262626;\n border-radius: 16px;\n padding: 48px;\n max-width: 480px;\n width: 100%;\n text-align: center;\n }\n .icon {\n width: 56px;\n height: 56px;\n background: #450a0a;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 24px;\n }\n .icon svg { width: 28px; height: 28px; }\n h1 { font-size: 22px; font-weight: 600; margin-bottom: 16px; color: #ef4444; }\n .reason { font-size: 14px; color: #a3a3a3; line-height: 1.6; }\n .reason a { color: #7c3aed; text-decoration: underline; }\n </style>\n</head>\n<body>\n <div class="card">\n <div class="icon">\n <svg fill="none" viewBox="0 0 24 24" stroke="#ef4444" stroke-width="2.5">\n <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>\n </svg>\n </div>\n <h1>Authorization Failed</h1>\n <p class="reason">${e}</p>\n </div>\n</body>\n</html>`}U(He,{login:()=>De});var Re,Pe,Me,$e,qe=D({"src/auth/login.ts"(){de(),le(),Oe=19275,Te="/callback"}});function Ne(e,t=0){if(t>5)return"[depth-limit]";if("string"==typeof e){for(const t of Me)if(t.test(e.trim()))return"[redacted]";return e.length>$e?e.slice(0,$e)+"…":e}if(Array.isArray(e))return e.slice(0,20).map(e=>Ne(e,t+1));if(null!==e&&"object"==typeof e){const r={};for(const[i,n]of Object.entries(e)){const e=i.toLowerCase();Re.has(e)?r[i]="[redacted]":Pe.has(e)?r[i]="string"==typeof n?"[content]":Ne(n,t+1):r[i]=Ne(n,t+1)}return r}return e}function We(e){return Ne(e)}var Le,Ve,Be,Fe,ze,Je=D({"src/middleware/audit.ts"(){Re=new Set(["access_token","refresh_token","password","api_key","secret","token","authorization","credential","credentials","private_key"]),Pe=new Set(["body","description","content","notes","summary","title","system_prompt","prompt"]),Me=[/^[^\s@]+@[^\s@]+\.[^\s@]+$/,/^bearer\s+\S+$/i,/^(sk|pk|npm|gh|glpat|xoxb|xoxp|rk|SG\.)[-_][A-Za-z0-9_-]{16,}/,/^eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/],$e=200}}),Qe=D({"src/middleware/rate-limiter.ts"(){Le=60,Ve=6e4}});function Ge(){const e=process.env.ATOMS_MCP_PROJECT_ID?.trim();return e?Be.test(e)?e:(Fe||(process.stderr.write(`[atoms-mcp] WARNING: ATOMS_MCP_PROJECT_ID="${e.slice(0,50)}" is not a valid UUID — scope ignored.\n`),Fe=!0),null):null}var Ye,Ke=D({"src/middleware/project-scope.ts"(){Be=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Fe=!1,ze=class extends Error{expectedProjectId;receivedProjectId;constructor(e,t){super("Tool call rejected: project_id is outside the configured ATOMS_MCP_PROJECT_ID scope."),this.name="ProjectScopeError",this.expectedProjectId=e,this.receivedProjectId=t}}}});function Ze(e){const t=function(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")}(e??"");return t?t.includes("cursor")?"cursor":t.includes("copilot")?"copilot":t.includes("claude")?Ye:t:Ye}var Xe,et,tt,rt,it,nt,at=D({"src/core/client-identity.ts"(){Ye="claude_desktop"}}),ot=D({"src/core/timestamps.ts"(){Xe=h.string().datetime({offset:!0})}}),st=D({"src/core/errors.ts"(){}}),dt={};function ct(e,t=tt){if("string"!=typeof e)return"";const r=e.replace(it,"").replace(nt,"");return r.length<=t?r:r.slice(0,t)+"..."}function lt(e){return"string"!=typeof e?"":e.trim().toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function mt(e,t){return{status:"success",data:e,...t?{meta:t}:{}}}function pt(e,t,r){return{total_count:e,limit:t,offset:r,has_more:r+t<e}}function ut(e,t){return{status:"error",message:e,next_steps:t}}function _t(e,t){return ut(`${e} '${ct(t)}' not found`,[`Verify the ${e.toLowerCase()} ID is correct`,"Use atoms_list_items or atoms_search to find valid IDs"])}function ft(e){return ut(`${e} role cannot modify project data`,["Contact your org admin to upgrade your role to editor","Use read-only tools (atoms_list_items, atoms_get_item, atoms_search) instead"])}function ht(e){return ut(`Validation error: ${ct(e,rt)}`,["Check the parameter types and constraints in the tool description","Ensure all required parameters are provided"])}function gt(){return ut("Not authenticated. Run 'npx @atoms-tech/atoms-mcp login' first.",["Run: npx @atoms-tech/atoms-mcp login","Or set ATOMS_ACCESS_TOKEN environment variable"])}function yt(){return ut("Your ATOMS session token expired and could not be refreshed automatically.",["Call atoms_status() — it will refresh the token if possible","If that still reports 'expired', restart Claude Desktop (quit and reopen)","If the problem persists, run: npx @atoms-tech/atoms-mcp logout && npx @atoms-tech/atoms-mcp login"])}function bt(e){return ut("Tool call rejected: project_id is outside the configured ATOMS_MCP_PROJECT_ID scope.",[`This MCP session is bound to project ${e}`,"Pass that project_id, or restart without ATOMS_MCP_PROJECT_ID to access other projects"])}function wt(e){return ut("Rate limited: too many requests to ATOMS API.",[e?`Wait ${e} seconds before retrying.`:"Wait a few seconds before retrying.","Reduce the frequency of back-to-back tool calls"])}function vt(e){return ut(`Parse error: ${ct(e,rt)}`,["Check that body/title/summary fields contain valid text (no unexpected control characters)","If the content was generated programmatically, validate the string before passing it","Simplify the content and retry — if it succeeds, the original content has a malformed character"])}function jt(e){return function(e){return/stale_item_write|sqlstate\s*40001|could not serialize access/i.test(e)}(e)}function xt(){return ut("Stale write rejected: this item changed after it was read.",["Call atoms_get_item again and use the returned metadata.updated_at value","Review the current item before retrying so you do not overwrite newer work","Retry atoms_update_item or atoms_link_items with the fresh expected timestamp"])}function St(e){const t=e instanceof Error?e.message:String(e);return/not authenticated/i.test(t)?gt():function(e){let t="";if(e instanceof Error)t=e.message;else if(e&&"object"==typeof e){const r=e;t="string"==typeof r.message?r.message:"string"==typeof r.code?r.code:String(e)}else null!=e&&(t=String(e));return/jwt\s*expired|invalid.?jwt|token.*(?:has\s+)?expired|jwt.*invalid|pgrst301/i.test(t)}(e)?yt():/rate.?limit|too many requests|429/i.test(t)?wt():jt(t)?xt():/invalid input syntax|invalid json|unexpected token|malformed|json parse/i.test(t)?vt(t):kt(t)}function kt(e){return/row.level security|permission denied for/i.test(e)?ut("Permission denied: your account does not have write access to this project.",["Verify you have editor or admin role in this project","Contact your org admin to upgrade your permissions","Use atoms_list_projects to confirm the project_id is one you have access to"]):ut(`Database error: ${ct(e,rt)}`,["This may be a temporary issue — try again","If the error persists, check that the project_id is valid"])}function It(e){return null==e?e:`<user_content>${e}</user_content>`}function Et(e){const t=JSON.stringify(e,null,2);if(t.length>et&&"success"===e.status&&Array.isArray(e.data)){const t=Math.max(1,Math.floor(e.data.length/2)),r={...e,data:e.data.slice(0,t),meta:{...e.meta,truncated:!0,truncation_message:`Response truncated from ${e.data.length} to ${t} items. Use 'offset' parameter or add filters to see more results.`}};return{content:[{type:"text",text:JSON.stringify(r,null,2)}],structuredContent:r}}return{content:[{type:"text",text:t}],structuredContent:e}}function Ot(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}],structuredContent:e,isError:!0}}U(dt,{CHARACTER_LIMIT:()=>et,accessDeniedError:()=>ft,authError:()=>gt,classifyError:()=>St,dbError:()=>kt,errorResponse:()=>ut,formatErrorResult:()=>Ot,formatToolResult:()=>Et,isStaleWriteErrorMessage:()=>jt,jwtExpiredError:()=>yt,notFoundError:()=>_t,paginationMeta:()=>pt,parseError:()=>vt,rateLimitError:()=>wt,sanitizeForEcho:()=>ct,sanitizeTagOrLevel:()=>lt,scopeError:()=>bt,staleWriteError:()=>xt,success:()=>mt,validationError:()=>ht,wrapUserContent:()=>It});var Tt,At=D({"src/tools/_base.ts"(){st(),et=25e3,tt=50,rt=200,it=new RegExp("[\\u0000-\\u001F\\u007F]","g"),nt=new RegExp("[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\uFEFF]","g")}});var Ht=D({"src/apps/register.ts"(){Tt={resourceDomains:["fonts.googleapis.com","fonts.gstatic.com"]}}});function Dt(e){return function(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}(`*${t=e,t.replace(/[\\%_*]/g,e=>`\\${e}`)}*`);var t}var Ut=D({"src/core/postgrest.ts"(){}});async function Ct(e,t,r){let i=e.from("items").select("*",{count:"exact"}).eq("project_id",t).is("deleted_at",null).order("created_at",{ascending:!0});r.type&&(i=i.eq("type",r.type)),r.domain&&(i=i.filter("data->tags->domains","cs",JSON.stringify([r.domain]))),r.level&&(i=i.filter("data->tags->>level","eq",r.level)),i=i.range(r.offset,r.offset+r.limit-1);const{data:n,error:a,count:o}=await i;if(a)throw new Error(a.message);return{items:n??[],totalCount:o??0}}async function Rt(e,t,r){const{data:i,error:n}=await e.from("items").select("*").eq("id",r).eq("project_id",t).is("deleted_at",null).maybeSingle();if(n)throw new Error(n.message);return i}async function Pt(e,t){if(t.baseline_id&&t.as_of)throw new Error("baseline_id and as_of are mutually exclusive");if(t.as_of)return t.as_of;if(t.baseline_id){const{data:r,error:i}=await e.from("baselines").select("project_id, snapshot_at, deleted_at").eq("id",t.baseline_id).maybeSingle();if(i)throw new Error(`resolveSnapshotAt: ${i.message}`);if(!r||null!==r.deleted_at)throw new Error(`Baseline not found: ${t.baseline_id}`);if(r.project_id!==t.project_id)throw new Error("Baseline does not belong to this project");return r.snapshot_at}return null}async function Mt(e,t){const{data:r,error:i}=await e.from("baselines").select("id, project_id, name, description, snapshot_at, created_at, created_by, locked_at, locked_by, deleted_at").eq("id",t).maybeSingle();if(i)throw new Error(`getBaselineById: ${i.message}`);return r&&null===r.deleted_at?r:null}function $t(e){return{id:e.id,project_id:e.project_id,type:e.type,title:e.title,data:e.data,created_by:e.changed_by,updated_by:e.changed_by,created_at:e.valid_from,updated_at:e.valid_from,deleted_at:null}}async function qt(e,t,r){const{data:i,error:n}=await e.rpc("project_state_at",{p_project_id:t,p_snapshot_at:r});if(n)throw new Error(`projectStateAt: ${n.message}`);return i??[]}async function Nt(e,t,r,i){const n=r.trim();if(!n)return{items:[],totalCount:0};let a=null;if(i.asset&&i.asset.trim()){const{data:r,error:n}=await e.from("test_results").select("item_id").eq("project_id",t).eq("asset",i.asset.trim());if(n)throw new Error(n.message);const o=new Set;for(const e of r??[])o.add(e.item_id);if(a=Array.from(o),0===a.length)return{items:[],totalCount:0}}let o=e.from("items").select("*",{count:"exact"}).eq("project_id",t).is("deleted_at",null).or(`title.ilike.${Dt(n)},data->>body.ilike.${Dt(n)},data->>summary.ilike.${Dt(n)}`).limit(i.limit);i.type&&(o=o.eq("type",i.type)),a&&(o=o.in("id",a));const{data:s,error:d,count:c}=await o;if(d)throw new Error(d.message);return{items:s??[],totalCount:c??0}}function Wt(e,t){const r=new Set(e.map(e=>e.id)),i=new Set,n=[],a=(e,t,a)=>{if(e===t)return;if(!r.has(e)||!r.has(t))return;const o=`${e}|${t}|${a}`;i.has(o)||(i.add(o),n.push({from_id:e,to_id:t,type:a}))};for(const e of t)a(e.from_id,e.to_id,e.type);for(const t of e){const e=t.data?.relationships??{},r=t=>Array.isArray(e[t])?e[t].filter(e=>"string"==typeof e):[];for(const e of r("parents"))a(t.id,e,"parent");for(const e of r("children"))a(t.id,e,"child");for(const e of r("related"))a(t.id,e,"related");for(const e of r("verifies"))a(t.id,e,"verifies");for(const e of r("verified_by"))a(t.id,e,"verified_by")}return n}async function Lt(e,t,r){const i="requirement"===r?"REQ":"test-case"===r?"TC":"note"===r?"NOTE":"TABLE",{data:n,error:a}=await e.rpc("next_item_id",{p_project_id:t,p_prefix:i});if(a)throw new Error(`ID generation failed: ${a.message}`);return n}var Vt=D({"src/db/queries.ts"(){Ut()}}),Bt={};async function Ft(){try{const e=await Se(async()=>{const e=await ve();return await async function(e){const{data:t,error:r}=await e.from("projects").select("id, name, description, org_id, created_at, organizations(name)").is("deleted_at",null).order("created_at",{ascending:!1});if(r)throw new Error(r.message);return(t??[]).map(e=>({id:e.id,name:e.name,description:e.description,org_id:e.org_id,org_name:e.organizations?.name??"Unknown",created_at:e.created_at}))}(e)}),t=Ge(),r=W,i=B().project_scope,n=i?new Set(i):null;return Et(mt(e.filter(e=>!r||e.org_id===r).filter(e=>!t||e.id===t).filter(e=>!n||n.has(e.id)).map(e=>({id:e.id,name:e.name,description:e.description,org_id:e.org_id,org_name:e.org_name,created_at:e.created_at}))))}catch(e){return Ot(St(e))}}U(Bt,{listProjectsHandler:()=>Ft});var zt=D({"src/tools/list-projects.ts"(){Ae(),Vt(),Ke(),F(),At()}}),Jt={};async function Qt(e){try{const t=await ve();let r,i,n=null;try{n=await Pt(t,{baseline_id:e.baseline_id,as_of:e.as_of,project_id:e.project_id})}catch(e){return Ot(kt(e.message))}if(n){const a=(await qt(t,e.project_id,n)).filter(t=>{if(e.type&&t.type!==e.type)return!1;const r=t.data??{};if(e.domain){const t=r?.tags?.domains??[];if(!Array.isArray(t)||!t.includes(e.domain))return!1}return!e.level||r?.tags?.level===e.level});i=a.length,r=a.slice(e.offset,e.offset+e.limit).map(e=>({id:e.id,title:e.title,type:e.type,data:e.data}))}else{const n=await Ct(t,e.project_id,{type:e.type,domain:e.domain,level:e.level,limit:e.limit,offset:e.offset});r=n.items,i=n.totalCount}return Et(mt(r.map(e=>({id:e.id,title:It(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),pt(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(Jt,{listItemsHandler:()=>Qt});var Gt=D({"src/tools/list-items.ts"(){Ae(),Vt(),At()}}),Yt={};async function Kt(e){try{const t=await ve();let r=null;try{r=await Pt(t,{baseline_id:e.baseline_id,as_of:e.as_of,project_id:e.project_id})}catch(e){return Ot(ut(e.message,["Pass baseline_id OR as_of, not both"]))}const i=r?await async function(e,t,r,i){const{data:n,error:a}=await e.rpc("item_state_at",{p_item_id:r,p_project_id:t,p_snapshot_at:i});if(a)throw new Error(`itemStateAt: ${a.message}`);return(n??[])[0]??null}(t,e.project_id,e.item_id,r).then(e=>e?$t(e):null):await Rt(t,e.project_id,e.item_id);if(!i)return Ot(_t("Item",e.item_id));const n={parents:[],children:[],related:[],verified_by:[],verifies:[]};if(r){const e=i.data?.relationships??{};for(const t of Object.keys(n)){const r=e[t];Array.isArray(r)&&(n[t]=r.slice())}}else{const r=await async function(e,t,r){const{data:i,error:n}=await e.from("item_relationships").select("from_id, to_id, type").eq("project_id",t).or(`from_id.eq.${r},to_id.eq.${r}`);if(n)throw new Error(n.message);return i??[]}(t,e.project_id,e.item_id),a={parent:"parents",child:"children",related:"related",verifies:"verifies",verified_by:"verified_by"},o={parent:"children",child:"parents",verified_by:"verifies",verifies:"verified_by",related:"related"},s=(e,t)=>{if(!(e in n))return;const r=n[e];r.includes(t)||r.push(t)},d=new Set([e.item_id]);for(const e of r)d.add(e.from_id),d.add(e.to_id);const c=i.data?.relationships??{};for(const e of Object.keys(n)){const t=c[e];if(Array.isArray(t))for(const e of t)"string"==typeof e&&d.add(e)}let l=new Set([e.item_id]);const m=Array.from(d);if(m.length>0){const{data:r,error:i}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).in("id",m);if(i)throw new Error(i.message);l=new Set((r??[]).map(e=>e.id)),l.add(e.item_id)}const p=e=>l.has(e);for(const t of r)if(t.from_id===e.item_id){if(!p(t.to_id))continue;s(a[t.type]??t.type,t.to_id)}else{if(!p(t.from_id))continue;s(o[t.type]??t.type,t.from_id)}for(const t of Object.keys(n)){const r=c[t];if(Array.isArray(r))for(const i of r)"string"==typeof i&&i!==e.item_id&&p(i)&&s(t,i)}}let a=[];if("test-case"===i.type)if(r)a=(i.data?.runs??[]).map(e=>({result:e.result??"not-run",run_by:e.by??e.run_by??null,run_at:e.date??e.run_at??"",note:e.note??null})).filter(e=>"string"==typeof e.run_at&&e.run_at.length>0).sort((e,t)=>t.run_at.localeCompare(e.run_at)).slice(0,20).map(e=>({result:e.result,run_by:e.run_by,run_at:e.run_at,note:e.note}));else{const{data:r,error:i}=await t.from("test_results").select("result, run_by, run_at, note").eq("item_id",e.item_id).eq("project_id",e.project_id).order("run_at",{ascending:!1}).limit(20);!i&&r&&(a=r)}return Et(mt({id:i.id,type:i.type,title:It(i.title),summary:It(i.data?.summary),body:It(i.data?.body),tags:i.data?.tags??{domains:[]},ownership:i.data?.ownership??{primary:null,additional:[]},relationships:n,links:i.data?.links??[],status:i.data?.status,..."test-case"===i.type?{latest_result:a[0]?.result??"not-run",test_results:a}:{},metadata:{created_at:i.created_at,created_by:i.created_by,updated_at:i.updated_at,updated_by:i.updated_by,source_id:i.data?.metadata?.source_id}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(Yt,{getItemHandler:()=>Kt});var Zt=D({"src/tools/get-item.ts"(){Ae(),Vt(),At()}}),Xt={};async function er(e){try{const t=await ve();let r,i,n=null;try{n=await Pt(t,{baseline_id:e.baseline_id,project_id:e.project_id})}catch(e){return Ot(kt(e.message))}if(n){const a=await qt(t,e.project_id,n),o=e.query.trim().toLowerCase(),s=0===o.length?[]:a.filter(t=>{if(e.type&&t.type!==e.type)return!1;const r=t.data??{};return[t.title,r?.body,r?.summary].filter(e=>"string"==typeof e).map(e=>e.toLowerCase()).some(e=>e.includes(o))});i=s.length,r=s.slice(0,e.limit).map(e=>({id:e.id,title:e.title,type:e.type,data:e.data}))}else{const n=await Nt(t,e.project_id,e.query,{type:e.type,limit:e.limit,asset:e.asset});r=n.items,i=n.totalCount}return Et(mt(r.map(e=>({id:e.id,title:It(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),pt(i,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(Xt,{searchHandler:()=>er});var tr=D({"src/tools/search.ts"(){Ae(),Vt(),At()}}),rr={};async function ir(e){try{const t=await ve(),{access_token:r}=await pe(),{items:i,totalCount:n}=await async function(e,t,r,i){const{ATOMS_SUPABASE_URL:n,ATOMS_SUPABASE_ANON_KEY:a}=await Promise.resolve().then(()=>(le(),ce)),o=await fetch(`${n}/functions/v1/embed`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i.accessToken}`,apikey:a},body:JSON.stringify({input:r})});if(!o.ok)throw new Error(`Embed function failed: ${o.status}`);const{embedding:s}=await o.json(),{data:d,error:c}=await e.rpc("semantic_search",{query_embedding:s,p_project_id:t,match_threshold:.3,match_count:i.limit,p_type:i.type||null});if(c)throw new Error(c.message);return{items:d??[],totalCount:(d??[]).length,method:"semantic"}}(t,e.project_id,e.query,{type:e.type,limit:e.limit,accessToken:r});return Et(mt(i.map(e=>({id:e.id,title:It(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),pt(n,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(rr,{semanticSearchHandler:()=>ir});var nr=D({"src/tools/semantic-search.ts"(){Ae(),be(),Vt(),At()}}),ar={};async function or(e){try{if(e.baseline_id&&e.as_of)return Ot(ht("baseline_id and as_of are mutually exclusive"));const t=await ve();let r;if(e.baseline_id){const i=await Mt(t,e.baseline_id);if(!i)return Ot(ht(`Baseline ${e.baseline_id} not found`));if(i.project_id!==e.project_id)return Ot(ht("Baseline does not belong to this project"));r=i.snapshot_at}else e.as_of&&(r=e.as_of);const{covered:i,uncovered:n,total:a}=await async function(e,t,r){let i,n,a;if(r.snapshotAt){const o=(await qt(e,t,r.snapshotAt)).map($t);i=o.filter(e=>"requirement"===e.type),n=o.map(e=>({id:e.id,data:e.data})),a=[]}else{const{data:r,error:o}=await e.from("items").select("*").eq("project_id",t).eq("type","requirement").is("deleted_at",null);if(o)throw new Error(o.message);i=r??[];const{data:s,error:d}=await e.from("items").select("id, data").eq("project_id",t).is("deleted_at",null);if(d)throw new Error(d.message);n=s??[];const{data:c,error:l}=await e.from("item_relationships").select("from_id, to_id, type").eq("project_id",t);if(l)throw new Error(l.message);a=c??[]}const o=i.filter(e=>!(r.domain&&!(e.data?.tags?.domains??[]).includes(r.domain)||r.level&&e.data?.tags?.level!==r.level)),s=Wt(n,a),d=new Set;for(const e of s)"verifies"===e.type?d.add(e.to_id):"verified_by"===e.type&&d.add(e.from_id);return{covered:o.filter(e=>d.has(e.id)),uncovered:o.filter(e=>!d.has(e.id)),total:o.length}}(t,e.project_id,{domain:e.domain,level:e.level,snapshotAt:r}),o=n.map(e=>({id:e.id,title:e.title,type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level}));return Et(mt({covered:i.length,uncovered:n.length,total:a,coverage_percent:a>0?Math.round(i.length/a*1e3)/10:100,uncovered_items:o}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(ar,{getCoverageHandler:()=>or});var sr=D({"src/tools/get-coverage.ts"(){Ae(),Vt(),At()}}),dr={};async function cr(e){try{const t=await ve(),r=await async function(e,t,r,i){const{data:n,error:a}=await e.from("change_history").select("*").eq("item_id",r).eq("project_id",t).order("changed_at",{ascending:!1}).limit(i);if(a)throw new Error(a.message);return n??[]}(t,e.project_id,e.item_id,e.limit),i=r.map(e=>{const t=[],r=e.old_data,i=e.new_data;if(r&&i){const e=new Set([...Object.keys(r),...Object.keys(i)]);for(const n of e)JSON.stringify(r[n])!==JSON.stringify(i[n])&&t.push(n)}else i?t.push("(created)"):r&&t.push("(deleted)");return{id:e.id,event_type:e.event_type,changed_by:e.changed_by??null,changed_at:e.changed_at,actor:e.actor??"user",session_id:e.session_id??null,fields_changed:t}});return Et(mt(i,pt(i.length,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(dr,{getHistoryHandler:()=>cr});var lr=D({"src/tools/get-history.ts"(){Ae(),Vt(),At()}}),mr={};async function pr(e){try{if(e.baseline_id&&e.as_of)return Ot(ht("baseline_id and as_of are mutually exclusive"));const t=await ve();let r,i,n;if(e.baseline_id){const i=await Mt(t,e.baseline_id);if(!i)return Ot(ht(`Baseline ${e.baseline_id} not found`));if(i.project_id!==e.project_id)return Ot(ht("Baseline does not belong to this project"));r=i.snapshot_at}else e.as_of&&(r=e.as_of);if(r){i=(await qt(t,e.project_id,r)).map($t).map(e=>({id:e.id,title:e.title,type:e.type,data:e.data})),n=Wt(i,[])}else{const{data:r,error:a}=await t.from("items").select("id, title, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(a)throw new Error(a.message);i=r??[];const{data:o,error:s}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(s)throw new Error(s.message);n=Wt(i,o??[])}if(0===i.length)return Et(mt({mermaid:"graph TD\n empty[No items in project]"}));const a=new Map;for(const e of i)a.set(e.id,e);const o=["graph TD"],s=new Set,d=new Set,c=(new Set((n??[]).filter(e=>"child"===e.type).map(e=>e.to_id)),new Set((n??[]).filter(e=>"parent"===e.type).map(e=>e.from_id)));let l;l=e.root_item_id?[e.root_item_id]:i.filter(e=>!c.has(e.id)).filter(t=>!(!e.include_tests&&"test-case"===t.type)).map(e=>e.id);const m=l.map(e=>({id:e,depth:0}));for(;m.length>0;){const{id:t,depth:r}=m.shift();if(s.has(t)||r>e.depth)continue;s.add(t);const i=a.get(t);if(!i)continue;if(!e.include_tests&&"test-case"===i.type)continue;const c=ur(i.title),l="requirement"===i.type?`["${c}"]`:"test-case"===i.type?`(["${c}"])`:`("${c}")`;o.push(` ${t}${l}`);for(const i of n??[]){if(i.from_id!==t)continue;const n=a.get(i.to_id);if(!n)continue;if(!e.include_tests&&"test-case"===n.type)continue;const c=`${i.from_id}-${i.type}-${i.to_id}`;d.has(c)||(d.add(c),"child"===i.type?o.push(` ${t} --\x3e ${i.to_id}`):"verified_by"===i.type?o.push(` ${t} -.->|verified_by| ${i.to_id}`):"related"===i.type&&o.push(` ${t} -.- ${i.to_id}`),!s.has(i.to_id)&&r+1<=e.depth&&m.push({id:i.to_id,depth:r+1}))}}return Et(mt({mermaid:o.join("\n"),node_count:s.size,edge_count:d.size}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}function ur(e){return e.replace(/"/g,"'").replace(/[[\]{}()]/g,"").substring(0,50)}U(mr,{exportMermaidHandler:()=>pr});var _r=D({"src/tools/export-mermaid.ts"(){Ae(),Vt(),At()}}),fr={};async function hr(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=(e.domains??[]).map(lt).filter(e=>e.length>0),n=void 0!==e.level?lt(e.level):void 0;if(i.length>0){const{data:r}=await t.from("metadata").select("domains").eq("project_id",e.project_id).maybeSingle(),n=r?.domains??[];if(n.length>0){const e=new Set(n.map(lt).filter(e=>e.length>0)),t=i.filter(t=>!e.has(t));if(t.length>0)return Ot(ht(`Unknown domain${t.length>1?"s":""}: ${t.join(", ")}. Registered: ${n.join(", ")}. Add new domains via Taxonomy Management in the ATOMS web app first.`))}}const a=await Lt(t,e.project_id,e.type),o=(new Date).toISOString(),s={id:a,type:e.type,title:e.title,summary:e.summary,body:e.body,tags:{domains:i,level:n??""},ownership:{primary:null,additional:[]},relationships:{parents:e.parent_ids??[],children:[],related:[]},links:[],metadata:{created_at:o,created_by:r,updated_at:o,updated_by:r}},{error:d}=await t.rpc("save_item_with_revision",{p_item_id:a,p_project_id:e.project_id,p_type:e.type,p_title:e.title,p_data:s,p_user_id:r,p_event_source:"mcp",p_event_session_id:ln()??null,p_diff_summary:null});if(d)throw new Error(d.message);if(e.parent_ids&&e.parent_ids.length>0)for(const i of e.parent_ids){const{error:n}=await t.rpc("apply_link_change",{p_project_id:e.project_id,p_from_id:a,p_to_id:i,p_type:"parent",p_action:"add",p_user_id:r,p_event_source:"mcp"});n&&process.stderr.write(`[atoms-mcp] Warning: parent link sync failed for ${i}: ${n.message}\n`)}return await t.from("change_history").insert({item_id:a,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:s,actor:mn(),session_id:ln()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),!1===e.echo?Et(mt({id:a,type:e.type})):Et(mt({id:a,type:e.type,title:e.title,summary:e.summary??"",body:e.body??"",domains:i,level:n??"",project_id:e.project_id}))}catch(e){return Ot(St(e))}}U(fr,{createItemHandler:()=>hr});var gr=D({"src/tools/create-item.ts"(){Ae(),Vt(),un(),At()}}),yr={};async function br(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=async(i,n,a,o)=>{const s=await Rt(t,e.project_id,i);if(!s)return{updated_fields:[],error:`Item "${i}" not found`};const d=void 0!==n.domains?n.domains.map(lt).filter(e=>e.length>0):void 0,c=void 0!==n.level?lt(n.level):void 0;if(void 0!==d&&d.length>0){const{data:r}=await t.from("metadata").select("domains").eq("project_id",e.project_id).maybeSingle(),i=r?.domains??[];if(i.length>0){const e=new Set(i.map(lt).filter(e=>e.length>0)),t=d.filter(t=>!e.has(t));if(t.length>0)return{updated_fields:[],error:`Unknown domains: ${t.join(", ")}`}}}const l={...s.data},m={...s.data},p=[],u=(e,t)=>JSON.stringify(e)!==JSON.stringify(t);if(void 0!==n.title&&u(s.data.title,n.title)&&(m.title=n.title,p.push("title")),void 0!==n.body&&u(s.data.body,n.body)&&(m.body=n.body,p.push("body")),void 0!==n.summary&&u(s.data.summary,n.summary)&&(m.summary=n.summary,p.push("summary")),void 0!==d&&u(s.data.tags?.domains??[],d)&&(m.tags={...m.tags,domains:d},p.push("domains")),void 0!==c&&u(s.data.tags?.level,c)&&(m.tags={...m.tags,level:c},p.push("level")),void 0!==n.status&&u(s.data.status,n.status)&&(m.status=n.status,p.push("status")),0===p.length)return{updated_fields:[],updatedData:s.data,itemType:s.type};m.metadata={...m.metadata,updated_at:(new Date).toISOString(),updated_by:r};const _=n.title??s.title,{data:f,error:h}=await t.rpc("save_item_with_revision",{p_item_id:i,p_project_id:e.project_id,p_type:s.type,p_title:_,p_data:m,p_user_id:r,p_event_source:"mcp",p_event_session_id:ln()??null,p_diff_summary:null,p_expected_updated_at:n.expected_updated_at??s.updated_at});if(h)return{updated_fields:[],error:h.message};if(!f)return{updated_fields:[],error:`Update silently failed for "${i}" — RPC returned no row. Likely RLS policy denied the write; verify your role and project access.`};const g={item_id:i,project_id:e.project_id,changed_by:r,event_type:"updated",old_data:l,new_data:m,actor:mn(),session_id:ln()};return a&&(g.batch_id=a),o&&(g.reason=o),await t.from("change_history").insert(g).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),{updated_fields:p,updatedData:m,itemType:s.type}};if(e.updates&&Array.isArray(e.updates)){const t=crypto.randomUUID(),r=!0===e.continue_on_error,n=[],a=[];for(const o of e.updates){const{updated_fields:s,error:d}=await i(o.item_id,o,t,e.reason);if(d){if(!r)return jt(d)?Ot(St(new Error(d))):Ot(ht(`Batch stopped at "${o.item_id}": ${d}. ${n.length} item(s) already updated.`));a.push({item_id:o.item_id,error:d})}else n.push({id:o.item_id,updated_fields:s})}return Et(mt({batch_id:t,updated:n.length,results:!1===e.echo?void 0:n,errors:a}))}if(!e.item_id)return Ot(ht("Provide either item_id (single) or updates[] (array form)."));const{updated_fields:n,updatedData:a,itemType:o,error:s}=await i(e.item_id,{title:e.title,body:e.body,summary:e.summary,domains:e.domains,level:e.level,status:e.status,expected_updated_at:e.expected_updated_at},void 0,e.reason);if(s)return s.includes("not found")?Ot(_t("Item",e.item_id)):s.includes("Unknown domain")?Ot(ht(s)):jt(s)?Ot(St(new Error(s))):Ot(kt(s));if(!1===e.echo)return Et(mt({id:e.item_id,updated_fields:n}));const d=a??{};return Et(mt({id:e.item_id,type:o,title:d.title??"",summary:d.summary??"",body:d.body??"",domains:d.tags?.domains??[],level:d.tags?.level??"",updated_fields:n}))}catch(e){return Ot(St(e))}}U(yr,{updateItemHandler:()=>br});var wr,vr,jr=D({"src/tools/update-item.ts"(){Ae(),Vt(),un(),At()}});function xr(e){const t=process.env.ATOMS_MCP_REQUIRE_CONFIRMATION?.trim();if(t){const r=t.toLowerCase();if("1"===r||"true"===r||"all"===r)return!0;if(t.split(",").map(e=>e.trim()).filter(Boolean).includes(e))return!0}return!!B().require_confirmation_for.includes(e)}function Sr(e,t=wr){const r=Date.now()+t,i={...e,exp:r},n=Buffer.from(JSON.stringify(i)).toString("base64url");return{token:`${n}.${l.createHmac("sha256",vr).update(n).digest("base64url")}`,expires_at:new Date(r).toISOString(),expires_in_seconds:Math.round(t/1e3)}}function kr(e,t){if("string"!=typeof e||!e.includes("."))return{valid:!1,reason:"Token format invalid"};const[r,i]=e.split(".");if(!r||!i)return{valid:!1,reason:"Token format invalid"};const n=l.createHmac("sha256",vr).update(r).digest("base64url"),a=Buffer.from(i,"utf8"),o=Buffer.from(n,"utf8");if(a.length!==o.length||!l.timingSafeEqual(a,o))return{valid:!1,reason:"Token signature invalid"};let s;try{s=JSON.parse(Buffer.from(r,"base64url").toString("utf8"))}catch{return{valid:!1,reason:"Token payload malformed"}}return"number"!=typeof s.exp||s.exp<Date.now()?{valid:!1,reason:"Token expired"}:s.tool!==t.tool?{valid:!1,reason:"Token issued for a different tool"}:s.project_id!==t.project_id?{valid:!1,reason:"Token issued for a different project"}:s.target!==t.target?{valid:!1,reason:"Token issued for a different target"}:{valid:!0}}function Ir(e){if(Array.isArray(e))return e.map(Ir);if(null!==e&&"object"==typeof e){const t={};for(const r of Object.keys(e).sort())t[r]=Ir(e[r]);return t}return e}function Er(e){const t=JSON.stringify(Ir(e));return l.createHash("sha256").update(t).digest("base64url").slice(0,16)}var Or,Tr=D({"src/middleware/confirmation.ts"(){F(),wr=6e4,vr=(()=>{const e=process.env.ATOMS_MCP_CONFIRMATION_SECRET;return e&&e.length>=16?e:l.randomBytes(32).toString("base64url")})()}}),Ar={};async function Hr(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=await async function(e,t,r){const{data:i,error:n}=await e.from("items").select("*").eq("id",r).eq("project_id",t).maybeSingle();if(n)throw new Error(n.message);return i}(t,e.project_id,e.item_id);if(!i)return Ot(_t("Item",e.item_id));if(i.deleted_at)return Ot(ht(`Item ${e.item_id} is already deleted`));if(xr(Or)){const t={tool:Or,project_id:e.project_id,target:e.item_id};if(!e.confirmation_token){const r=Sr(t);return Et(mt({status:"confirmation_required",preview:{id:e.item_id,title:i.title,type:i.type,would_soft_delete:!0},confirmation_token:r.token,expires_at:r.expires_at,expires_in_seconds:r.expires_in_seconds,message:`Re-call atoms_delete_item with the same project_id, item_id, and confirmation_token within ${r.expires_in_seconds}s to execute.`}))}const r=kr(e.confirmation_token,t);if(!r.valid)return Ot(ht(`confirmation_token rejected: ${r.reason}`))}const{error:n}=await t.from("items").update({deleted_at:(new Date).toISOString()}).eq("id",e.item_id).eq("project_id",e.project_id);if(n)throw new Error(n.message);return await t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"deleted",old_data:i.data,new_data:null,actor:mn(),session_id:ln()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Et(mt({id:e.item_id,title:i.title,type:i.type,deleted:!0,message:`Item ${e.item_id} soft-deleted. It can still be found in audit logs.`}))}catch(e){return Ot(St(e))}}U(Ar,{deleteItemHandler:()=>Hr});var Dr=D({"src/tools/delete-item.ts"(){Ae(),Vt(),un(),Tr(),At(),Or="atoms_delete_item"}}),Ur={};async function Cr(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const{data:i,error:n}=await t.from("items").select("*").eq("id",e.item_id).eq("project_id",e.project_id).maybeSingle();if(n)throw new Error(n.message);if(!i)return Ot(_t("Item",e.item_id));if(!i.deleted_at)return Ot(ut(`Item ${e.item_id} is not deleted and cannot be restored.`,["Use atoms_get_item to view the current item state","Use atoms_delete_item first if you intended to delete it"]));const{error:a}=await t.from("items").update({deleted_at:null}).eq("id",e.item_id).eq("project_id",e.project_id);if(a)throw new Error(a.message);return await t.rpc("repair_live_item_relationships_from_jsonb",{p_project_id:e.project_id,p_item_id:e.item_id}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: relationship repair failed: ${e.message}\n`)}),await t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"restored",old_data:null,new_data:i.data,actor:mn(),session_id:ln()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Et(mt({id:e.item_id,title:i.title,type:i.type,restored:!0,message:`Item ${e.item_id} restored successfully.`}))}catch(e){return Ot(St(e))}}U(Ur,{restoreItemHandler:()=>Cr});var Rr=D({"src/tools/restore-item.ts"(){Ae(),un(),At()}}),Pr={};async function Mr(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=async(i,n,a)=>{const o=await Rt(t,e.project_id,i.from_id);if(!o)throw new Error(`Item "${i.from_id}" not found`);const s=await Rt(t,e.project_id,i.to_id);if(!s)throw new Error(`Item "${i.to_id}" not found`);const{error:d}=await t.rpc("apply_link_change",{p_project_id:e.project_id,p_from_id:i.from_id,p_to_id:i.to_id,p_type:i.type,p_action:i.action,p_user_id:r,p_event_source:"mcp",p_expected_from_updated_at:i.expected_from_updated_at??o.updated_at,p_expected_to_updated_at:i.expected_to_updated_at??s.updated_at});if(d)throw new Error(d.message);const c={item_id:i.from_id,project_id:e.project_id,changed_by:r,event_type:"updated",old_data:null,new_data:{relationship_change:{action:i.action,type:i.type,from:i.from_id,to:i.to_id}},actor:mn(),session_id:ln()};n&&(c.batch_id=n),a&&(c.reason=a),await t.from("change_history").insert(c).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)})};if(e.operations&&Array.isArray(e.operations)){for(const t of e.operations)if(t.from_id===t.to_id)return Ot(ht(`Self-reference in batch: "${t.from_id}"`));const r=[...new Set(e.operations.flatMap(e=>[e.from_id,e.to_id]))],{data:n}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).in("id",r),a=new Set((n??[]).map(e=>e.id)),o=r.filter(e=>!a.has(e));if(o.length>0)return Ot(_t("Items",o.join(", ")));const{data:s}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id),d=new Map;for(const e of s??[])"parent"===e.type&&(d.has(e.from_id)||d.set(e.from_id,new Set),d.get(e.from_id).add(e.to_id));for(const t of e.operations){if("parent"!==t.type&&"child"!==t.type)continue;const[e,r]="parent"===t.type?[t.from_id,t.to_id]:[t.to_id,t.from_id];"remove"===t.action?d.get(e)?.delete(r):(d.has(e)||d.set(e,new Set),d.get(e).add(r))}const c=0,l=1,m=2,p=new Map,u=new Set;for(const[e,t]of d){u.add(e);for(const e of t)u.add(e)}const _=e=>{if((p.get(e)??c)!==c)return null;const t=[],r=[],i=e=>{p.set(e,l),r.push(e),t.push({node:e,parents:[...d.get(e)??[]],idx:0})};for(i(e);t.length>0;){const e=t[t.length-1];if(e.idx>=e.parents.length){p.set(e.node,m),r.pop(),t.pop();continue}const n=e.parents[e.idx];e.idx++;const a=p.get(n)??c;if(a===l){const e=r.indexOf(n);return[...r.slice(e),n]}a!==m&&i(n)}return null};for(const e of u){const t=_(e);if(t)return Ot(ht(`Batch would create a cycle: ${t.join(" -> ")}. No changes applied.`))}const f=crypto.randomUUID(),h=e.operations.filter(e=>"remove"===e.action),g=e.operations.filter(e=>"add"===e.action);for(const t of[...h,...g])await i(t,f,e.reason);return Et(mt({batch_id:f,processed:e.operations.length,operations:!1===e.echo?void 0:e.operations}))}return e.from_id&&e.to_id&&e.type&&e.action?e.from_id===e.to_id?Ot(ht("Cannot create a relationship between an item and itself")):(await i({action:e.action,from_id:e.from_id,to_id:e.to_id,type:e.type,expected_from_updated_at:e.expected_from_updated_at,expected_to_updated_at:e.expected_to_updated_at},void 0,e.reason),!1===e.echo?Et(mt({action:e.action,from_id:e.from_id,to_id:e.to_id})):Et(mt({action:e.action,type:e.type,from_id:e.from_id,to_id:e.to_id,message:`Relationship ${"add"===e.action?"added":"removed"}: ${e.from_id} --[${e.type}]--\x3e ${e.to_id}`}))):Ot(ht("Provide (from_id, to_id, type, action) for single-op or operations[] for batch."))}catch(e){return Ot(St(e))}}U(Pr,{linkItemsHandler:()=>Mr});var $r=D({"src/tools/link-items.ts"(){Ae(),Vt(),un(),At()}}),qr=D({"src/core/bulk-parent-links.ts"(){}}),Nr={};async function Wr(e){try{if(!e.items||0===e.items.length)return Ot(ht("items array must contain at least 1 item"));if(e.items.length>100)return Ot(ht(`Maximum 100 items per call (received ${e.items.length}). Split into multiple calls.`));const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=await async function(e,t,r){const{data:i}=await e.from("metadata").select("domains").eq("project_id",t).maybeSingle(),n=i?.domains??[],a=n.length>0?new Set(n.map(lt).filter(e=>e.length>0)):null,o=Er(n.map(lt).filter(e=>e.length>0).sort()),s=[],d=new Set,c={};for(let e=0;e<r.length;e++){const t=r[e];if(c[t.type]=(c[t.type]??0)+1,!a)continue;const i=(t.domains??[]).map(lt).filter(e=>e.length>0);if(0===i.length)continue;const o=i.filter(e=>!a.has(e));o.length>0&&(d.add(e),s.push({index:e,title:t.title,error:`Unknown domain${o.length>1?"s":""}: ${o.join(", ")}. Registered: ${n.join(", ")}.`}))}return{registeredDomains:n,registeredDomainHash:o,invalidDomainIndexes:d,errors:s,typeBreakdown:c}}(t,e.project_id,e.items);if(xr("atoms_bulk_import")){const t={tool:"atoms_bulk_import",project_id:e.project_id,target:Er({items:e.items,registered_domain_hash:i.registeredDomainHash})};if(!e.confirmation_token){const r=Sr(t);return Et(mt({status:"confirmation_required",preview:{total_items:e.items.length,by_type:i.typeBreakdown,sample_titles:e.items.slice(0,5).map(e=>e.title),would_fail:i.errors.length>0,errors:i.errors,registered_domain_hash:i.registeredDomainHash},confirmation_token:r.token,expires_at:r.expires_at,expires_in_seconds:r.expires_in_seconds,message:`Re-call atoms_bulk_import with the same project_id, items array, and confirmation_token within ${r.expires_in_seconds}s to execute. Changing items or project domains will invalidate the token.`}))}const r=kr(e.confirmation_token,t);if(!r.valid)return Ot(ht(`confirmation_token rejected: ${r.reason}`))}const n=(new Date).toISOString(),a=ln(),o=[],s=[...i.errors],d=[];for(let r=0;r<e.items.length;r++){const n=e.items[r];if(i.invalidDomainIndexes.has(r))continue;const a=(n.domains??[]).map(lt).filter(e=>e.length>0),o=void 0!==n.level?lt(n.level):void 0;try{const i=await Lt(t,e.project_id,n.type);d.push({itemId:i,input:n,sanitizedDomains:a,sanitizedLevel:o,index:r})}catch(e){s.push({index:r,title:n.title,error:`ID generation failed: ${e instanceof Error?e.message:String(e)}`})}}if(0===d.length)return Et(mt({created:0,items:[],errors:s}));const c=[],l=new Map;for(const{itemId:t,input:i,sanitizedDomains:a,sanitizedLevel:o}of d){const s={id:t,type:i.type,title:i.title,summary:i.summary,body:i.body,tags:{domains:a,level:o??""},ownership:{primary:null,additional:[]},relationships:{parents:i.parent_id?[i.parent_id]:[],children:[],related:[]},links:[],metadata:{created_at:n,created_by:r,updated_at:n,updated_by:r}};l.set(t,s),c.push({id:t,project_id:e.project_id,type:i.type,title:i.title,data:s,created_by:r,updated_by:r})}const m=c.map(e=>({id:e.id,type:e.type,title:e.title,data:e.data})),{error:p}=await t.rpc("bulk_save_items_with_revisions",{p_project_id:e.project_id,p_items:m,p_user_id:r,p_event_source:"mcp"});if(p)for(const{itemId:i,input:n,index:l}of d){const d=c.find(e=>e.id===i);if(!d)continue;const{error:m}=await t.rpc("save_item_with_revision",{p_item_id:d.id,p_project_id:e.project_id,p_type:d.type,p_title:d.title,p_data:d.data,p_user_id:r,p_event_source:"mcp",p_event_session_id:a,p_diff_summary:null});m?s.push({index:l,title:n.title,error:m.message}):o.push({id:i,title:n.title,type:n.type})}else for(const{itemId:e,input:t}of d)o.push({id:e,title:t.title,type:t.type});const u=d.filter(({input:e})=>e.parent_id).filter(({itemId:e})=>o.some(t=>t.id===e)).map(({itemId:t,input:r})=>({from_id:t,to_id:r.parent_id,type:"parent",project_id:e.project_id}));await async function(e){for(const t of e.links){const{error:r}=await e.client.rpc("apply_link_change",{p_project_id:e.projectId,p_from_id:t.from,p_to_id:t.to,p_type:"parent",p_action:"add",p_user_id:e.userId,p_event_source:"mcp"});r&&e.warn(`bulk parent link sync failed for ${t.to}: ${r.message??"unknown error"}`)}}({client:t,projectId:e.project_id,links:u.map(e=>({from:e.from_id,to:e.to_id})),userId:r,warn:e=>process.stderr.write(`[atoms-mcp] Warning: ${e}\n`)});const _=o.map(t=>({item_id:t.id,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:l.get(t.id)??null,actor:mn(),session_id:a}));return _.length>0&&await t.from("change_history").insert(_).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: bulk change_history log failed: ${e.message}\n`)}),Et(mt({created:o.length,items:o,errors:s.length>0?s:[],project_id:e.project_id}))}catch(e){return Ot(St(e))}}U(Nr,{bulkImportHandler:()=>Wr});var Lr=D({"src/tools/bulk-import.ts"(){Ae(),Vt(),un(),Tr(),At(),qr()}}),Vr={};async function Br(e){try{const t=await ve(),r=ke(),i=Ie();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const n=await Rt(t,e.project_id,e.item_id);if(!n)return Ot(_t("Item",e.item_id));if("test-case"!==n.type)return Ot(ht(`Item '${e.item_id}' is a ${n.type}, not a test-case`));const a=(e.asset??"").trim(),o=(new Date).toISOString(),{data:s,error:d}=await t.rpc("record_test_results_bulk",{p_project_id:e.project_id,p_item_id:e.item_id,p_run_by:i||r,p_actor_user_id:r,p_results:[{result:e.result,note:e.note,asset:a||void 0,date:o}]});if(d)throw new Error(d.message);const c=Array.isArray(s)?s[0]:s?.[0];await t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"test_result_recorded",old_data:null,new_data:{result:e.result,run_at:o,note:e.note,asset:a||null},actor:mn(),session_id:ln()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)});const l=a?` (asset: ${a})`:"";return Et(mt({item_id:e.item_id,result:e.result,run_at:o,run_by:i||r,note:e.note??null,asset:a||null,asset_id:c?.asset_id??null,asset_created:c?.asset_created??!1,message:`Test result '${e.result}' recorded for ${e.item_id}${l}`}))}catch(e){return Ot(St(e))}}U(Vr,{recordTestResultHandler:()=>Br});var Fr=D({"src/tools/record-test-result.ts"(){Ae(),Vt(),un(),At()}}),zr={};async function Jr(e){try{const t=await ve(),r=ke(),i=Ie();if(!Array.isArray(e.results)||0===e.results.length)return Ot(ht("results must be a non-empty array"));if(e.results.length>100)return Ot(ht(`results must have at most 100 entries (got ${e.results.length})`));try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const n=await Rt(t,e.project_id,e.item_id);if(!n)return Ot(_t("Item",e.item_id));if("test-case"!==n.type)return Ot(ht(`Item '${e.item_id}' is a ${n.type}, not a test-case`));const{data:a,error:o}=await t.rpc("record_test_results_bulk",{p_project_id:e.project_id,p_item_id:e.item_id,p_run_by:i||r,p_actor_user_id:r,p_results:e.results});if(o)throw new Error(o.message);return t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"test_result_recorded",old_data:null,new_data:{bulk:!0,count:e.results.length,results:a},actor:mn(),session_id:ln()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Et(mt({item_id:e.item_id,count:e.results.length,results:a,message:`Recorded ${e.results.length} test results for ${e.item_id}`}))}catch(e){return Ot(St(e))}}U(zr,{recordTestResultsBulkHandler:()=>Jr});var Qr,Gr,Yr,Kr,Zr=D({"src/tools/record-test-results-bulk.ts"(){Ae(),Vt(),un(),At()}});function Xr(e){return Math.min(Math.max(e??5,1),10)}var ei=D({"src/core/trace-graph.ts"(){Qr=200,Gr=new Set(["parent","verifies"]),Yr=new Set(["child","verified_by"]),Kr={parent:"child",child:"parent",verifies:"verified_by",verified_by:"verifies",related:"related"}}}),ti={};async function ri(e){try{const t=Xr(e.depth),r=await ve(),i=await Rt(r,e.project_id,e.item_id);if(!i)return Ot(_t("Item",e.item_id));const{data:n,error:a}=await r.from("items").select("id, title, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(a)throw new Error(a.message);const{data:o,error:s}=await r.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(s)throw new Error(s.message);const d=Wt(n??[],o??[]);return Et(mt(function(e){const t=Xr(e.depth),r=e.relationshipTypes?.length?new Set(e.relationshipTypes):null,i=new Map(e.items.map(e=>[e.id,e]));i.has(e.rootItemId)||i.set(e.rootItemId,e.rootItem);const n=new Map,a=(e,t,r)=>{n.has(e)||n.set(e,[]),n.get(e).push({neighbor:t,relType:r})};for(const t of e.relationships){const i=t.type;if(("upstream"===e.direction||"both"===e.direction)&&(!Gr.has(i)||r&&!r.has(i)||a(t.from_id,t.to_id,i),Yr.has(i))){const e=Kr[i]??i;r&&!r.has(e)||a(t.to_id,t.from_id,e)}if(("downstream"===e.direction||"both"===e.direction)&&(!Yr.has(i)||r&&!r.has(i)||a(t.from_id,t.to_id,i),Gr.has(i))){const e=Kr[i]??i;r&&!r.has(e)||a(t.to_id,t.from_id,e)}"related"!==i||r&&!r.has("related")||(a(t.from_id,t.to_id,"related"),a(t.to_id,t.from_id,"related"))}const o=new Set([e.rootItemId]),s=[],d=[],c=[];for(const t of n.get(e.rootItemId)??[])c.push({id:t.neighbor,depth:1,relType:t.relType,from:e.rootItemId});for(;c.length>0&&s.length<Qr;){const{id:e,depth:r,relType:a,from:l}=c.shift();if(o.has(e)||r>t)continue;o.add(e);const m=i.get(e);if(m&&(s.push({id:e,title:m.title,type:m.type,relationship:a,depth:r,from:l}),d.push({from:l,to:e,type:a}),r<t))for(const t of n.get(e)??[])o.has(t.neighbor)||c.push({id:t.neighbor,depth:r+1,relType:t.relType,from:e})}const l={id:e.rootItemId,title:e.rootItem.title,type:e.rootItem.type,relationship:"root",depth:0,from:e.rootItemId};return{root:e.rootItemId,project_id:e.projectId,direction:e.direction,depth_limit:t,items:[l,...s],edges:d,total_count:s.length+1,...s.length>=Qr?{truncated:!0,truncation_message:`Results capped at ${Qr} items. Use a smaller depth or filter by relationship_types.`}:{}}}({projectId:e.project_id,rootItemId:e.item_id,rootItem:{id:e.item_id,title:i.title??e.item_id,type:i.type??"unknown"},items:n??[],relationships:d??[],direction:e.direction,depth:t,relationshipTypes:e.relationship_types})))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(ti,{traceHandler:()=>ri});var ii=D({"src/tools/trace.ts"(){Ae(),Vt(),ei(),At()}}),ni={};async function ai(e){try{let t=(await ve()).from("assets").select("id, name, archived_at, created_at").eq("project_id",e.project_id).order("name");e.include_archived||(t=t.is("archived_at",null));const{data:r,error:i}=await t;if(i)throw new Error(i.message);return Et(mt({assets:r||[],total:(r||[]).length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(ni,{listAssetsHandler:()=>ai});var oi=D({"src/tools/assets.ts"(){Ae(),At()}}),si={};async function di(e,t,r,i){if(r&&i)throw new Error("baseline_id and as_of are mutually exclusive");if(i)return i;if(!r)return null;const{data:n,error:a}=await e.from("baselines").select("project_id, snapshot_at, deleted_at").eq("id",r).maybeSingle();if(a)throw new Error(a.message);if(!n||n.project_id!==t||n.deleted_at)throw new Error("Baseline not found for this project");return n.snapshot_at}async function ci(e,t,r){if(r){const{data:i,error:n}=await e.rpc("variables_at",{p_project_id:t,p_snapshot_at:r});if(n)throw new Error(n.message);return i??[]}const{data:i,error:n}=await e.from("project_variables").select("*").eq("project_id",t).order("name");if(n)throw new Error(n.message);return i??[]}async function li(e,t,r,i){const{data:n,error:a}=await e.rpc("save_variable_with_revision",{p_project_id:t.project_id,p_name:t.name,p_value:t.value,p_unit:t.unit??null,p_description:t.description??null,p_is_signal:t.is_signal??!1,p_range_min:t.range_min??null,p_range_max:t.range_max??null,p_rate_ms:t.rate_ms??null,p_user_id:r,p_event_source:i,p_variable_id:t.id??null});if(a)throw new Error(a.message);return n}async function mi(e){try{const t=await ve(),r=await di(t,e.project_id,e.baseline_id,e.as_of),i=await ci(t,e.project_id,r);return Et(mt({variables:i,total:i.length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function pi(e){try{const t=await ve(),r=await di(t,e.project_id,e.baseline_id,e.as_of),i=(await ci(t,e.project_id,r)).find(t=>t.name===e.variable_name)??null;if(!i)return Ot(_t("Variable",e.variable_name));const{data:n}=await t.from("items").select("id, title, type").eq("project_id",e.project_id).is("deleted_at",null),a=`{${e.variable_name}}`,o=(n||[]).filter(e=>(e.data?.body||"").includes(a));return Et(mt({variable:i,referenced_by:o.map(e=>({id:e.id,title:e.title,type:e.type})),reference_count:o.length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function ui(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=await t.from("project_variables").select("*").eq("project_id",e.project_id).eq("name",e.variable_name).maybeSingle();if(!i.data)return Ot(_t("Variable",e.variable_name));const n={updated_by:r,updated_at:(new Date).toISOString()};return void 0!==e.value&&(n.value=e.value),void 0!==e.unit&&(n.unit=e.unit||null),void 0!==e.description&&(n.description=e.description||null),Et(mt({variable:await li(t,{...i.data,...n,project_id:e.project_id,name:e.variable_name,value:n.value??i.data.value,unit:n.unit??i.data.unit,description:n.description??i.data.description},r,"mcp")}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function _i(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const i=/^[a-zA-Z_][a-zA-Z0-9_]*$/;if(e.variables&&Array.isArray(e.variables)){const n=e.variables.filter(e=>!i.test(e.name));if(n.length>0)return Ot(ht(`Invalid variable name(s): ${n.map(e=>e.name).join(", ")}. Use letters, digits, underscores; must start with letter or underscore.`));const a=e.variables.map(e=>e.name),o=a.filter((e,t)=>a.indexOf(e)!==t);if(o.length>0)return Ot(ht(`Duplicate names in batch: ${[...new Set(o)].join(", ")}`));const{data:s}=await t.from("project_variables").select("name").eq("project_id",e.project_id).in("name",a),d=(s??[]).map(e=>e.name);if(d.length>0)return Ot(ht(`Variable(s) already exist: ${d.join(", ")}. Remove them from the batch or use atoms_update_variable to change values.`));const c=e.variables.map(t=>({project_id:e.project_id,name:t.name,value:t.value,unit:t.unit??null,description:t.description??null,created_by:r,updated_by:r})),l=await async function(e,t,r,i){const{data:n,error:a}=await e.rpc("bulk_save_variables_with_revisions",{p_project_id:t,p_variables:r,p_user_id:i,p_event_source:"mcp"});if(a)throw new Error(a.message);return n??[]}(t,e.project_id,c,r);return Et(mt({batch_id:crypto.randomUUID(),created:(l??[]).length,variables:!1===e.echo?(l??[]).map(e=>({name:e.name})):l??[],message:`${(l??[]).length} variable(s) created. Reference them as {name} in requirement bodies.`}))}if(!e.name||!e.value)return Ot(ht("Provide either (name, value) for single variable or variables[] for batch."));if(!i.test(e.name))return Ot(ht(`Variable name "${e.name}" is invalid. Use letters, digits, and underscores only; must start with a letter or underscore.`));const{data:n}=await t.from("project_variables").select("name").eq("project_id",e.project_id).eq("name",e.name).maybeSingle();if(n)return Ot(ht(`Variable "${e.name}" already exists in this project.`));const a=await li(t,{project_id:e.project_id,name:e.name,value:e.value,unit:e.unit??null,description:e.description??null},r,"mcp");return!1===e.echo?Et(mt({name:e.name})):Et(mt({variable:a,message:`Variable "${e.name}" created. Reference it in requirement bodies as {${e.name}}.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function fi(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const{data:i}=await t.from("project_variables").select("id").eq("project_id",e.project_id).eq("name",e.name).maybeSingle();if(!i)return Ot(_t("Variable",e.name));const{data:n}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).filter("data->>body","ilike",`%{${e.name}}%`),a=(n??[]).length;if(a>0&&!e.force)return Ot(ht(`Variable "${e.name}" is referenced in ${a} item${a>1?"s":""}. Use force=true to delete anyway, or remove all {${e.name}} references first.`));if(xr("atoms_delete_variable")){const t={tool:"atoms_delete_variable",project_id:e.project_id,target:e.name};if(!e.confirmation_token){const r=Sr(t);return Et(mt({status:"confirmation_required",preview:{variable_name:e.name,referenced_by_items:a,would_orphan_references:a>0},confirmation_token:r.token,expires_at:r.expires_at,expires_in_seconds:r.expires_in_seconds,message:`Re-call atoms_delete_variable with the same project_id, name, and confirmation_token within ${r.expires_in_seconds}s to execute.`}))}const r=kr(e.confirmation_token,t);if(!r.valid)return Ot(ht(`confirmation_token rejected: ${r.reason}`))}const o=await async function(e,t,r,i){const{data:n,error:a}=await e.rpc("delete_variable_with_revision",{p_project_id:t,p_variable_id:r,p_user_id:i,p_event_source:"mcp"});if(a)throw new Error(a.message);return Boolean(n)}(t,e.project_id,i.id,r);return o?Et(mt({deleted:!0,name:e.name,referenced_by_items:a,warning:a>0?`${a} item${a>1?"s":""} still contain {${e.name}} — those references are now unresolved.`:void 0})):Ot(_t("Variable",e.name))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(si,{createVariableHandler:()=>_i,deleteVariableHandler:()=>fi,getVariableHandler:()=>pi,listVariablesHandler:()=>mi,updateVariableHandler:()=>ui});var hi=D({"src/tools/variables.ts"(){Ae(),Tr(),At()}}),gi={};async function yi(e,t){const{data:r}=await e.from("metadata").select("domains, levels").eq("project_id",t).maybeSingle();return{domains:r?.domains??[],levels:r?.levels??[]}}async function bi(e,t,r,i){const{error:n}=await e.from("metadata").upsert({project_id:t,domains:r,levels:i},{onConflict:"project_id"});if(n)throw new Error(n.message)}async function wi(e){try{const t=await ve(),{domains:r}=await yi(t,e.project_id),{counts:i,orphans:n}=await async function(e,t,r){const{data:i}=await e.from("items").select("data").eq("project_id",t).is("deleted_at",null),n={};for(const e of r)n[e]=0;const a={};for(const e of i??[]){const t=e.data?.tags?.domains??[];for(const e of t)a[e]=(a[e]??0)+1,e in n&&n[e]++}const o=new Set(r),s={};for(const[e,t]of Object.entries(a))o.has(e)||(s[e]=t);return{counts:n,orphans:s}}(t,e.project_id,r),a=r.map(e=>({name:e,registered:!0,item_count:i[e]??0})),o=Object.entries(n).map(([e,t])=>({name:e,registered:!1,item_count:t})).sort((e,t)=>e.name.localeCompare(t.name)),s=[...a,...o];return Et(mt({domains:s,total:s.length,registered_count:a.length,unregistered_count:o.length,note:o.length>0?`${o.length} unregistered tag(s) found on items. Use atoms_create_domain to register them, or atoms_update_item to remove them.`:void 0}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function vi(e){try{const t=await ve();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const{domains:r,levels:i}=await yi(t,e.project_id),n=e.name.trim().toLowerCase();return n?r.includes(n)?Ot(ht(`Domain "${n}" already exists in this project.`)):(await bi(t,e.project_id,[...r,n],i),Et(mt({domain:{name:n},message:`Domain "${n}" registered. You can now tag items with domains=["${n}"].`}))):Ot(ht("Domain name cannot be empty."))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function ji(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const{domains:i,levels:n}=await yi(t,e.project_id),a=e.name.trim().toLowerCase(),o=e.new_name.trim().toLowerCase();if(!o)return Ot(ht("new_name cannot be empty."));const s=i.includes(a);if(!s){const{data:r}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).filter("data->tags->domains","cs",JSON.stringify([a])).limit(1);if(!r||0===r.length)return Ot(_t("Domain",a))}if(o!==a&&i.includes(o))return Ot(ht(`Domain "${o}" already exists. Choose a different name.`));const{data:d,error:c}=await t.from("items").select("id, data").eq("project_id",e.project_id).is("deleted_at",null).filter("data->tags->domains","cs",JSON.stringify([a]));if(c)throw new Error(c.message);const l=(d??[]).length;if(e.dry_run)return Et(mt({dry_run:!0,rename:{from:a,to:o},items_affected:l,message:`Dry run: renaming "${a}" → "${o}" would update ${l} item${1!==l?"s":""}. Call again with dry_run=false to apply.`}));let m;m=s?i.map(e=>e===a?o:e):i.includes(o)?i:[...i,o],await bi(t,e.project_id,m,n);const{error:p}=await t.rpc("apply_domain_change",{p_project_id:e.project_id,p_old_domain:a,p_new_domain:o,p_user_id:r,p_event_source:"mcp"});if(p)throw new Error(p.message);return Et(mt({domain:{name:o},renamed_from:a,items_updated:l,message:`Domain renamed "${a}" → "${o}". ${l} item${1!==l?"s":""} updated.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}async function xi(e){try{const t=await ve(),r=ke();try{await Ee(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ot(ft("Viewer"));throw e}const{domains:i,levels:n}=await yi(t,e.project_id),a=e.name.trim().toLowerCase(),o=i.includes(a),{data:s,error:d}=await t.from("items").select("id, data").eq("project_id",e.project_id).is("deleted_at",null).filter("data->tags->domains","cs",JSON.stringify([a]));if(d)throw new Error(d.message);const c=(s??[]).length;if(!o&&0===c)return Ot(_t("Domain",a));if(c>0&&!e.force)return Ot(ht(`Domain "${a}" is used by ${c} item${c>1?"s":""}. Use force=true to delete and strip the domain from all items, or re-tag them first.`));if(xr("atoms_delete_domain")){const t={tool:"atoms_delete_domain",project_id:e.project_id,target:a};if(!e.confirmation_token){const e=Sr(t);return Et(mt({status:"confirmation_required",preview:{domain_name:a,items_using_count:c,would_strip_from_items:c>0},confirmation_token:e.token,expires_at:e.expires_at,expires_in_seconds:e.expires_in_seconds,message:`Re-call atoms_delete_domain with the same project_id, name, and confirmation_token within ${e.expires_in_seconds}s to execute.`}))}const r=kr(e.confirmation_token,t);if(!r.valid)return Ot(ht(`confirmation_token rejected: ${r.reason}`))}const l=o?i.filter(e=>e!==a):i;if(await bi(t,e.project_id,l,n),c>0){const{error:i}=await t.rpc("apply_domain_change",{p_project_id:e.project_id,p_old_domain:a,p_new_domain:null,p_user_id:r,p_event_source:"mcp"});if(i)throw new Error(i.message)}return Et(mt({deleted:!0,name:a,items_updated:c,message:c>0?`Domain "${a}" deleted and stripped from ${c} item${c>1?"s":""}.`:`Domain "${a}" deleted.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(gi,{createDomainHandler:()=>vi,deleteDomainHandler:()=>xi,listDomainsHandler:()=>wi,updateDomainHandler:()=>ji});var Si=D({"src/tools/domains.ts"(){Ae(),Tr(),At()}}),ki={};async function Ii(e){try{const t=await ve(),{data:r,error:i}=await t.from("projects").select("id, name").eq("id",e.project_id).is("deleted_at",null).maybeSingle(),n=r?.name??null,a=i?.message??null,{data:o,error:s}=await t.from("items").select("id, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(s)throw new Error(s.message);const d=o??[];if(!n&&0===d.length){if(a)throw new Error(a);return Ot(_t("Project",e.project_id))}const c={requirements:0,test_cases:0,notes:0},l=[],m=[];for(const e of d)if("requirement"===e.type){c.requirements++;const t=e.data?.tags?.domains??[];m.push({id:e.id,domains:t})}else"test-case"===e.type?(c.test_cases++,l.push(e.id)):"note"===e.type&&c.notes++;const p={passed:0,failed:0,blocked:0,not_run:0};if(l.length>0){const{data:r,error:i}=await t.from("test_results").select("item_id, result, run_at").eq("project_id",e.project_id).order("run_at",{ascending:!1});if(i)throw new Error(i.message);const n=new Map;for(const e of r??[])n.has(e.item_id)||n.set(e.item_id,e.result);for(const e of l){const t=n.get(e);t&&"not-run"!==t?"passed"===t?p.passed++:"failed"===t?p.failed++:"blocked"===t?p.blocked++:p.not_run++:p.not_run++}}const{data:u,error:_}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(_)throw new Error(_.message);const f=Wt(o??[],u??[]),h=new Set;for(const e of f)"verifies"===e.type?h.add(e.to_id):"verified_by"===e.type&&h.add(e.from_id);const g=m.filter(e=>h.has(e.id)).length,y=m.length,b={covered:g,uncovered:y-g,total:y,percent:y>0?Math.round(g/y*1e3)/10:100},w=new Map;for(const e of m){const t=e.domains.length>0?e.domains:["(untagged)"],r=h.has(e.id);for(const e of t){w.has(e)||w.set(e,{covered:0,total:0});const t=w.get(e);t.total++,r&&t.covered++}}const v=Array.from(w.entries()).sort((e,t)=>e[0].localeCompare(t[0])).map(([e,t])=>({domain:e,covered:t.covered,total:t.total,percent:t.total>0?Math.round(t.covered/t.total*1e3)/10:100})),j=new Date;j.setDate(j.getDate()-7);const{count:x,error:S}=await t.from("change_history").select("id",{count:"exact",head:!0}).eq("project_id",e.project_id).gte("changed_at",j.toISOString());if(S)throw new Error(S.message);return Et(mt({project_id:e.project_id,project_name:n??`Project ${e.project_id.slice(0,8)}`,counts:c,test_status:p,coverage:b,coverage_by_domain:v,recent_changes:x??0,last_updated:(new Date).toISOString()}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(ki,{projectSummaryHandler:()=>Ii});var Ei,Oi=D({"src/tools/project-summary.ts"(){Ae(),Vt(),At()}}),Ti={};async function Ai(e){try{const t=await ve();let r,i;if(e.query&&e.query.trim().length>0){const n=await Nt(t,e.project_id,e.query,{type:e.type,limit:e.limit});r=n.items.map(e=>({id:e.id,title:e.title,type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),i=n.totalCount}else{const n=await Ct(t,e.project_id,{type:e.type,domain:e.domain,level:e.level,limit:e.limit,offset:e.offset});r=n.items.map(e=>({id:e.id,title:e.title,type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),i=n.totalCount}const n=await async function(e,t){const{data:r,error:i}=await e.from("items").select("data").eq("project_id",t).is("deleted_at",null);if(i)throw new Error(i.message);const n=new Set,a=new Set;for(const e of r??[]){const t=e.data?.tags;if(t){const e=t.domains;if(e)for(const t of e)n.add(t);const r=t.level;r&&a.add(r)}}return{domains:[...n].sort(),levels:[...a].sort()}}(t,e.project_id);return Et(mt({items:r,filters:{domains:n.domains,levels:n.levels,types:Ei},project_id:e.project_id},pt(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(Ti,{browseHandler:()=>Ai});var Hi,Di,Ui,Ci,Ri=D({"src/tools/browse.ts"(){Ae(),Vt(),At(),Ei=["requirement","test-case","note","table"]}}),Pi={};async function Mi(e){try{const t=e.direction??"downstream",r=null!=e.depth?Math.min(Math.max(e.depth,1),20):null,i=await ve(),n=await Rt(i,e.project_id,e.item_id);if(!n)return Ot(_t("Item",e.item_id));const{data:a,error:o}=await i.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(o)throw new Error(o.message);const{data:s,error:d}=await i.from("items").select("id, title, type").eq("project_id",e.project_id).is("deleted_at",null);if(d)throw new Error(d.message);const c=new Map;for(const e of s??[])c.set(e.id,{title:e.title,type:e.type});const l=new Map,m=(e,t,r)=>{l.has(e)||l.set(e,[]),l.get(e).push({neighbor:t,relType:r})};for(const e of a??[]){const r=e.type;"upstream"!==t&&"both"!==t||(Di.includes(r)&&m(e.from_id,e.to_id,r),Ui.includes(r)&&m(e.to_id,e.from_id,Ci[r]??r)),"downstream"!==t&&"both"!==t||(Ui.includes(r)&&m(e.from_id,e.to_id,r),Di.includes(r)&&m(e.to_id,e.from_id,Ci[r]??r)),"related"===r&&(m(e.from_id,e.to_id,"related"),m(e.to_id,e.from_id,"related"))}const p=new Set;p.add(e.item_id);const u=[];let _=0;const f=[];for(const t of l.get(e.item_id)??[])f.push({id:t.neighbor,depth:1,path:[{from_id:e.item_id,to_id:t.neighbor,relationship_type:t.relType}]});for(;f.length>0&&u.length<Hi;){const{id:e,depth:t,path:i}=f.shift();if(p.has(e))continue;if(null!==r&&t>r)continue;p.add(e);const n=c.get(e);if(n&&(u.push({item_id:e,title:It(n.title),type:n.type,depth:t,relationship_path:i}),t>_&&(_=t),null===r||t<r))for(const r of l.get(e)??[])p.has(r.neighbor)||f.push({id:r.neighbor,depth:t+1,path:[...i,{from_id:e,to_id:r.neighbor,relationship_type:r.relType}]})}let h=0;if(e.include_variable_refs){const{data:t}=await i.from("variable_references").select("variable_id").eq("item_id",e.item_id).eq("project_id",e.project_id),r=[...new Set((t??[]).map(e=>e.variable_id))];if(r.length>0){const{data:t}=await i.from("project_variables").select("id, name").in("id",r),n=new Map((t??[]).map(e=>[e.id,e.name])),{data:a}=await i.from("variable_references").select("variable_id, item_id").in("variable_id",r).eq("project_id",e.project_id).neq("item_id",e.item_id);for(const t of a??[]){const r=t.item_id;if(p.has(r))continue;if(u.length>=Hi)break;p.add(r);const i=c.get(r);if(!i)continue;const a=n.get(t.variable_id)??t.variable_id;u.push({item_id:r,title:It(i.title),type:i.type,depth:1,relationship_path:[{from_id:e.item_id,to_id:r,relationship_type:`variable_ref:${a}`}]}),h++}}}const g={};for(const e of u)g[e.type]=(g[e.type]??0)+1;const y=c.get(e.item_id);return Et(mt({root:{item_id:e.item_id,title:It(y?.title??n.title),type:y?.type??n.type},impacted_items:u,summary:{total_impacted:u.length,by_type:g,max_depth_reached:_,...h>0?{variable_impact_count:h}:{},...u.length>=Hi?{truncated:!0}:{}}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):Ot(kt(e instanceof Error?e.message:String(e)))}}U(Pi,{impactAnalysisHandler:()=>Mi});var $i=D({"src/tools/impact-analysis.ts"(){Ae(),Vt(),At(),Hi=200,Di=["parent","verifies","traces_to","allocated_from","decomposed_from"],Ui=["child","verified_by","allocated_to","decomposes_to","traced_by"],Ci={parent:"child",child:"parent",verifies:"verified_by",verified_by:"verifies",related:"related",traces_to:"traced_by",traced_by:"traces_to",allocated_to:"allocated_from",allocated_from:"allocated_to",decomposes_to:"decomposed_from",decomposed_from:"decomposes_to"}}}),qi=D({"src/core/relationship-sync.ts"(){}}),Ni={};function Wi(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ot(gt()):null}async function Li(e,t,r,i){const{data:n,error:a}=await e.from("items").select("id, deleted_at").eq("project_id",t).eq("id",r).maybeSingle();if(a)throw new Error(a.message);if(o=n,!Boolean(o?.id&&null===o.deleted_at))return;var o;const{error:s}=await e.from("item_relationships").delete().eq("project_id",t).eq("from_id",r);if(s)throw new Error(s.message);const d=function(e,t,r){const i=r.relationships??{},n=[],a=(r,i)=>{if(Array.isArray(r))for(const a of r)"string"==typeof a&&a.length>0&&a!==t&&n.push({from_id:t,to_id:a,type:i,project_id:e})};return a(i.parents,"parent"),a(i.children,"child"),a(i.related,"related"),a(i.verified_by,"verified_by"),a(i.verifies,"verifies"),n}(t,r,i);if(0===d.length)return;const c=Array.from(new Set(d.map(e=>e.to_id))),{data:l,error:m}=await e.from("items").select("id").eq("project_id",t).is("deleted_at",null).in("id",c);if(m)throw new Error(m.message);const p=function(e,t){return e.filter(e=>t.has(e.to_id))}(d,new Set((l??[]).map(e=>e.id)));if(0===p.length)return;const{error:u}=await e.from("item_relationships").insert(p);if(u)throw new Error(u.message)}async function Vi(e,t,r,i){if(await Li(e,t,r.id,r.data),"test-case"===r.type){const{error:n}=await e.rpc("sync_test_results_for_item",{p_project_id:t,p_item_id:r.id,p_actor_user_id:i});if(n)throw new Error(n.message)}}async function Bi(e,t,r,i){await e.from("change_history").insert({item_id:r.id,project_id:t,changed_by:i,event_type:"restored",old_data:r.oldData,new_data:r.data,actor:mn(),session_id:ln()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)})}async function Fi(e,t,r){const{data:i,error:n}=await e.from("projects").select("org_id").eq("id",t).maybeSingle();if(n)return Ot(kt(n.message));if(!i)return Ot(_t("Project",t));const{data:a,error:o}=await e.from("organizations").select("feature_flags").eq("id",i.org_id).maybeSingle();return o?Ot(kt(o.message)):!0!==(a?.feature_flags??{})[r]?Ot(ut(`Feature '${r}' is not enabled for this organization`,["Ask an org admin to enable the flag in Org Settings."])):null}async function zi(e){try{const t=await ve(),r=await async function(e,t){const{data:r,error:i}=await e.from("baselines").select("id, project_id, name, description, snapshot_at, created_at, created_by, locked_at, locked_by").eq("project_id",t).is("deleted_at",null).order("snapshot_at",{ascending:!1});if(i)throw new Error(`listBaselines: ${i.message}`);return r??[]}(t,e.project_id),i=Math.max(e.offset??0,0),n=Math.min(Math.max(e.limit??50,1),100);return Et(mt({baselines:r.slice(i,i+n).map(e=>({id:e.id,name:e.name,description:e.description,snapshot_at:e.snapshot_at,created_at:e.created_at,created_by:e.created_by,locked:null!==e.locked_at,locked_at:e.locked_at})),total:r.length,offset:i,limit:n}))}catch(e){return Wi(e)??Ot(kt(e instanceof Error?e.message:String(e)))}}async function Ji(e){try{const t=await ve(),r=await Fi(t,e.project_id,"baselines_enabled");if(r)return r;const{data:i,error:n}=await t.from("baselines").insert({project_id:e.project_id,name:e.name.trim(),description:e.description??null,snapshot_at:(new Date).toISOString()}).select().single();return n?"23505"===n.code?Ot(ut(`Baseline name "${e.name}" already exists in this project`,["Pick a different name"])):Ot(kt(n.message)):Et(mt({id:i.id,name:i.name,description:i.description,snapshot_at:i.snapshot_at,created_at:i.created_at,locked:!1}))}catch(e){return Wi(e)??Ot(kt(e instanceof Error?e.message:String(e)))}}async function Qi(e){try{const t=await ve(),r=await Mt(t,e.baseline_id);if(!r)return Ot(_t("Baseline",e.baseline_id));const i=await Fi(t,r.project_id,"baselines_enabled");if(i)return i;if(null!==r.locked_at)return Ot(ut("Baseline is already locked",["Locks are one-way; nothing to do"]));const{data:n}=await t.from("projects").select("org_id").eq("id",r.project_id).maybeSingle();if(!n)return Ot(_t("Project",r.project_id));const{data:a}=await t.auth.getUser(),o=a?.user?.id;if(!o)return Ot(gt());const{data:s}=await t.from("org_members").select("role").eq("user_id",o).eq("org_id",n.org_id).maybeSingle();if(!s||"admin"!==s.role)return Ot(ut("Only admins can lock baselines",["Ask an org admin to lock this baseline"]));const{data:d,error:c}=await t.from("baselines").update({locked_at:(new Date).toISOString(),locked_by:o}).eq("id",e.baseline_id).select().single();return c?Ot(kt(c.message)):Et(mt({id:d.id,locked_at:d.locked_at,locked_by:d.locked_by}))}catch(e){return Wi(e)??Ot(kt(e instanceof Error?e.message:String(e)))}}async function Gi(e){try{const t=await ve(),[r,i]=await Promise.all([Mt(t,e.baseline_a_id),Mt(t,e.baseline_b_id)]);if(!r||!i)return Ot(_t("Baseline","one of the baseline_ids"));if(r.project_id!==i.project_id)return Ot(ut("Both baselines must belong to the same project",["Verify both baseline_ids are from the same project_id"]));const[n,a]=await Promise.all([qt(t,r.project_id,r.snapshot_at),qt(t,i.project_id,i.snapshot_at)]),o=new Map(n.map(e=>[e.id,e])),s=new Map(a.map(e=>[e.id,e])),d=Array.from(new Set([...o.keys(),...s.keys()])).sort(),c=[];let l=0,m=0,p=0;for(const e of d){const t=o.get(e),r=s.get(e);if(!t&&r)l++,c.push({item_id:e,change_type:"added"});else if(t&&!r)m++,c.push({item_id:e,change_type:"removed"});else if(t&&r){const i=[];t.title!==r.title&&i.push("title"),t.type!==r.type&&i.push("type");JSON.stringify(t.data??{})!==JSON.stringify(r.data??{})&&i.push("data"),i.length>0&&(p++,c.push({item_id:e,change_type:"modified",fields_changed:i}))}}const u=Math.max(e.offset??0,0),_=Math.min(Math.max(e.limit??50,1),200),f=e.detail?c.slice(u,u+_):c.slice(u,u+_).map(({item_id:e,change_type:t,fields_changed:r})=>({item_id:e,change_type:t,fields_changed:r}));return Et(mt({summary:{added:l,removed:m,modified:p,total_compared:d.length},items:f,page_info:{offset:u,limit:_,total:c.length},baselines:{a:{id:r.id,name:r.name,snapshot_at:r.snapshot_at},b:{id:i.id,name:i.name,snapshot_at:i.snapshot_at}}}))}catch(e){return Wi(e)??Ot(kt(e instanceof Error?e.message:String(e)))}}async function Yi(e){try{const t=await ve(),r=await Mt(t,e.baseline_id);if(!r)return Ot(_t("Baseline",e.baseline_id));const i=await Fi(t,r.project_id,"baselines_enabled");if(i)return i;const{data:n}=await t.from("projects").select("org_id").eq("id",r.project_id).maybeSingle();if(!n)return Ot(_t("Project",r.project_id));const{data:a}=await t.auth.getUser(),o=a?.user?.id;if(!o)return Ot(gt());const{data:s}=await t.from("org_members").select("role").eq("user_id",o).eq("org_id",n.org_id).maybeSingle();if(!s||"admin"!==s.role)return Ot(ut("Only admins can restore from a baseline",["Ask an org admin to run the restore"]));const d=await qt(t,r.project_id,r.snapshot_at),c=d.map(e=>e.id),{data:l,error:m}=c.length>0?await t.from("items").select("*").eq("project_id",r.project_id).in("id",c):{data:[],error:null};if(m)return Ot(kt(m.message));const p=function(e,t){const r=new Map(t.map(e=>[e.id,e]));return e.flatMap(e=>{const t=r.get(e.id),i=[];var n,a;return t?(null!==t.deleted_at&&i.push("deleted_at"),t.type!==e.type&&i.push("type"),t.title!==e.title&&i.push("title"),n=t.data,a=e.data,JSON.stringify(n??{})!==JSON.stringify(a??{})&&i.push("data")):i.push("missing"),0===i.length?[]:[{id:e.id,type:e.type,title:e.title,data:e.data??{},diff_summary:{changed_fields:i,restored:!0},oldData:t?.data??null}]})}(d,l??[]),{data:u,error:_}=await t.rpc("variables_at",{p_project_id:r.project_id,p_snapshot_at:r.snapshot_at});if(_)return Ot(kt(_.message));const f=u??[];if(!0!==e.confirm)return Et(mt({preview:!0,items_to_restore:p.length,variables_to_restore:f.length}));let h=0;if(p.length>0){const e=p.map(({oldData:e,...t})=>t),{data:i,error:n}=await t.rpc("bulk_save_items_with_revisions",{p_project_id:r.project_id,p_items:e,p_user_id:o,p_event_source:"restore"});if(n)return Ot(kt(n.message));h="number"==typeof i?i:p.length;for(const e of p)await Vi(t,r.project_id,e,o),await Bi(t,r.project_id,e,o)}if(f.length>0){const{error:e}=await t.rpc("bulk_save_variables_with_revisions",{p_project_id:r.project_id,p_variables:f,p_user_id:o,p_event_source:"restore"});if(e)return Ot(kt(e.message))}return Et(mt({ok:!0,items_restored:h,variables_restored:f.length}))}catch(e){return Wi(e)??Ot(kt(e instanceof Error?e.message:String(e)))}}U(Ni,{createBaselineHandler:()=>Ji,diffBaselinesHandler:()=>Gi,listBaselinesHandler:()=>zi,lockBaselineHandler:()=>Qi,restoreBaselineHandler:()=>Yi,syncRestoredItemRelationships:()=>Li});var Ki,Zi,Xi,en,tn,rn,nn,an,on=D({"src/tools/baselines.ts"(){Ae(),un(),At(),Vt(),qi()}}),sn={};function dn(t){Q(t.toolName)&&function(t,r){const i=`ui://${r.toolName}/${r.htmlFile}`,n=function(e,t){const r=_(import.meta.url),i=o.dirname(r);return o.resolve(i,"apps","src","apps",e,t)}(r.appName,r.htmlFile),a=[...Tt.resourceDomains,...r.extraResourceDomains??[]];g(t,r.toolName,{title:r.title,description:r.description,inputSchema:r.inputSchema,annotations:r.annotations,_meta:{ui:{resourceUri:i}}},r.handler),y(t,`${r.title} UI`,i,{mimeType:b},async()=>{const t=await e.readFile(n,"utf-8");return{contents:[{uri:i,mimeType:b,text:t,_meta:{ui:{csp:{resourceDomains:a}}}}]}})}(en,t)}function cn(e,t){return async r=>{const i=P(B(),e);if(!i.allowed)return Ot(ut(`Tool ${e} is blocked by org policy.`,[$(i.reason),"Contact your org admin to adjust the MCP access policy."]));try{!function(e){const t=Ge();if(!t)return;if("object"!=typeof e||null===e)return;const r=e.project_id;if("string"==typeof r&&r!==t)throw new ze(t,r)}(r)}catch(e){if(e instanceof ze)return Ot(bt(e.expectedProjectId));throw e}{const e=B(),t=r?.project_id;if(null!==e.project_scope&&"string"==typeof t&&!e.project_scope.includes(t))return Ot(ut("Tool call rejected: project_id is outside your org's MCP project scope.",[`Allowed projects: ${e.project_scope.length} project${1===e.project_scope.length?"":"s"} configured by your admin`,"Use atoms_list_projects to see which projects you can access in this session"]))}let n,a;try{a=await ve(),n=ke()}catch{return t(r)}const o=await async function(e,t){const r=parseInt(process.env.ATOMS_RATE_LIMIT_RPM??"",10)||Le,i=new Date,n=new Date(i.getTime()-Ve);try{const{count:a,error:o}=await t.from("mcp_rate_limits").select("id",{count:"exact",head:!0}).eq("user_id",e).gte("requested_at",n.toISOString());if(o)throw new Error(o.message);if((a??0)>=r){const{data:r,error:a}=await t.from("mcp_rate_limits").select("requested_at").eq("user_id",e).gte("requested_at",n.toISOString()).order("requested_at",{ascending:!0}).limit(1).maybeSingle();if(a)throw new Error(a.message);const o=(r?new Date(r.requested_at).getTime():i.getTime())+Ve-i.getTime();return{allowed:!1,retryAfterSeconds:Math.max(1,Math.ceil(o/1e3))}}t.from("mcp_rate_limits").insert({user_id:e,requested_at:i.toISOString()}).then(({error:e})=>{e&&process.stderr.write(`[rate-limiter] insert failed: ${e.message}\n`)});const s=new Date(i.getTime()-2*Ve);return t.from("mcp_rate_limits").delete().eq("user_id",e).lt("requested_at",s.toISOString()).then(()=>{}),{allowed:!0}}catch(e){return process.stderr.write(`[rate-limiter] DB check failed (${e instanceof Error?e.message:String(e)}), falling back to allow.\n`),{allowed:!0}}}(n,a);if(!o.allowed)return Ot(wt(o.retryAfterSeconds));if("1"===process.env.ATOMS_MCP_LOCKDOWN||"true"===process.env.ATOMS_MCP_LOCKDOWN){const e=r?.project_id;if("string"==typeof e)try{const{requireWriteAccess:t}=await Promise.resolve().then(()=>(Ae(),we));await t(a,e)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message){const{accessDeniedError:e}=await Promise.resolve().then(()=>(At(),dt));return Ot(e("Viewer (lockdown mode: read access requires editor or admin)"))}}}const s=function(){const e=performance.now();return()=>Math.round(performance.now()-e)}();let d,c="success";try{const e=await t(r),i=e;if(!0===i?.isError){c="error";const e=i?.content;if(e?.[0]?.text)try{d=JSON.parse(e[0].text).message}catch{}}return e}catch(e){throw c="error",d=e instanceof Error?e.message:String(e),e}finally{const t={tool_name:e,params:r,status:c,duration_ms:s(),error_msg:d,project_id:r.project_id,session_id:rn,client_name:nn};try{!function(e,t,r){const i=We(r.params);e.from("mcp_audit_log").insert({user_id:t,tool_name:r.tool_name,params:i,status:r.status,duration_ms:r.duration_ms,error_msg:r.error_msg??null,project_id:r.project_id??null,session_id:r.session_id??null,client_name:r.client_name??null}).then(({error:e})=>{e&&process.stderr.write(`[audit] Failed to log: ${e.message}\n`)})}(await ve(),n,t)}catch{}}}}function ln(){return rn}function mn(){return an}function pn(){return nn}U(sn,{ATOMS_MCP_SERVER_VERSION:()=>Xi,getMcpActor:()=>mn,getMcpClientName:()=>pn,getMcpSessionId:()=>ln,server:()=>en});var un=D({"src/server.ts"(){Je(),Qe(),K(),Ke(),F(),L(),at(),ot(),Ae(),At(),Ht(),Ki=v(import.meta.url),Zi=Ki("../package.json"),Xi=Zi.version,en=new w({name:"atoms-mcp-server",version:Xi}),tn=(e,t,r)=>{if(Q(e))return en.registerTool(e,t,r)},rn=m(),nn=Ze(process.env.ATOMS_CLIENT_NAME),an=function(){const e=Ze(nn);return"cursor"===e?"mcp_cursor":"copilot"===e?"mcp_copilot":"mcp_claude"}(),tn("atoms_status",{title:"ATOMS MCP Health Check",description:'Check the health and authentication status of the ATOMS MCP server.\n\nCall this FIRST if other atoms tools fail or return auth errors.\nThis tool works WITHOUT authentication — it checks whether credentials\nexist and are valid, and tells the user exactly what to do if not.\n\nArgs: None\n\nReturns:\n { status: "authenticated"|"not_authenticated"|"expired", email, message, next_steps }\n\nExamples:\n - "Is ATOMS MCP working?" → atoms_status()\n - "Why are atoms tools failing?" → atoms_status()',inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},async()=>{try{const{readCredentials:e,isTokenValid:t}=await Promise.resolve().then(()=>(de(),Z)),r=await e();if(!r)return{content:[{type:"text",text:JSON.stringify({status:"not_authenticated",message:"No ATOMS credentials found. You need to login first.",next_steps:["1. Open a terminal on your machine","2. Run: npx @atoms-tech/atoms-mcp login","3. Complete the login in your browser","4. Restart Claude Desktop (quit and reopen)","5. Try your query again"]},null,2)}]};if(!t(r))try{const{getValidToken:e}=await Promise.resolve().then(()=>(be(),me)),{email:t}=await e(),{invalidateClient:r}=await Promise.resolve().then(()=>(Ae(),we));return r(),{content:[{type:"text",text:JSON.stringify({status:"authenticated",email:t,message:"Token was expired but has been refreshed. You're good to go!"},null,2)}]}}catch{return{content:[{type:"text",text:JSON.stringify({status:"expired",email:r.user_email??"unknown",message:"Your session has expired and could not be refreshed.",next_steps:["1. Open a terminal on your machine","2. Run: npx @atoms-tech/atoms-mcp logout","3. Run: npx @atoms-tech/atoms-mcp login","4. Complete the login in your browser","5. Restart Claude Desktop (quit and reopen)"]},null,2)}]}}return{content:[{type:"text",text:JSON.stringify({status:"authenticated",email:r.user_email??"unknown",message:"ATOMS MCP is connected and authenticated. All tools are ready."},null,2)}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({status:"error",message:`Health check failed: ${e instanceof Error?e.message:String(e)}`,next_steps:["1. Open a terminal","2. Run: npx @atoms-tech/atoms-mcp login","3. Restart Claude Desktop"]},null,2)}]}}}),tn("atoms_list_projects",{title:"List ATOMS Projects",description:'List all projects the authenticated user has access to.\n\nThis is the ENTRY POINT tool. Call this first to discover project IDs,\nthen use those IDs with all other tools.\n\nArgs: None\n\nReturns:\n { status: "success", data: [{ id, name, description, org_id, created_at }] }\n\nExamples:\n - "What projects do I have?" → atoms_list_projects()\n - "Show my projects" → atoms_list_projects()\n\nWorkflow:\n atoms_list_projects → atoms_list_items(project_id) → atoms_get_item(project_id, item_id)',inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_list_projects",async()=>{const{listProjectsHandler:e}=await Promise.resolve().then(()=>(zt(),Bt));return e()})),tn("atoms_list_items",{title:"List ATOMS Items",description:'List items in an ATOMS project with optional type/domain/level filters.\n\nThis is the primary ENTRY POINT for discovering items. Use it to browse\nproject contents before drilling into specific items with atoms_get_item.\n\nArgs:\n - project_id (string, UUID): The project to list items from\n - type (string, optional): Filter: requirement|test-case|note|table\n - domain (string, optional): Filter by domain tag\n - level (string, optional): Filter by level (System, Subsystem, Component)\n - limit (number, optional): Max results, 1-200 (default 50)\n - offset (number, optional): Pagination offset (default 0)\n\nReturns:\n { status: "success", data: [{ id, title, type, status, domains, level }], meta: { total_count, limit, offset, has_more } }\n\nExamples:\n - "Show me all requirements" → atoms_list_items(project_id, type="requirement")\n - "What test cases exist?" → atoms_list_items(project_id, type="test-case")\n\nErrors:\n - "Project not found" → verify project_id\n - "Access denied" → check org membership',inputSchema:{project_id:h.string().uuid("Must be a valid project UUID").describe("UUID of the project"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:h.string().max(64).optional().describe("Filter by domain tag (e.g., 'Safety', 'Performance')"),level:h.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),limit:h.number().int().min(1).max(200).default(50).describe("Max results (default 50, max 200)"),offset:h.number().int().min(0).default(0).describe("Pagination offset (default 0)"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID. Returns items as they were at that baseline's moment. Mutually exclusive with as_of."),as_of:h.string().datetime().optional().describe("Optional ISO 8601 timestamp. Returns items as they were at that moment. Mutually exclusive with baseline_id.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_list_items",async e=>{const{listItemsHandler:t}=await Promise.resolve().then(()=>(Gt(),Jt));return t(e)})),tn("atoms_get_item",{title:"Get ATOMS Item Details",description:'Get full details of a single item including relationships, test history, and ownership.\n\nUse after atoms_list_items or atoms_search to drill into a specific item.\n\nArgs:\n - project_id (string, UUID): The project containing the item\n - item_id (string): Item ID (e.g., "REQ-001", "TC-050")\n\nReturns:\n Full WorkItem with relationships, test runs, ownership, metadata.\n\nExamples:\n - "Show me requirement REQ-001" → atoms_get_item(project_id, "REQ-001")\n - "Get test case details" → atoms_get_item(project_id, "TC-050")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID (e.g., REQ-001)"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID. Returns the item as it was at that baseline's moment. Mutually exclusive with as_of."),as_of:h.string().datetime().optional().describe("Optional ISO 8601 timestamp. Returns the item as it was at that moment. Mutually exclusive with baseline_id.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_get_item",async e=>{const{getItemHandler:t}=await Promise.resolve().then(()=>(Zt(),Yt));return t(e)})),tn("atoms_search",{title:"Search ATOMS Items",description:'Full-text search across items in a project. Searches title, body, and summary.\n\nAlternative entry point to atoms_list_items when you know what you\'re looking for.\n\nArgs:\n - project_id (string, UUID): The project to search in\n - query (string): Search text (max 1000 chars)\n - type (string, optional): Filter by type\n - asset (string, optional): Filter to items with at least one run on the given asset\n (VIN, module, lot, ...). Case-sensitive exact match.\n - limit (number, optional): Max results, 1-100 (default 25)\n\nReturns:\n Matching items with @basic fields, ranked by relevance.\n\nExamples:\n - "Find braking requirements" → atoms_search(project_id, "braking system")\n - "Search for safety tests" → atoms_search(project_id, "safety", type="test-case")\n - "Tests that ran on this VIN" → atoms_search(project_id, "", asset="WEV...0005")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),query:h.string().min(1).max(1e3).describe("Search text"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),asset:h.string().max(200).optional().describe("Filter to items with at least one run on this asset (case-sensitive exact)"),limit:h.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID. Searches within the project state at that baseline's moment.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_search",async e=>{const{searchHandler:t}=await Promise.resolve().then(()=>(tr(),Xt));return t(e)})),tn("atoms_semantic_search",{title:"Semantic Search ATOMS Items",description:'Meaning-based search across items using pgvector embeddings (GTE-small, 384d).\n\nUse when atoms_search (exact/substring) misses conceptually related items.\nBest for natural-language concept queries; atoms_search is better for exact phrases or IDs.\n\nArgs:\n - project_id (string, UUID): The project to search in\n - query (string): Natural-language concept query (max 1000 chars)\n - type (string, optional): Filter by type\n - limit (number, optional): Max results, 1-50 (default 10)\n\nReturns:\n Semantically similar items ranked by cosine similarity.\n\nExamples:\n - "Find braking-related requirements" → atoms_semantic_search(project_id, "braking deceleration stopping distance")\n - "Safety-related test cases" → atoms_semantic_search(project_id, "hazard risk mitigation safety", type="test-case")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),query:h.string().min(1).max(1e3).describe("Natural-language concept query"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),limit:h.number().int().min(1).max(50).default(10).describe("Max results (default 10, max 50)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_semantic_search",async e=>{const{semanticSearchHandler:t}=await Promise.resolve().then(()=>(nr(),rr));return t(e)})),dn({appName:"coverage",htmlFile:"coverage-app.html",toolName:"atoms_get_coverage",title:"Get Test Coverage Report",description:'Find requirements without linked test cases (coverage gaps).\n\nEssential for compliance reporting in safety-critical industries.\n\nIn MCP App hosts (Claude, ChatGPT), renders a visual coverage heatmap.\nFalls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): The project to analyze\n - domain (string, optional): Filter by domain\n - level (string, optional): Filter by level\n - baseline_id (string, UUID, optional): coverage at that baseline\'s moment\n - as_of (string ISO 8601, optional): coverage at that moment\n baseline_id and as_of are mutually exclusive.\n\nReturns:\n { covered, uncovered, total, coverage_percent, uncovered_items: [...] }\n\nExamples:\n - "What\'s our test coverage?" → atoms_get_coverage(project_id)\n - "Safety coverage gaps?" → atoms_get_coverage(project_id, domain="Safety")\n - "Coverage at PDR submission" → atoms_get_coverage(project_id, baseline_id="...")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),domain:h.string().max(64).optional().describe("Filter by domain tag"),level:h.string().max(32).optional().describe("Filter by level"),baseline_id:h.string().uuid().optional().describe("Compute coverage as of this baseline's snapshot moment"),as_of:h.string().datetime().optional().describe("Compute coverage as of this ISO 8601 moment")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:cn("atoms_get_coverage",async e=>{const{getCoverageHandler:t}=await Promise.resolve().then(()=>(sr(),ar));return t(e)})}),tn("atoms_get_history",{title:"Get Item Change History",description:"Audit trail for an item — who changed what, when, and whether it was human or AI.\n\nShows actor attribution (user vs mcp_<client>) and session grouping.\n\nArgs:\n - project_id (string, UUID): The project containing the item\n - item_id (string): Item ID\n - limit (number, optional): Max entries (default 20, max 100)\n\nReturns:\n Change entries with actor, session_id, event_type, changed_at, fields_changed.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID"),limit:h.number().int().min(1).max(100).default(20).describe("Max entries (default 20)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_get_history",async e=>{const{getHistoryHandler:t}=await Promise.resolve().then(()=>(lr(),dr));return t(e)})),dn({appName:"mermaid",htmlFile:"mermaid-app.html",toolName:"atoms_export_mermaid",title:"Export Mermaid Diagram",description:"Generate a Mermaid diagram of the requirement/test hierarchy.\n\nOutput is paste-ready for markdown docs. Shows parent-child relationships\nand requirement-to-test-case verification links.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive pan/zoom diagram.\nFalls back to Mermaid text for non-UI clients.\n\nArgs:\n - project_id (string, UUID): The project to visualize\n - root_item_id (string, optional): Start node (default: all roots)\n - depth (number, optional): Max depth (default 3, max 10)\n - include_tests (boolean, optional): Show test case links (default true)\n - baseline_id (string, UUID, optional): render the graph at that baseline's moment\n - as_of (string ISO 8601, optional): render the graph at that moment\n baseline_id and as_of are mutually exclusive.\n\nReturns:\n Mermaid graph string.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),root_item_id:h.string().max(20).optional().describe("Root item ID to start from"),depth:h.number().int().min(1).max(10).default(3).describe("Max depth"),include_tests:h.boolean().default(!0).describe("Include test case links"),baseline_id:h.string().uuid().optional().describe("Render the graph as of this baseline's snapshot moment"),as_of:h.string().datetime().optional().describe("Render the graph as of this ISO 8601 moment")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},extraResourceDomains:["cdn.jsdelivr.net","mermaid.ink"],handler:cn("atoms_export_mermaid",async e=>{const{exportMermaidHandler:t}=await Promise.resolve().then(()=>(_r(),mr));return t(e)})}),tn("atoms_create_item",{title:"Create ATOMS Item",description:"Create a new requirement, test case, or note in a project.\n\nRequires editor or admin role. Changes are logged with AI actor attribution.\n\nArgs:\n - project_id (string, UUID): Target project\n - type (string): requirement|test-case|note\n - title (string): Item title (max 500 chars)\n - body (string, optional): Rich text body\n - summary (string, optional): Brief summary\n - domains (string[], optional): Domain tags\n - level (string, optional): System|Subsystem|Component\n - parent_ids (string[], optional): Parent item IDs to link\n\nReturns:\n Created item with generated ID (e.g., \"REQ-058\")\n\nSide effects:\n - Logs to change_history with actor='mcp_<client>'\n - Creates relationships if parent_ids provided",inputSchema:{project_id:h.string().uuid().describe("UUID of the target project"),type:h.enum(["requirement","test-case","note"]).describe("Item type"),title:h.string().min(1).max(500).describe("Item title"),body:h.string().max(5e4).optional().describe("Rich text body"),summary:h.string().max(2e3).optional().describe("Brief summary"),domains:h.array(h.string().max(64)).max(20).optional().describe("Domain tags"),level:h.string().max(32).optional().describe("System|Subsystem|Component"),parent_ids:h.array(h.string().max(20)).max(50).optional().describe("Parent item IDs to link"),echo:h.boolean().optional().describe("false → lean {id,type} response, saves ~580 tokens")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_create_item",async e=>{const{createItemHandler:t}=await Promise.resolve().then(()=>(gr(),fr));return t(e)})),tn("atoms_update_item",{title:"Update ATOMS Item",description:"Update an existing item's fields. Only provided fields are changed.\n\nRequires editor or admin role. Logs old/new data diff to change history.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Item to update\n - title, body, summary, domains, level, status: Fields to update (all optional)\n - expected_updated_at (string, optional): Pass metadata.updated_at from atoms_get_item to reject stale writes\n\nReturns:\n Updated item.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).optional().describe("Item ID (single form)"),title:h.string().min(1).max(500).optional().describe("New title"),body:h.string().max(5e4).optional().describe("New body"),summary:h.string().max(2e3).optional().describe("New summary"),domains:h.array(h.string().max(64)).max(20).optional().describe("New domain tags"),level:h.string().max(32).optional().describe("New level"),status:h.enum(["passed","failed","blocked","not-run"]).optional().describe("Test case status"),expected_updated_at:Xe.optional().describe("metadata.updated_at value from atoms_get_item; rejects stale writes"),echo:h.boolean().optional().describe("false → lean {id,updated_fields} response, saves ~580 tokens"),reason:h.string().max(500).optional().describe("Change rationale, persisted in audit history"),updates:h.array(h.object({item_id:h.string().min(1).max(20),title:h.string().min(1).max(500).optional(),body:h.string().max(5e4).optional(),summary:h.string().max(2e3).optional(),domains:h.array(h.string().max(64)).max(20).optional(),level:h.string().max(32).optional(),status:h.enum(["passed","failed","blocked","not-run"]).optional(),expected_updated_at:Xe.optional()})).optional().describe("Array form: update many items in one call"),idempotency_key:h.string().max(128).optional().describe("Safe retry key (24h TTL)"),continue_on_error:h.boolean().optional().describe("Allow partial success (default false = atomic)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_update_item",async e=>{const{updateItemHandler:t}=await Promise.resolve().then(()=>(jr(),yr));return t(e)})),tn("atoms_delete_item",{title:"Delete ATOMS Item",description:'Soft-delete an item (sets deleted_at, preserves for audit trail).\n\nRequires editor or admin role. The item is never truly removed.\n\nWhen ATOMS_MCP_REQUIRE_CONFIRMATION is set on the server, this tool uses a\ntwo-step flow: the first call returns a preview + confirmation_token; the\nsecond call (with the token) executes. Tokens expire in 60s and are bound\nto (tool, project_id, item_id).\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Item to delete\n - confirmation_token (string, optional): Token from a prior preview call\n\nReturns:\n Confirmation with deleted item summary, or { status: "confirmation_required",\n preview, confirmation_token } when the confirmation gate is active.',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID to soft-delete"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!1}},cn("atoms_delete_item",async e=>{const{deleteItemHandler:t}=await Promise.resolve().then(()=>(Dr(),Ar));return t(e)})),tn("atoms_restore_item",{title:"Restore ATOMS Item",description:'Undo a soft-delete — restores an item that was previously deleted with atoms_delete_item.\n\nOnly works on items that have been soft-deleted (still in the database with deleted_at set).\nHas no effect if the item is not deleted. Logs the restoration to change_history.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): ID of the deleted item to restore\n\nReturns:\n Confirmation with the restored item\'s title and type.\n\nExamples:\n - "Restore the deleted requirement REQ-042" → atoms_restore_item(project_id, "REQ-042")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().max(256).describe("ID of the soft-deleted item to restore")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_restore_item",async e=>{const{restoreItemHandler:t}=await Promise.resolve().then(()=>(Rr(),Ur));return t(e)})),tn("atoms_link_items",{title:"Link ATOMS Items",description:"Add or remove relationships between items.\n\nSupports: parent, child, related, verifies, verified_by.\nUpdates both items' JSONB data and the shadow table.\n\nArgs:\n - project_id (string, UUID): Project containing both items\n - from_id (string): Source item\n - to_id (string): Target item\n - type (string): parent|child|related|verifies|verified_by\n - action (string): add|remove\n - expected_from_updated_at / expected_to_updated_at (string, optional): pass metadata.updated_at values from atoms_get_item to reject stale link commands\n\nReturns:\n Updated relationship state for both items.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),from_id:h.string().min(1).max(20).optional().describe("Source item ID (single form)"),to_id:h.string().min(1).max(20).optional().describe("Target item ID (single form)"),type:h.enum(["parent","child","related","verifies","verified_by"]).optional().describe("Relationship type"),action:h.enum(["add","remove"]).optional().describe("Add or remove"),expected_from_updated_at:Xe.optional().describe("metadata.updated_at for from_id from atoms_get_item"),expected_to_updated_at:Xe.optional().describe("metadata.updated_at for to_id from atoms_get_item"),echo:h.boolean().optional().describe("false → lean response"),reason:h.string().max(500).optional().describe("Change rationale, persisted in audit history"),operations:h.array(h.object({action:h.enum(["add","remove"]),from_id:h.string().min(1).max(20),to_id:h.string().min(1).max(20),type:h.enum(["parent","child","related","verifies","verified_by"]),expected_from_updated_at:Xe.optional(),expected_to_updated_at:Xe.optional()})).optional().describe("Array form: batch relationship ops. Always atomic with DAG cycle detection."),idempotency_key:h.string().max(128).optional().describe("Safe retry key (24h TTL)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_link_items",async e=>{const{linkItemsHandler:t}=await Promise.resolve().then(()=>($r(),Pr));return t(e)})),dn({appName:"import",htmlFile:"import-app.html",toolName:"atoms_bulk_import",title:"Bulk Import ATOMS Items",description:"Bulk create multiple items in a single tool call. Essential for AI agents\nthat generate 20+ requirements at once.\n\nChecks write access once, generates sequential IDs, batch inserts all items,\nand reports per-item errors without aborting the entire batch.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive results table.\nFalls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): Target project\n - items (array): Up to 100 items to create, each with:\n - type (string): requirement|test-case|note\n - title (string): Item title (max 500 chars)\n - body (string, optional): Rich text body\n - summary (string, optional): Brief summary\n - domains (string[], optional): Domain tags\n - level (string, optional): System|Subsystem|Component\n - parent_id (string, optional): Parent item ID to link\n\nReturns:\n { created: number, items: [{ id, title, type }], errors: [] }\n\nLimits:\n - Maximum 100 items per call\n - If any item fails, others still succeed — errors reported separately\n\nSide effects:\n - Logs to change_history with actor='mcp_<client>' for each created item\n - Creates parent relationships for items with parent_id",inputSchema:{project_id:h.string().uuid().describe("UUID of the target project"),items:h.array(h.object({type:h.enum(["requirement","test-case","note"]).describe("Item type"),title:h.string().min(1).max(500).describe("Item title"),body:h.string().max(5e4).optional().describe("Rich text body"),summary:h.string().max(2e3).optional().describe("Brief summary"),domains:h.array(h.string().max(64)).max(20).optional().describe("Domain tags"),level:h.string().max(32).optional().describe("System|Subsystem|Component"),parent_id:h.string().max(20).optional().describe("Parent item ID to link")})).min(1).max(100).describe("Items to create (1-100)"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},handler:cn("atoms_bulk_import",async e=>{const{bulkImportHandler:t}=await Promise.resolve().then(()=>(Lr(),Nr));return t(e)})}),tn("atoms_record_test_result",{title:"Record Test Result",description:"Record a pass/fail/blocked result for a test case.\n\nAppends to canonical items.data.runs and projects test_results from that\nhistory. Use atoms_get_item to see all results.\n\nArgs:\n - project_id (string, UUID): Project containing the test case\n - item_id (string): Test case ID (e.g., \"TC-001\")\n - result (string): passed|failed|blocked|not-run\n - note (string, optional): Reason or comment for this result\n - asset (string, optional): Asset identifier this run was executed against\n (VIN, module ID, lot number, serial number, etc). Max 200 chars.\n Auto-trimmed; empty/whitespace is treated as omitted. If the asset\n name does not yet exist in the project, it is auto-registered.\n\nReturns:\n Recorded result with timestamp. When asset is provided, also returns\n asset_id and asset_created.\n\nSide effects:\n - Appends to items.data.runs through save_item_with_revision\n - Projects public.test_results from items.data.runs\n - Inserts into assets table if asset name is new in this project\n - Logs to change_history with actor='mcp_<client>'",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Test case ID (e.g., TC-001)"),result:h.enum(["passed","failed","blocked","not-run"]).describe("Test result"),note:h.string().max(1e3).optional().describe("Reason or comment for this result"),asset:h.string().max(200).optional().describe("Asset identifier this run was executed against (VIN, module ID, lot, etc). Auto-registered if new.")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_record_test_result",async e=>{const{recordTestResultHandler:t}=await Promise.resolve().then(()=>(Fr(),Vr));return t(e)})),tn("atoms_record_test_results",{title:"Record Test Results (bulk)",description:'Record many runs on one test case in a single atomic call.\n\nOptimized for multi-asset campaigns. Each entry can have its own asset and\nresult. Asset lookup-or-create happens per entry; new asset names are\nauto-registered. Runs are appended to canonical items.data.runs, then\ntest_results is projected from that history. The whole call is one DB\ntransaction; any entry failure rolls back the rest.\n\nUse the singular atoms_record_test_result for one-off runs.\n\nArgs:\n - project_id (string, UUID): Project containing the test case\n - item_id (string): Test case ID (e.g., "TC-001")\n - results (array, 1-100 entries): Each entry has:\n - asset (string, optional): Asset identifier (VIN, module ID, lot)\n - result (string, required): passed|failed|blocked|not-run\n - note (string, optional): Run note\n - date (string, optional): ISO 8601 datetime, default now()\n\nReturns:\n Per-entry outcomes with asset_id, asset_created, run_at, ok flags.\n\nSide effects:\n - Appends to items.data.runs through save_item_with_revision\n - Projects public.test_results from items.data.runs\n - Inserts new rows into assets for any asset name not already registered\n - Logs a single change_history entry summarizing the batch',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Test case ID (e.g., TC-001)"),results:h.array(h.object({asset:h.string().max(200).optional().describe("Asset identifier this run was executed against. Auto-registered if new."),result:h.enum(["passed","failed","blocked","not-run"]).describe("Test result"),note:h.string().max(500).optional().describe("Run note"),date:h.string().optional().describe("ISO 8601 datetime (default: now())")})).min(1).max(100).describe("1 to 100 result entries on this test case")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_record_test_results",async e=>{const{recordTestResultsBulkHandler:t}=await Promise.resolve().then(()=>(Zr(),zr));return t(e)})),dn({appName:"trace",htmlFile:"trace-app.html",toolName:"atoms_trace",title:"Trace Item Relationships",description:'Walk the traceability graph from a starting item.\n\nAnswers questions like "which requirements does TC-003 verify?" or\n"if I change REQ-001, what\'s affected downstream?"\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive graph visualization.\nFalls back to flat list for non-UI clients.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Starting item ID\n - direction (string): upstream|downstream|both\n - upstream: follow parent/verifies links (dependencies)\n - downstream: follow child/verified_by links (dependents)\n - both: trace in both directions\n - depth (number, optional): Max traversal depth (default 5, max 10)\n - relationship_types (string[], optional): Filter to specific types (parent, child, related, verifies, verified_by)\n\nReturns:\n { root, direction, items: [{ id, title, type, relationship, depth }], total_count }\n\nExamples:\n - "What does TC-003 verify?" → atoms_trace(project_id, "TC-003", "upstream")\n - "What\'s affected by REQ-001?" → atoms_trace(project_id, "REQ-001", "downstream")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Starting item ID"),direction:h.enum(["upstream","downstream","both"]).describe("Traversal direction"),depth:h.number().int().min(1).max(10).default(5).describe("Max traversal depth (default 5)"),relationship_types:h.array(h.enum(["parent","child","related","verifies","verified_by"])).optional().describe("Filter to specific relationship types")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:cn("atoms_trace",async e=>{const{traceHandler:t}=await Promise.resolve().then(()=>(ii(),ti));return t(e)})}),tn("atoms_list_assets",{title:"List Project Assets",description:"List asset identifiers registered in a project.\n\nAssets are free-form per-instance IDs (VINs, module names, lot numbers,\nserial numbers) that test runs are tagged against. They are auto-registered\nthe first time they appear on a run via atoms_record_test_result.\n\nArgs:\n - project_id (string, UUID): Project to list assets for\n - include_archived (boolean, optional): Include archived assets (default false)\n\nReturns:\n { assets: [{ id, name, archived_at, created_at }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),include_archived:h.boolean().optional().describe("Include archived assets (default false)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_list_assets",async e=>{const{listAssetsHandler:t}=await Promise.resolve().then(()=>(oi(),ni));return t(e)})),tn("atoms_list_variables",{title:"List Project Variables",description:"List all parameterized variables defined in a project.\n\nVariables are shared values (e.g., backup_camera_latency = 2s) that can be\nreferenced in requirement bodies as {variable_name}.\n\nArgs:\n - project_id (string, UUID): Project to list variables for\n - baseline_id (string, UUID, optional): list variables as they were at that baseline\n - as_of (string ISO 8601, optional): list variables as they were at that moment\n\nReturns:\n { variables: [{ id, name, value, unit, description }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID for time-travel reads"),as_of:h.string().datetime().optional().describe("Optional ISO timestamp for time-travel reads")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_list_variables",async e=>{const{listVariablesHandler:t}=await Promise.resolve().then(()=>(hi(),si));return t(e)})),tn("atoms_get_variable",{title:"Get Project Variable",description:'Get a specific variable by name, including which items reference it.\n\nArgs:\n - project_id (string, UUID): Project containing the variable\n - variable_name (string): Variable name (e.g., "backup_camera_latency")\n - baseline_id (string, UUID, optional): get the variable as it was at that baseline\n - as_of (string ISO 8601, optional): get the variable as it was at that moment\n\nReturns:\n { variable, referenced_by: [{ id, title, type }], reference_count }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),variable_name:h.string().min(1).max(64).describe("Variable name"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID for time-travel reads"),as_of:h.string().datetime().optional().describe("Optional ISO timestamp for time-travel reads")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_get_variable",async e=>{const{getVariableHandler:t}=await Promise.resolve().then(()=>(hi(),si));return t(e)})),tn("atoms_update_variable",{title:"Update Project Variable",description:"Update a variable's value, unit, or description.\n\nArgs:\n - project_id (string, UUID): Project containing the variable\n - variable_name (string): Variable name\n - value (string, optional): New value\n - unit (string, optional): Unit abbreviation (e.g., 's', 'Hz')\n - description (string, optional): Human-readable description\n\nReturns:\n { variable }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),variable_name:h.string().min(1).max(64).describe("Variable name"),value:h.string().max(500).optional().describe("New value"),unit:h.string().max(32).optional().describe("Unit abbreviation"),description:h.string().max(2e3).optional().describe("Description")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_update_variable",async e=>{const{updateVariableHandler:t}=await Promise.resolve().then(()=>(hi(),si));return t(e)})),tn("atoms_create_variable",{title:"Create Project Variable",description:'Create one or many parameterized variables for a project.\n\nSingle form: provide name + value.\nArray form: provide variables[] for bulk creation (one atomic INSERT).\n\nVariables can then be embedded in requirement bodies as {variable_name}.\n\nArgs (single form):\n - project_id (string, UUID): Project to add the variable to\n - name (string): Variable name, letters, digits, underscores; must start with letter/underscore\n - value (string): Initial value (e.g., "100", "true", "nominal")\n - unit (string, optional): Unit abbreviation (e.g., "Hz", "ms", "m/s²")\n - description (string, optional): Human-readable description\n - echo (bool, optional): false → lean {name} response (default true)\n\nArgs (array form):\n - project_id (string, UUID): Project UUID\n - variables[]: Array of { name, value, unit?, description? }\n - echo (bool, optional): false → lean {name} list (default true)\n - idempotency_key (string, optional): Dedup key\n\nReturns (single): { variable, message }\nReturns (array): { batch_id, created, variables, message }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).optional().describe("Variable name (single form)"),value:h.string().min(1).max(500).optional().describe("Initial value (single form)"),unit:h.string().max(32).optional().describe("Unit abbreviation"),description:h.string().max(2e3).optional().describe("Description"),echo:h.boolean().optional().describe("false → lean response (default true)"),variables:h.array(h.object({name:h.string().min(1).max(64).describe("Variable name"),value:h.string().min(1).max(500).describe("Initial value"),unit:h.string().max(32).optional().describe("Unit abbreviation"),description:h.string().max(2e3).optional().describe("Description")})).optional().describe("Array form: batch variable creation. Atomic — all or none."),idempotency_key:h.string().max(128).optional().describe("Dedup key for retries")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_create_variable",async e=>{const{createVariableHandler:t}=await Promise.resolve().then(()=>(hi(),si));return t(e)})),tn("atoms_delete_variable",{title:"Delete Project Variable",description:"Delete a project variable. Refuses by default if any items reference it.\n\nArgs:\n - project_id (string, UUID): Project containing the variable\n - name (string): Variable name to delete\n - force (bool, optional): Delete even if items still reference it (default false)\n\nReturns:\n { deleted, name, referenced_by_items, warning? }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Variable name to delete"),force:h.boolean().optional().describe("Delete even if items reference it (default false)"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1}},cn("atoms_delete_variable",async e=>{const{deleteVariableHandler:t}=await Promise.resolve().then(()=>(hi(),si));return t(e)})),tn("atoms_list_domains",{title:"List Project Domains",description:"List all domain tags registered in a project's taxonomy, with item counts.\n\nArgs:\n - project_id (string, UUID): Project to inspect\n\nReturns:\n { domains: [{ name, item_count }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_list_domains",async e=>{const{listDomainsHandler:t}=await Promise.resolve().then(()=>(Si(),gi));return t(e)})),tn("atoms_create_domain",{title:"Create Project Domain",description:'Register a new domain tag in the project taxonomy.\n\nOnce created, items can be tagged with this domain via atoms_create_item or atoms_update_item.\n\nArgs:\n - project_id (string, UUID): Project to add the domain to\n - name (string): Domain name (lowercased automatically, e.g., "safety", "can-bus")\n\nReturns:\n { domain: { name }, message }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Domain name")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_create_domain",async e=>{const{createDomainHandler:t}=await Promise.resolve().then(()=>(Si(),gi));return t(e)})),tn("atoms_update_domain",{title:"Rename Project Domain",description:"Rename a domain tag. Updates the taxonomy and propagates to all tagged items.\n\nUse dry_run=true first to see how many items will be affected before committing.\n\nArgs:\n - project_id (string, UUID): Project containing the domain\n - name (string): Current domain name\n - new_name (string): New domain name\n - dry_run (bool, optional): If true, return impact count without making changes (default false)\n\nReturns (dry_run=false):\n { domain: { name }, renamed_from, items_updated, message }\n\nReturns (dry_run=true):\n { dry_run: true, rename: { from, to }, items_affected, message }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Current domain name"),new_name:h.string().min(1).max(64).describe("New domain name"),dry_run:h.boolean().optional().describe("Preview impact without applying (default false)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_update_domain",async e=>{const{updateDomainHandler:t}=await Promise.resolve().then(()=>(Si(),gi));return t(e)})),tn("atoms_delete_domain",{title:"Delete Project Domain",description:"Delete a domain tag from the project taxonomy.\n\nRefuses by default if any items are tagged with it. With force=true, deletes the\ndomain and strips the tag from all items.\n\nArgs:\n - project_id (string, UUID): Project containing the domain\n - name (string): Domain name to delete\n - force (bool, optional): Strip from items and delete anyway (default false)\n\nReturns:\n { deleted, name, items_updated, message }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Domain name to delete"),force:h.boolean().optional().describe("Strip from all items and delete (default false)"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1}},cn("atoms_delete_domain",async e=>{const{deleteDomainHandler:t}=await Promise.resolve().then(()=>(Si(),gi));return t(e)})),dn({appName:"summary",htmlFile:"summary-app.html",toolName:"atoms_project_summary",title:"Project Compliance Summary",description:'One-call project health and compliance dashboard.\n\nReturns item counts by type, test execution status, requirement coverage\n(overall and by domain), and recent change activity.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive dashboard with charts.\nFalls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): Project to summarize\n\nReturns:\n { project_name, counts, test_status, coverage, coverage_by_domain, recent_changes, last_updated }\n\nExamples:\n - "Give me a compliance snapshot" → atoms_project_summary(project_id)\n - "How\'s our test coverage?" → atoms_project_summary(project_id)',inputSchema:{project_id:h.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:cn("atoms_project_summary",async e=>{const{projectSummaryHandler:t}=await Promise.resolve().then(()=>(Oi(),ki));return t(e)})}),dn({appName:"browse",htmlFile:"browse-app.html",toolName:"atoms_browse",title:"Browse ATOMS Items",description:'View and filter items in an ATOMS project with a visual browser.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive filterable list\nwith expandable item details. Falls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): The project to browse\n - type (string, optional): Filter by type (requirement, test-case, note, table)\n - domain (string, optional): Filter by domain tag\n - level (string, optional): Filter by level (System, Subsystem, Component)\n - query (string, optional): Full-text search query\n - limit (number, optional): Max results (default 25, max 100)\n - offset (number, optional): Pagination offset (default 0)\n\nReturns:\n { items: [...], filters: { domains, levels, types }, meta: { total_count, limit, offset, has_more } }\n\nExamples:\n - "Show me the requirements" → atoms_browse(project_id, type="requirement")\n - "Browse safety items" → atoms_browse(project_id, domain="Safety")\n - "Find braking requirements" → atoms_browse(project_id, query="braking")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:h.string().max(64).optional().describe("Filter by domain tag"),level:h.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),query:h.string().max(1e3).optional().describe("Full-text search query"),limit:h.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)"),offset:h.number().int().min(0).default(0).describe("Pagination offset (default 0)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:cn("atoms_browse",async e=>{const{browseHandler:t}=await Promise.resolve().then(()=>(Ri(),Ti));return t(e)})}),tn("atoms_impact_analysis",{title:"Analyze Impact of Changing an Item",description:'Analyze the blast radius of changing a specific item.\n\nTraverses the relationship graph from the given item and returns every item that\nwould be affected by a change — with their types, depths, and full relationship paths.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Item to analyze (e.g., "REQ-001")\n - direction (string, optional): "downstream" (default) | "upstream" | "both"\n - downstream: what this item affects (children, verified_by)\n - upstream: what affects this item (parents, verifies)\n - both: full bidirectional traversal\n - depth (integer, optional): Max traversal depth. Omit for full depth (all reachable nodes).\n - include_variable_refs (boolean, optional): Reserved for future variable-aware impact. Default true.\n\nReturns:\n { root: { item_id, title, type }, impacted_items: [{ item_id, title, type, depth, relationship_path }], summary: { total_impacted, by_type, max_depth_reached } }\n\nExamples:\n - "What breaks if I change REQ-001?" → atoms_impact_analysis(project_id, "REQ-001")\n - "What\'s upstream of TC-005?" → atoms_impact_analysis(project_id, "TC-005", direction="upstream")\n - "Show direct dependencies only" → atoms_impact_analysis(project_id, "REQ-001", depth=1)\n\nErrors:\n - "Item not found" → verify item_id exists in the project',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID to analyze (e.g., REQ-001)"),direction:h.enum(["downstream","upstream","both"]).default("downstream").describe("Traversal direction (default: downstream)"),depth:h.number().int().min(1).optional().describe("Max traversal depth. Omit for full depth."),include_variable_refs:h.boolean().default(!0).describe("Reserved for variable-aware impact (future). Default true.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_impact_analysis",async e=>{const{impactAnalysisHandler:t}=await Promise.resolve().then(()=>($i(),Pi));return t(e)})),tn("atoms_list_baselines",{title:"List ATOMS Baselines",description:"List named baselines (named project snapshots) for a project, newest first.\n\nA baseline is a labeled moment in time. Use atoms_create_baseline to make one,\natoms_diff_baselines to compare two, and atoms_restore_baseline to roll back.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),limit:h.number().int().min(1).max(100).default(50).describe("Max results (default 50, max 100)"),offset:h.number().int().min(0).default(0).describe("Pagination offset")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_list_baselines",async e=>{const{listBaselinesHandler:t}=await Promise.resolve().then(()=>(on(),Ni));return t(e)})),tn("atoms_create_baseline",{title:"Create ATOMS Baseline",description:"Create a named baseline (snapshot label) at the current moment for a project.\n\nThe baseline records a name + timestamp. It does NOT copy item data; the project\nstate at that moment is reconstructed via temporal queries against item_revisions.\nNames must be unique per project (case-insensitive).",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(200).describe("Baseline name (1-200 chars, unique per project)"),description:h.string().max(2e3).optional().describe("Optional description (max 2000 chars)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_create_baseline",async e=>{const{createBaselineHandler:t}=await Promise.resolve().then(()=>(on(),Ni));return t(e)})),tn("atoms_lock_baseline",{title:"Lock ATOMS Baseline",description:"Lock a baseline so it becomes audit-grade immutable. Admin only. One-way: there is no unlock.\n\nAfter locking, the baseline's name, description, and snapshot_at cannot be changed,\nand it cannot be deleted. Use for regulatory submissions and DRBs.",inputSchema:{baseline_id:h.string().uuid().describe("UUID of the baseline to lock")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},cn("atoms_lock_baseline",async e=>{const{lockBaselineHandler:t}=await Promise.resolve().then(()=>(on(),Ni));return t(e)})),tn("atoms_diff_baselines",{title:"Diff ATOMS Baselines",description:"Compute the diff between two baselines: which items were added, removed, or modified.\n\nReturns a summary plus a paginated items array. Pass detail=true to include\nper-item field-level diffs (slower for large projects).",inputSchema:{baseline_a_id:h.string().uuid().describe("UUID of the first (older) baseline"),baseline_b_id:h.string().uuid().describe("UUID of the second (newer) baseline"),detail:h.boolean().default(!1).describe("Include per-item field-level diffs (default false)"),limit:h.number().int().min(1).max(200).default(50).describe("Max items per page (default 50)"),offset:h.number().int().min(0).default(0).describe("Pagination offset")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},cn("atoms_diff_baselines",async e=>{const{diffBaselinesHandler:t}=await Promise.resolve().then(()=>(on(),Ni));return t(e)})),tn("atoms_restore_baseline",{title:"Restore from ATOMS Baseline",description:"Restore project state to a baseline. Admin only. Preview-by-default.\n\nCall with confirm=false (default) for a dry run that returns items_to_restore count.\nCall with confirm=true to execute: each item present at the baseline gets a new\nrevision matching its historical state, event_source='restore'. Items added AFTER\nthe baseline are NOT deleted. Always recoverable via further history.",inputSchema:{baseline_id:h.string().uuid().describe("UUID of the baseline to restore from"),confirm:h.boolean().default(!1).describe("Set true to execute; false (default) returns a preview")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1}},cn("atoms_restore_baseline",async e=>{const{restoreBaselineHandler:t}=await Promise.resolve().then(()=>(on(),Ni));return t(e)}))}});K(),F(),Ae(),V();var _n=v(import.meta.url)("../package.json"),fn=process.argv.slice(2)[0];(async function(){switch(fn){case"login":{const{login:e}=await Promise.resolve().then(()=>(qe(),He));try{const{email:t}=await e();process.stderr.write(`\n ✓ Authenticated as ${t}\n`),process.stderr.write(" ✓ Credentials saved to ~/.atoms/credentials.json\n\n"),process.stderr.write(" Next steps:\n"),process.stderr.write(" $ claude mcp add atoms -- npx -y @atoms-tech/atoms-mcp\n"),process.stderr.write("\n Or add to Claude Desktop (Settings → MCP → Edit Config):\n"),process.stderr.write(' {\n "mcpServers": {\n "atoms": {\n "command": "npx",\n "args": ["-y", "@atoms-tech/atoms-mcp"]\n }\n }\n }\n\n')}catch(e){process.stderr.write(`\n ✗ ${e instanceof Error?e.message:String(e)}\n\n`),process.exit(1)}break}case"whoami":{const{getValidToken:e}=await Promise.resolve().then(()=>(be(),me));try{const{email:t,user_id:r}=await e();process.stderr.write(`\n ✓ Logged in as ${t}\n User ID: ${r}\n\n`)}catch(e){process.stderr.write("\n ✗ Not authenticated.\n Run: npx @atoms-tech/atoms-mcp login\n\n"),process.exit(1)}break}case"logout":{const{clearCredentials:e}=await Promise.resolve().then(()=>(de(),Z));await e(),process.stderr.write("Logged out. Credentials removed.\n"),process.stderr.write("Run 'npx @atoms-tech/atoms-mcp login' to authenticate again.\n");break}case"--version":case"-v":process.stderr.write(`@atoms-tech/atoms-mcp v${_n.version}\n`);break;case"--help":case"-h":process.stderr.write("ATOMS MCP Server — AI agent integration for requirements management\n\nUsage:\n npx @atoms-tech/atoms-mcp Start MCP server (stdio)\n npx @atoms-tech/atoms-mcp login Authenticate with ATOMS.tech\n npx @atoms-tech/atoms-mcp logout Switch account / clear credentials\n npx @atoms-tech/atoms-mcp whoami Show current user\n npx @atoms-tech/atoms-mcp --version Show version\n");break;default:{process.stderr.write("[atoms-mcp] Starting MCP server via stdio...\n");try{const{getValidToken:e}=await Promise.resolve().then(()=>(be(),me)),{email:t}=await e();process.stderr.write(`[atoms-mcp] Authenticated as ${t}\n`)}catch{process.stderr.write("[atoms-mcp] No credentials found. Starting login flow...\n");try{const{login:e}=await Promise.resolve().then(()=>(qe(),He)),{email:t}=await e();process.stderr.write(`[atoms-mcp] Authenticated as ${t}\n`)}catch(e){process.stderr.write(`[atoms-mcp] Login failed: ${e instanceof Error?e.message:String(e)}\n[atoms-mcp] Starting server without auth. Tools will fail until you run: npx @atoms-tech/atoms-mcp login\n`)}}try{const t=await ve(),r=ke(),i=await async function(e,t){const r=process.env.ATOMS_MCP_ORG_ID?.trim();if(r)return r;const{data:i,error:n}=await e.from("org_members").select("org_id, joined_at").eq("user_id",t).order("joined_at",{ascending:!0}).limit(1).maybeSingle();return n?(process.stderr.write(`[atoms-mcp] Could not resolve session org: ${n.message}\n`),null):i?.org_id??null}(t,r),n=await async function(e,t,r){if(!r)return{role:null,is_member:!1,effective:q};const{data:i,error:n}=await e.rpc("mcp_resolve_policy",{p_user_id:t,p_org_id:r});return n?(process.stderr.write(`[atoms-mcp] mcp_resolve_policy failed (${n.code}): ${n.message}. Falling back to permissive policy.\n`),{role:null,is_member:!1,effective:q}):function(e){if(!e||"object"!=typeof e)return{role:null,is_member:!1,effective:q};const t=e,r="string"==typeof t.role?t.role:null,i=!0===t.is_member,n=t.effective??{},a=(e,t)=>Array.isArray(e)?e.filter(e=>"string"==typeof e):t,o=(e,t)=>"boolean"==typeof e?e:t,s=n.project_scope,d=Array.isArray(s)&&s.length>0?s.filter(e=>"string"==typeof e):null;return{role:r,is_member:i,effective:{read_only:o(n.read_only,q.read_only),lockdown:o(n.lockdown,q.lockdown),block_destructive:o(n.block_destructive,q.block_destructive),block_bulk_import:o(n.block_bulk_import,q.block_bulk_import),cross_project_browse:o(n.cross_project_browse,q.cross_project_browse),allowed_toolsets:a(n.allowed_toolsets,q.allowed_toolsets),forbidden_tools:a(n.forbidden_tools,q.forbidden_tools),project_scope:d,require_confirmation_for:a(n.require_confirmation_for,q.require_confirmation_for)}}}(i)}(t,r,i);e={effective:n.effective,role:n.role,org_id:i},N=e.effective,W=e.org_id,n.is_member&&i?process.stderr.write(`[atoms-mcp] Org policy loaded: org=${i.slice(0,8)}… role=${n.role??"?"} read_only=${n.effective.read_only} lockdown=${n.effective.lockdown}\n`):process.stderr.write("[atoms-mcp] No org membership detected — running with permissive default policy.\n")}catch(e){process.stderr.write(`[atoms-mcp] Could not resolve session policy (${e instanceof Error?e.message:String(e)}). Running with permissive default — RLS still enforces row-level access.\n`)}process.stderr.write(`[atoms-mcp] Tool registry: ${function(){const e=J(),t=z(),r=[];r.push(e?"read-only":"read+write"),null===t?r.push("all toolsets"):(r.push(`toolsets=${[...t.allowed].sort().join(",")||"(none — only always-on tools)"}`),t.unknown.length>0&&r.push(`unknown=${t.unknown.join(",")}`));const i=process.env.ATOMS_MCP_PROJECT_ID?.trim();return i&&r.push(`project_scope=${i.slice(0,8)}…`),("1"===process.env.ATOMS_MCP_LOCKDOWN||"true"===process.env.ATOMS_MCP_LOCKDOWN)&&r.push("lockdown=on"),r.join(" | ")}()}\n`);const{server:t}=await Promise.resolve().then(()=>(un(),sn)),r=new j;await t.connect(r),process.stderr.write("[atoms-mcp] MCP server running. Waiting for tool calls...\n")}}var e})().catch(e=>{process.stderr.write(`[atoms-mcp] Fatal: ${e instanceof Error?e.message:String(e)}\n`),process.exit(1)});
2
+ import e,{readFile as t,mkdir as r,writeFile as i,unlink as n}from"fs/promises";import{homedir as a}from"os";import s,{join as o}from"path";import{createClient as d}from"@supabase/supabase-js";import{createServer as c}from"http";import l,{randomUUID as m,randomBytes as p,createHash as u}from"crypto";import{fileURLToPath as _,URL as f}from"url";import{z as h}from"zod";import{registerAppTool as g,registerAppResource as y,RESOURCE_MIME_TYPE as b}from"@modelcontextprotocol/ext-apps/server";import{McpServer as w}from"@modelcontextprotocol/sdk/server/mcp.js";import{createRequire as v}from"module";import{StdioServerTransport as j}from"@modelcontextprotocol/sdk/server/stdio.js";var x,S,k,I,E,O,A,T=Object.defineProperty,H=Object.getOwnPropertyNames,U=(e,t)=>function(){return e&&(t=(0,e[H(e)[0]])(e=0)),t},D=(e,t)=>{for(var r in t)T(e,r,{get:t[r],enumerable:!0})};function R(e){return A.get(e)}var C=U({"src/core/tool-catalog.ts"(){x=new Set(["atoms_status","atoms_list_projects"]),S=new Set(["atoms_create_item","atoms_update_item","atoms_delete_item","atoms_restore_item","atoms_link_items","atoms_bulk_import","atoms_record_test_result","atoms_record_test_results","atoms_create_variable","atoms_update_variable","atoms_delete_variable","atoms_create_domain","atoms_update_domain","atoms_delete_domain","atoms_create_level","atoms_create_baseline","atoms_lock_baseline","atoms_restore_baseline"]),k=new Set(["atoms_delete_item","atoms_delete_variable","atoms_delete_domain","atoms_restore_baseline"]),I=new Set(["atoms_bulk_import"]),E={items:["atoms_list_items","atoms_get_item","atoms_search","atoms_semantic_search","atoms_browse","atoms_get_history","atoms_create_item","atoms_update_item","atoms_delete_item","atoms_restore_item","atoms_bulk_import"],traceability:["atoms_trace","atoms_link_items","atoms_export_mermaid","atoms_impact_analysis"],coverage:["atoms_get_coverage","atoms_record_test_result","atoms_record_test_results","atoms_project_summary","atoms_list_assets"],variables:["atoms_list_variables","atoms_get_variable","atoms_create_variable","atoms_update_variable","atoms_delete_variable"],domains:["atoms_list_domains","atoms_create_domain","atoms_update_domain","atoms_delete_domain","atoms_list_levels","atoms_create_level"],baselines:["atoms_list_baselines","atoms_create_baseline","atoms_lock_baseline","atoms_diff_baselines","atoms_restore_baseline"]},O=Object.keys(E),A=new Map;for(const[e,t]of Object.entries(E))for(const r of t)A.set(r,e)}});function P(e,t){if(x.has(t))return{allowed:!0};if(e.lockdown&&!new Set(["atoms_list_items","atoms_get_item","atoms_search","atoms_browse"]).has(t))return{allowed:!1,reason:"lockdown"};if(e.forbidden_tools.includes(t))return{allowed:!1,reason:"forbidden_tool"};if(e.read_only&&S.has(t))return{allowed:!1,reason:"read_only"};if(e.block_destructive&&k.has(t))return{allowed:!1,reason:"block_destructive"};if(e.block_bulk_import&&I.has(t))return{allowed:!1,reason:"block_bulk_import"};const r=R(t);return void 0===r||e.allowed_toolsets.includes(r)?{allowed:!0}:{allowed:!1,reason:"toolset_disabled"}}function $(e,t){return P(e,t).allowed}function M(e){switch(e){case"lockdown":return"MCP is in lockdown mode for your role — only read-only browse is available.";case"read_only":return"Your org policy puts MCP in read-only mode. Mutating tools are not exposed.";case"block_destructive":return"Your org policy blocks destructive operations (delete tools).";case"block_bulk_import":return"Your org policy blocks bulk import.";case"forbidden_tool":return"This tool is on your org's forbidden list.";case"toolset_disabled":return"This tool's toolset is not enabled in your org policy."}}var q,N,L,W,V=U({"src/core/policy.ts"(){C()}}),B=U({"src/core/types.ts"(){q={read_only:!1,lockdown:!1,block_destructive:!1,block_bulk_import:!1,cross_project_browse:!0,allowed_toolsets:["items","traceability","coverage","variables","domains","baselines"],forbidden_tools:[],project_scope:null,require_confirmation_for:[]},N={read_only:!0,lockdown:!0,block_destructive:!0,block_bulk_import:!0,cross_project_browse:!1,allowed_toolsets:[],forbidden_tools:[],project_scope:[],require_confirmation_for:[]}}});function F(){return L}var z=U({"src/middleware/session-policy.ts"(){B(),L=q,W=null}});function J(){const e=process.env.ATOMS_MCP_TOOLSETS?.trim();if(!e)return null;const t=e.split(",").map(e=>e.trim()).filter(Boolean);if(0===t.length)return null;const r=new Set,i=[];for(const e of t)O.includes(e)?r.add(e):i.push(e);return{allowed:r,unknown:i}}function Q(){const e=process.env.ATOMS_MCP_READ_ONLY;return"1"===e||"true"===e?.toLowerCase()}function G(e){if(Q()&&S.has(e))return!1;if(x.has(e))return!0;const t=J();if(null===t)return $(F(),e);const r=R(e);return!!r&&!!t.allowed.has(r)&&$(F(),e)}var Y,K,Z=U({"src/middleware/tool-registry.ts"(){C(),V(),z()}}),X={};async function ee(){try{const e=await t(K,"utf-8"),r=JSON.parse(e);return r.access_token&&r.refresh_token?r:null}catch{return null}}async function te(e){await r(Y,{recursive:!0,mode:448}),await i(K,JSON.stringify(e,null,2),{mode:384})}async function re(){const e=await ee();return!!e&&e.expires_at>Date.now()+6e4}function ie(e){return e.expires_at>Date.now()+6e4}async function ne(){try{await n(K)}catch{}}function ae(){return K}D(X,{clearCredentials:()=>ne,getCredentialsPath:()=>ae,hasValidCredentials:()=>re,isTokenValid:()=>ie,readCredentials:()=>ee,writeCredentials:()=>te});var se,oe,de,ce=U({"src/auth/token-store.ts"(){Y=o(a(),".atoms"),K=o(Y,"credentials.json")}}),le={};function me(e){return e.trim().replace(/\/+$/,"")}D(le,{ATOMS_APP_URL:()=>de,ATOMS_SUPABASE_ANON_KEY:()=>oe,ATOMS_SUPABASE_URL:()=>se,resolveUrl:()=>me});var pe=U({"src/config.ts"(){se=me(process.env.ATOMS_SUPABASE_URL??"https://gmebjyhomsbvhrxffzre.supabase.co"),oe=process.env.ATOMS_SUPABASE_ANON_KEY??"sb_publishable_b49MUAB8XCrQiF7x5b9lUA_w1aplSBH",de=process.env.ATOMS_APP_URL??"https://atoms.tech"}}),ue={};async function _e(){const e=process.env.ATOMS_ACCESS_TOKEN;if(e){const t=fe(e);return{access_token:e,refresh_token:"",user_id:t.sub,email:t.email??""}}const t=await ee();if(!t)throw new Error("Not authenticated. Run 'npx @atoms-tech/atoms-mcp login' to connect your ATOMS account.");if(t.expires_at>Date.now()+6e4){const e=fe(t.access_token);return{access_token:t.access_token,refresh_token:t.refresh_token,user_id:e.sub,email:e.email??t.user_email??""}}process.stderr.write("[atoms-mcp] Refreshing access token...\n");const r=d(se,oe),{data:i,error:n}=await r.auth.refreshSession({refresh_token:t.refresh_token});if(n||!i.session)throw new Error(`Token refresh failed: ${n?.message??"No session returned"}. Re-run 'npx @atoms-tech/atoms-mcp login'.`);const a=i.session;return await te({access_token:a.access_token,refresh_token:a.refresh_token,expires_at:Date.now()+1e3*(a.expires_in??3600),user_email:a.user?.email}),{access_token:a.access_token,refresh_token:a.refresh_token,user_id:a.user?.id??"",email:a.user?.email??""}}function fe(e){const t=e.split(".");if(3!==t.length)throw new Error("Invalid JWT format");const r=t[1].replace(/-/g,"+").replace(/_/g,"/").padEnd(t[1].length+(4-t[1].length%4)%4,"="),i=JSON.parse(Buffer.from(r,"base64").toString("utf-8"));if(!i.sub)throw new Error("JWT missing 'sub' claim");return i}D(ue,{getValidToken:()=>_e});var he,ge,ye,be,we,ve=U({"src/auth/refresh.ts"(){ce(),pe()}}),je={};async function xe(){if(he)try{const e=await ee();e&&e.expires_at>we&&(process.stderr.write("[atoms-mcp] Credentials file refreshed elsewhere — rebuilding client.\n"),Se())}catch{}if(he&&be>Date.now()+6e4)return he;he&&process.stderr.write("[atoms-mcp] Token expiring, refreshing client...\n");const{access_token:e,user_id:t,email:r}=await _e(),i=e.split(".");if(3===i.length)try{const e=JSON.parse(Buffer.from(i[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString());be=1e3*(e.exp??0)}catch{be=Date.now()+36e5}try{const e=await ee();we=e?.expires_at??0}catch{we=0}const n=d(se,oe,{global:{headers:{Authorization:`Bearer ${e}`}}});return ge=t,ye=r,he=n}function Se(){he=null,ge=null,ye=null,be=0,we=0}function ke(e){let t="";if(e instanceof Error)t=e.message;else if(e&&"object"==typeof e){const r=e;t="string"==typeof r.message?r.message:"string"==typeof r.code?r.code:String(e)}else null!=e&&(t=String(e));return/jwt\s*expired|invalid.?jwt|token.*(?:has\s+)?expired|jwt.*invalid|pgrst301/i.test(t)}async function Ie(e){try{return await e()}catch(t){if(!ke(t))throw t;return process.stderr.write("[atoms-mcp] Data tier rejected JWT — invalidating client and retrying once.\n"),Se(),await e()}}function Ee(){if(!ge)throw new Error("Not authenticated. Call getClient() first.");return ge}function Oe(){return ye??""}async function Ae(e,t){const r=Ee(),{data:i,error:n}=await e.from("projects").select("org_id").eq("id",t).maybeSingle();if(n||!i)throw new Error(`Project '${t}' not found`);const{data:a,error:s}=await e.from("org_members").select("role").eq("user_id",r).eq("org_id",i.org_id).maybeSingle();if(s||!a){const{data:t}=await e.rpc("is_platform_admin");if(!0===t)return"admin";throw new Error("Not a member of this project's organization")}if("viewer"===a.role)throw new Error("VIEWER_ROLE");return a.role}D(je,{getClient:()=>xe,getUserEmail:()=>Oe,getUserId:()=>Ee,invalidateClient:()=>Se,isJwtExpiredError:()=>ke,requireWriteAccess:()=>Ae,withJwtRetry:()=>Ie});var Te,He,Ue=U({"src/db/client.ts"(){ve(),ce(),pe(),he=null,ge=null,ye=null,be=0,we=0}}),De={};async function Re(){const e=p(32).toString("base64url"),t=u("sha256").update(e).digest("base64url"),r=`http://localhost:${Te}${He}`,i=new f(`${de}/auth/mcp-consent`);return i.searchParams.set("redirect_uri",r),i.searchParams.set("code_challenge",t),i.searchParams.set("code_challenge_method","S256"),new Promise((t,r)=>{let n;function a(){clearTimeout(n)}const s=c(async(i,n)=>{const o=new f(i.url??"/",`http://localhost:${Te}`);if(o.pathname!==He)return n.writeHead(404),void n.end("Not found");try{if("access_denied"===o.searchParams.get("error"))return n.writeHead(200,{"Content-Type":"text/html"}),n.end(Pe("Authorization was cancelled.")),a(),s.close(),void r(new Error("Authorization cancelled by user."));const i=o.searchParams.get("code");if(i){const o=await fetch(`${se}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{"Content-Type":"application/json",apikey:oe},body:JSON.stringify({auth_code:i,code_verifier:e})}),d=await o.json();if(!o.ok||!d.access_token){const e=d.error_description??d.msg??"Unknown error";return n.writeHead(200,{"Content-Type":"text/html"}),n.end(Pe(`Code exchange failed: ${e}`)),a(),s.close(),void r(new Error(`Code exchange failed: ${e}`))}let c="authenticated";try{c=JSON.parse(Buffer.from(d.access_token.split(".")[1],"base64").toString()).email??c}catch{}const{createClient:l}=await import("@supabase/supabase-js"),m=l(se,oe,{global:{headers:{Authorization:`Bearer ${d.access_token}`}}}),{data:p,error:u}=await m.from("org_members").select("org_id").limit(1);return u||!p||0===p.length?(n.writeHead(200,{"Content-Type":"text/html"}),n.end(Pe('No ATOMS account found for this Google account.<br><br>Please sign up at <a href="https://atoms.tech">atoms.tech</a> first, then run this login command again.')),a(),s.close(),void r(new Error("No ATOMS account found. Sign up at atoms.tech first."))):(await te({access_token:d.access_token,refresh_token:d.refresh_token,expires_at:Date.now()+1e3*(d.expires_in??3600),user_email:c}),n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ce(c)),a(),s.close(),void t({email:c}))}const d=o.searchParams.get("access_token"),c=o.searchParams.get("refresh_token"),l=parseInt(o.searchParams.get("expires_in")??"3600",10);d&&c?(await te({access_token:d,refresh_token:c,expires_at:Date.now()+1e3*l}),n.writeHead(200,{"Content-Type":"text/html"}),n.end(Ce("authenticated")),a(),s.close(),t({email:"authenticated"})):(n.writeHead(200,{"Content-Type":"text/html"}),n.end("<!DOCTYPE html>\n<html>\n<head><title>ATOMS MCP Login</title></head>\n<body>\n <h1>Completing login...</h1>\n <script>\n const hash = window.location.hash.substring(1);\n const params = new URLSearchParams(hash);\n const data = {\n access_token: params.get('access_token'),\n refresh_token: params.get('refresh_token'),\n expires_in: params.get('expires_in'),\n user_email: ''\n };\n if (data.access_token) {\n // Decode JWT to get email\n try {\n const payload = JSON.parse(atob(data.access_token.split('.')[1]));\n data.user_email = payload.email || '';\n } catch(e) {}\n fetch('/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then(() => {\n document.body.innerHTML = '<h1>Login successful! You can close this tab.</h1>';\n }).catch(() => {\n document.body.innerHTML = '<h1>Login failed. Please try again.</h1>';\n });\n } else {\n document.body.innerHTML = '<h1>Login failed — no tokens received.</h1>';\n }\n <\/script>\n</body>\n</html>"))}catch(e){n.writeHead(500),n.end("Authentication failed"),a(),s.close(),r(e)}});s.on("request",async(e,i)=>{if("POST"===e.method&&"/token"===e.url){let n="";e.on("data",e=>{n+=e.toString()}),e.on("end",async()=>{try{const e=JSON.parse(n);await te({access_token:e.access_token,refresh_token:e.refresh_token,expires_at:Date.now()+1e3*(e.expires_in??3600),user_email:e.user_email}),i.writeHead(200,{"Content-Type":"application/json"}),i.end(JSON.stringify({ok:!0})),a(),s.close(),t({email:e.user_email??"authenticated"})}catch(e){i.writeHead(500),i.end("Failed to store credentials"),a(),s.close(),r(e)}})}}),s.listen(Te,"127.0.0.1",async()=>{process.stderr.write("\nOpening browser for ATOMS login...\n"),process.stderr.write(`If the browser doesn't open, visit:\n${i.toString()}\n\n`);try{const{default:e}=await import("open");await e(i.toString())}catch{process.stderr.write("Could not open browser automatically. Please open the URL above.\n")}}),n=setTimeout(()=>{s.close(),r(new Error("Login timed out after 60 seconds. Run 'npx @atoms-tech/atoms-mcp login' to try again."))},6e4)})}function Ce(e){return`<!DOCTYPE html>\n<html>\n<head>\n <title>ATOMS MCP — Authorized</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n background: #0a0a0a;\n color: #fafafa;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n background: #171717;\n border: 1px solid #262626;\n border-radius: 16px;\n padding: 48px;\n max-width: 480px;\n width: 100%;\n text-align: center;\n }\n .icon {\n width: 56px;\n height: 56px;\n background: #052e16;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 24px;\n }\n .icon svg { width: 28px; height: 28px; }\n h1 {\n font-size: 22px;\n font-weight: 600;\n margin-bottom: 8px;\n color: #22c55e;\n }\n .email {\n font-size: 14px;\n color: #a3a3a3;\n margin-bottom: 32px;\n }\n .permissions {\n text-align: left;\n background: #0a0a0a;\n border: 1px solid #262626;\n border-radius: 12px;\n padding: 20px;\n margin-bottom: 24px;\n }\n .permissions h3 {\n font-size: 12px;\n font-weight: 600;\n color: #737373;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin-bottom: 12px;\n }\n .perm-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 6px 0;\n font-size: 14px;\n color: #d4d4d4;\n }\n .perm-item .dot {\n width: 6px;\n height: 6px;\n background: #22c55e;\n border-radius: 50%;\n flex-shrink: 0;\n }\n .closing {\n font-size: 13px;\n color: #525252;\n }\n .closing span { color: #a3a3a3; font-variant-numeric: tabular-nums; }\n </style>\n</head>\n<body>\n <div class="card">\n <div class="icon">\n <svg fill="none" viewBox="0 0 24 24" stroke="#22c55e" stroke-width="2.5">\n <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>\n </svg>\n </div>\n <h1>Authorization Granted</h1>\n <p class="email">${e}</p>\n\n <div class="permissions">\n <h3>ATOMS MCP can now</h3>\n <div class="perm-item"><span class="dot"></span> Read your projects and requirements</div>\n <div class="perm-item"><span class="dot"></span> Create and edit requirements and test cases</div>\n <div class="perm-item"><span class="dot"></span> Record test results (pass/fail)</div>\n <div class="perm-item"><span class="dot"></span> Manage traceability relationships</div>\n <div class="perm-item"><span class="dot"></span> Export coverage reports and diagrams</div>\n </div>\n\n <p class="closing">This tab will close in <span id="countdown">5</span>s</p>\n </div>\n\n <script>\n let seconds = 5;\n const el = document.getElementById('countdown');\n const timer = setInterval(() => {\n seconds--;\n el.textContent = seconds;\n if (seconds <= 0) {\n clearInterval(timer);\n window.close();\n // If window.close() is blocked (not opened by script), update message\n setTimeout(() => {\n document.querySelector('.closing').textContent = 'You can close this tab now.';\n }, 500);\n }\n }, 1000);\n <\/script>\n</body>\n</html>`}function Pe(e){return`<!DOCTYPE html>\n<html>\n<head>\n <title>ATOMS MCP — Failed</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n background: #0a0a0a;\n color: #fafafa;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n background: #171717;\n border: 1px solid #262626;\n border-radius: 16px;\n padding: 48px;\n max-width: 480px;\n width: 100%;\n text-align: center;\n }\n .icon {\n width: 56px;\n height: 56px;\n background: #450a0a;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 24px;\n }\n .icon svg { width: 28px; height: 28px; }\n h1 { font-size: 22px; font-weight: 600; margin-bottom: 16px; color: #ef4444; }\n .reason { font-size: 14px; color: #a3a3a3; line-height: 1.6; }\n .reason a { color: #7c3aed; text-decoration: underline; }\n </style>\n</head>\n<body>\n <div class="card">\n <div class="icon">\n <svg fill="none" viewBox="0 0 24 24" stroke="#ef4444" stroke-width="2.5">\n <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>\n </svg>\n </div>\n <h1>Authorization Failed</h1>\n <p class="reason">${e}</p>\n </div>\n</body>\n</html>`}D(De,{login:()=>Re});var $e,Me,qe,Ne,Le=U({"src/auth/login.ts"(){ce(),pe(),Te=19275,He="/callback"}});function We(e,t=0){if(t>5)return"[depth-limit]";if("string"==typeof e){for(const t of qe)if(t.test(e.trim()))return"[redacted]";return e.length>Ne?e.slice(0,Ne)+"…":e}if(Array.isArray(e))return e.slice(0,20).map(e=>We(e,t+1));if(null!==e&&"object"==typeof e){const r={};for(const[i,n]of Object.entries(e)){const e=i.toLowerCase();$e.has(e)?r[i]="[redacted]":Me.has(e)?r[i]="string"==typeof n?"[content]":We(n,t+1):r[i]=We(n,t+1)}return r}return e}function Ve(e){return We(e)}var Be,Fe,ze,Je,Qe,Ge=U({"src/middleware/audit.ts"(){$e=new Set(["access_token","refresh_token","password","api_key","secret","token","authorization","credential","credentials","private_key"]),Me=new Set(["body","description","content","notes","summary","title","system_prompt","prompt"]),qe=[/^[^\s@]+@[^\s@]+\.[^\s@]+$/,/^bearer\s+\S+$/i,/^(sk|pk|npm|gh|glpat|xoxb|xoxp|rk|SG\.)[-_][A-Za-z0-9_-]{16,}/,/^eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/],Ne=200}}),Ye=U({"src/middleware/rate-limiter.ts"(){Be=60,Fe=6e4}});function Ke(){const e=process.env.ATOMS_MCP_PROJECT_ID?.trim();return e?ze.test(e)?e:(Je||(process.stderr.write(`[atoms-mcp] WARNING: ATOMS_MCP_PROJECT_ID="${e.slice(0,50)}" is not a valid UUID — scope ignored.\n`),Je=!0),null):null}var Ze,Xe=U({"src/middleware/project-scope.ts"(){ze=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Je=!1,Qe=class extends Error{expectedProjectId;receivedProjectId;constructor(e,t){super("Tool call rejected: project_id is outside the configured ATOMS_MCP_PROJECT_ID scope."),this.name="ProjectScopeError",this.expectedProjectId=e,this.receivedProjectId=t}}}});function et(e){const t=function(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")}(e??"");return t?t.includes("cursor")?"cursor":t.includes("copilot")?"copilot":t.includes("claude")?Ze:t:Ze}var tt,rt,it,nt,at,st,ot=U({"src/core/client-identity.ts"(){Ze="claude_desktop"}}),dt=U({"src/core/timestamps.ts"(){tt=h.string().datetime({offset:!0})}}),ct=U({"src/core/errors.ts"(){}}),lt={};function mt(e,t=it){if("string"!=typeof e)return"";const r=e.replace(at,"").replace(st,"");return r.length<=t?r:r.slice(0,t)+"..."}function pt(e){return"string"!=typeof e?"":e.trim().toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function ut(e,t){return{status:"success",data:e,...t?{meta:t}:{}}}function _t(e,t,r){return{total_count:e,limit:t,offset:r,has_more:r+t<e}}function ft(e,t){return{status:"error",message:e,next_steps:t}}function ht(e,t){return ft(`${e} '${mt(t)}' not found`,[`Verify the ${e.toLowerCase()} ID is correct`,"Use atoms_list_items or atoms_search to find valid IDs"])}function gt(e){return ft(`${e} role cannot modify project data`,["Contact your org admin to upgrade your role to editor","Use read-only tools (atoms_list_items, atoms_get_item, atoms_search) instead"])}function yt(e){return ft(`Validation error: ${mt(e,nt)}`,["Check the parameter types and constraints in the tool description","Ensure all required parameters are provided"])}function bt(){return ft("Not authenticated. Run 'npx @atoms-tech/atoms-mcp login' first.",["Run: npx @atoms-tech/atoms-mcp login","Or set ATOMS_ACCESS_TOKEN environment variable"])}function wt(){return ft("Your ATOMS session token expired and could not be refreshed automatically.",["Call atoms_status() — it will refresh the token if possible","If that still reports 'expired', restart Claude Desktop (quit and reopen)","If the problem persists, run: npx @atoms-tech/atoms-mcp logout && npx @atoms-tech/atoms-mcp login"])}function vt(e){return ft("Tool call rejected: project_id is outside the configured ATOMS_MCP_PROJECT_ID scope.",[`This MCP session is bound to project ${e}`,"Pass that project_id, or restart without ATOMS_MCP_PROJECT_ID to access other projects"])}function jt(e){return ft("Rate limited: too many requests to ATOMS API.",[e?`Wait ${e} seconds before retrying.`:"Wait a few seconds before retrying.","Reduce the frequency of back-to-back tool calls"])}function xt(e){return ft(`Parse error: ${mt(e,nt)}`,["Check that body/title/summary fields contain valid text (no unexpected control characters)","If the content was generated programmatically, validate the string before passing it","Simplify the content and retry — if it succeeds, the original content has a malformed character"])}function St(e){return function(e){return/stale_item_write|sqlstate\s*40001|could not serialize access/i.test(e)}(e)}function kt(){return ft("Stale write rejected: this item changed after it was read.",["Call atoms_get_item again and use the returned metadata.updated_at value","Review the current item before retrying so you do not overwrite newer work","Retry atoms_update_item or atoms_link_items with the fresh expected timestamp"])}function It(e){const t=e instanceof Error?e.message:String(e);return/not authenticated/i.test(t)?bt():function(e){let t="";if(e instanceof Error)t=e.message;else if(e&&"object"==typeof e){const r=e;t="string"==typeof r.message?r.message:"string"==typeof r.code?r.code:String(e)}else null!=e&&(t=String(e));return/jwt\s*expired|invalid.?jwt|token.*(?:has\s+)?expired|jwt.*invalid|pgrst301/i.test(t)}(e)?wt():/rate.?limit|too many requests|429/i.test(t)?jt():St(t)?kt():/invalid input syntax|invalid json|unexpected token|malformed|json parse/i.test(t)?xt(t):Et(t)}function Et(e){return/row.level security|permission denied for/i.test(e)?ft("Permission denied: your account does not have write access to this project.",["Verify you have editor or admin role in this project","Contact your org admin to upgrade your permissions","Use atoms_list_projects to confirm the project_id is one you have access to"]):ft(`Database error: ${mt(e,nt)}`,["This may be a temporary issue — try again","If the error persists, check that the project_id is valid"])}function Ot(e){return null==e?e:`<user_content>${e}</user_content>`}function At(e){const t=JSON.stringify(e,null,2);if(t.length>rt&&"success"===e.status&&Array.isArray(e.data)){const t=Math.max(1,Math.floor(e.data.length/2)),r={...e,data:e.data.slice(0,t),meta:{...e.meta,truncated:!0,truncation_message:`Response truncated from ${e.data.length} to ${t} items. Use 'offset' parameter or add filters to see more results.`}};return{content:[{type:"text",text:JSON.stringify(r,null,2)}],structuredContent:r}}return{content:[{type:"text",text:t}],structuredContent:e}}function Tt(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}],structuredContent:e,isError:!0}}D(lt,{CHARACTER_LIMIT:()=>rt,accessDeniedError:()=>gt,authError:()=>bt,classifyError:()=>It,dbError:()=>Et,errorResponse:()=>ft,formatErrorResult:()=>Tt,formatToolResult:()=>At,isStaleWriteErrorMessage:()=>St,jwtExpiredError:()=>wt,notFoundError:()=>ht,paginationMeta:()=>_t,parseError:()=>xt,rateLimitError:()=>jt,sanitizeForEcho:()=>mt,sanitizeTagOrLevel:()=>pt,scopeError:()=>vt,staleWriteError:()=>kt,success:()=>ut,validationError:()=>yt,wrapUserContent:()=>Ot});var Ht,Ut=U({"src/tools/_base.ts"(){ct(),rt=25e3,it=50,nt=200,at=new RegExp("[\\u0000-\\u001F\\u007F]","g"),st=new RegExp("[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\uFEFF]","g")}});var Dt=U({"src/apps/register.ts"(){Ht={resourceDomains:["fonts.googleapis.com","fonts.gstatic.com"]}}});function Rt(e){return function(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}(`*${t=e,t.replace(/[\\%_*]/g,e=>`\\${e}`)}*`);var t}var Ct=U({"src/core/postgrest.ts"(){}});async function Pt(e,t,r){let i=e.from("items").select("*",{count:"exact"}).eq("project_id",t).is("deleted_at",null).order("created_at",{ascending:!0});r.type&&(i=i.eq("type",r.type)),r.domain&&(i=i.filter("data->tags->domains","cs",JSON.stringify([r.domain]))),r.level&&(i=i.filter("data->tags->>level","eq",r.level)),i=i.range(r.offset,r.offset+r.limit-1);const{data:n,error:a,count:s}=await i;if(a)throw new Error(a.message);return{items:n??[],totalCount:s??0}}async function $t(e,t,r){const{data:i,error:n}=await e.from("items").select("*").eq("id",r).eq("project_id",t).is("deleted_at",null).maybeSingle();if(n)throw new Error(n.message);return i}async function Mt(e,t){if(t.baseline_id&&t.as_of)throw new Error("baseline_id and as_of are mutually exclusive");if(t.as_of)return t.as_of;if(t.baseline_id){const{data:r,error:i}=await e.from("baselines").select("project_id, snapshot_at, deleted_at").eq("id",t.baseline_id).maybeSingle();if(i)throw new Error(`resolveSnapshotAt: ${i.message}`);if(!r||null!==r.deleted_at)throw new Error(`Baseline not found: ${t.baseline_id}`);if(r.project_id!==t.project_id)throw new Error("Baseline does not belong to this project");return r.snapshot_at}return null}async function qt(e,t){const{data:r,error:i}=await e.from("baselines").select("id, project_id, name, description, snapshot_at, created_at, created_by, locked_at, locked_by, deleted_at").eq("id",t).maybeSingle();if(i)throw new Error(`getBaselineById: ${i.message}`);return r&&null===r.deleted_at?r:null}function Nt(e){return{id:e.id,project_id:e.project_id,type:e.type,title:e.title,data:e.data,created_by:e.changed_by,updated_by:e.changed_by,created_at:e.valid_from,updated_at:e.valid_from,deleted_at:null}}async function Lt(e,t,r){const{data:i,error:n}=await e.rpc("project_state_at",{p_project_id:t,p_snapshot_at:r});if(n)throw new Error(`projectStateAt: ${n.message}`);return i??[]}async function Wt(e,t,r,i){const n=r.trim();if(!n)return{items:[],totalCount:0};let a=null;if(i.asset&&i.asset.trim()){const{data:r,error:n}=await e.from("test_results").select("item_id").eq("project_id",t).eq("asset",i.asset.trim());if(n)throw new Error(n.message);const s=new Set;for(const e of r??[])s.add(e.item_id);if(a=Array.from(s),0===a.length)return{items:[],totalCount:0}}let s=e.from("items").select("*",{count:"exact"}).eq("project_id",t).is("deleted_at",null).or(`title.ilike.${Rt(n)},data->>body.ilike.${Rt(n)},data->>summary.ilike.${Rt(n)}`).limit(i.limit);i.type&&(s=s.eq("type",i.type)),a&&(s=s.in("id",a));const{data:o,error:d,count:c}=await s;if(d)throw new Error(d.message);return{items:o??[],totalCount:c??0}}function Vt(e,t){const r=new Set(e.map(e=>e.id)),i=new Set,n=[],a=(e,t,a)=>{if(e===t)return;if(!r.has(e)||!r.has(t))return;const s=`${e}|${t}|${a}`;i.has(s)||(i.add(s),n.push({from_id:e,to_id:t,type:a}))};for(const e of t)a(e.from_id,e.to_id,e.type);for(const t of e){const e=t.data?.relationships??{},r=t=>Array.isArray(e[t])?e[t].filter(e=>"string"==typeof e):[];for(const e of r("parents"))a(t.id,e,"parent");for(const e of r("children"))a(t.id,e,"child");for(const e of r("related"))a(t.id,e,"related");for(const e of r("verifies"))a(t.id,e,"verifies");for(const e of r("verified_by"))a(t.id,e,"verified_by")}return n}async function Bt(e,t,r){const i="requirement"===r?"REQ":"test-case"===r?"TC":"note"===r?"NOTE":"TABLE",{data:n,error:a}=await e.rpc("next_item_id",{p_project_id:t,p_prefix:i});if(a)throw new Error(`ID generation failed: ${a.message}`);return n}var Ft=U({"src/db/queries.ts"(){Ct()}}),zt={};async function Jt(){try{const e=await Ie(async()=>{const e=await xe();return await async function(e){const{data:t,error:r}=await e.from("projects").select("id, name, description, org_id, created_at, organizations(name)").is("deleted_at",null).order("created_at",{ascending:!1});if(r)throw new Error(r.message);return(t??[]).map(e=>({id:e.id,name:e.name,description:e.description,org_id:e.org_id,org_name:e.organizations?.name??"Unknown",created_at:e.created_at}))}(e)}),t=Ke(),r=W,i=F().project_scope,n=i?new Set(i):null;return At(ut(e.filter(e=>!r||e.org_id===r).filter(e=>!t||e.id===t).filter(e=>!n||n.has(e.id)).map(e=>({id:e.id,name:e.name,description:e.description,org_id:e.org_id,org_name:e.org_name,created_at:e.created_at}))))}catch(e){return Tt(It(e))}}D(zt,{listProjectsHandler:()=>Jt});var Qt=U({"src/tools/list-projects.ts"(){Ue(),Ft(),Xe(),z(),Ut()}}),Gt={};async function Yt(e){try{const t=await xe();let r,i,n=null;try{n=await Mt(t,{baseline_id:e.baseline_id,as_of:e.as_of,project_id:e.project_id})}catch(e){return Tt(Et(e.message))}if(n){const a=(await Lt(t,e.project_id,n)).filter(t=>{if(e.type&&t.type!==e.type)return!1;const r=t.data??{};if(e.domain){const t=r?.tags?.domains??[];if(!Array.isArray(t)||!t.includes(e.domain))return!1}return!e.level||r?.tags?.level===e.level});i=a.length,r=a.slice(e.offset,e.offset+e.limit).map(e=>({id:e.id,title:e.title,type:e.type,data:e.data}))}else{const n=await Pt(t,e.project_id,{type:e.type,domain:e.domain,level:e.level,limit:e.limit,offset:e.offset});r=n.items,i=n.totalCount}return At(ut(r.map(e=>({id:e.id,title:Ot(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),_t(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(Gt,{listItemsHandler:()=>Yt});var Kt=U({"src/tools/list-items.ts"(){Ue(),Ft(),Ut()}}),Zt={};async function Xt(e){try{const t=await xe();let r=null;try{r=await Mt(t,{baseline_id:e.baseline_id,as_of:e.as_of,project_id:e.project_id})}catch(e){return Tt(ft(e.message,["Pass baseline_id OR as_of, not both"]))}const i=r?await async function(e,t,r,i){const{data:n,error:a}=await e.rpc("item_state_at",{p_item_id:r,p_project_id:t,p_snapshot_at:i});if(a)throw new Error(`itemStateAt: ${a.message}`);return(n??[])[0]??null}(t,e.project_id,e.item_id,r).then(e=>e?Nt(e):null):await $t(t,e.project_id,e.item_id);if(!i)return Tt(ht("Item",e.item_id));const n={parents:[],children:[],related:[],verified_by:[],verifies:[]};if(r){const e=i.data?.relationships??{};for(const t of Object.keys(n)){const r=e[t];Array.isArray(r)&&(n[t]=r.slice())}}else{const r=await async function(e,t,r){const{data:i,error:n}=await e.from("item_relationships").select("from_id, to_id, type").eq("project_id",t).or(`from_id.eq.${r},to_id.eq.${r}`);if(n)throw new Error(n.message);return i??[]}(t,e.project_id,e.item_id),a={parent:"parents",child:"children",related:"related",verifies:"verifies",verified_by:"verified_by"},s={parent:"children",child:"parents",verified_by:"verifies",verifies:"verified_by",related:"related"},o=(e,t)=>{if(!(e in n))return;const r=n[e];r.includes(t)||r.push(t)},d=new Set([e.item_id]);for(const e of r)d.add(e.from_id),d.add(e.to_id);const c=i.data?.relationships??{};for(const e of Object.keys(n)){const t=c[e];if(Array.isArray(t))for(const e of t)"string"==typeof e&&d.add(e)}let l=new Set([e.item_id]);const m=Array.from(d);if(m.length>0){const{data:r,error:i}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).in("id",m);if(i)throw new Error(i.message);l=new Set((r??[]).map(e=>e.id)),l.add(e.item_id)}const p=e=>l.has(e);for(const t of r)if(t.from_id===e.item_id){if(!p(t.to_id))continue;o(a[t.type]??t.type,t.to_id)}else{if(!p(t.from_id))continue;o(s[t.type]??t.type,t.from_id)}for(const t of Object.keys(n)){const r=c[t];if(Array.isArray(r))for(const i of r)"string"==typeof i&&i!==e.item_id&&p(i)&&o(t,i)}}let a=[];if("test-case"===i.type)if(r)a=(i.data?.runs??[]).map(e=>({result:e.result??"not-run",run_by:e.by??e.run_by??null,run_at:e.date??e.run_at??"",note:e.note??null})).filter(e=>"string"==typeof e.run_at&&e.run_at.length>0).sort((e,t)=>t.run_at.localeCompare(e.run_at)).slice(0,20).map(e=>({result:e.result,run_by:e.run_by,run_at:e.run_at,note:e.note}));else{const{data:r,error:i}=await t.from("test_results").select("result, run_by, run_at, note").eq("item_id",e.item_id).eq("project_id",e.project_id).order("run_at",{ascending:!1}).limit(20);!i&&r&&(a=r)}return At(ut({id:i.id,type:i.type,title:Ot(i.title),summary:Ot(i.data?.summary),body:Ot(i.data?.body),tags:i.data?.tags??{domains:[]},ownership:i.data?.ownership??{primary:null,additional:[]},relationships:n,links:i.data?.links??[],status:i.data?.status,..."test-case"===i.type?{latest_result:a[0]?.result??"not-run",test_results:a}:{},metadata:{created_at:i.created_at,created_by:i.created_by,updated_at:i.updated_at,updated_by:i.updated_by,source_id:i.data?.metadata?.source_id}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(Zt,{getItemHandler:()=>Xt});var er=U({"src/tools/get-item.ts"(){Ue(),Ft(),Ut()}}),tr={};async function rr(e){try{const t=await xe();let r,i,n=null;try{n=await Mt(t,{baseline_id:e.baseline_id,project_id:e.project_id})}catch(e){return Tt(Et(e.message))}if(n){const a=await Lt(t,e.project_id,n),s=e.query.trim().toLowerCase(),o=0===s.length?[]:a.filter(t=>{if(e.type&&t.type!==e.type)return!1;const r=t.data??{};return[t.title,r?.body,r?.summary].filter(e=>"string"==typeof e).map(e=>e.toLowerCase()).some(e=>e.includes(s))});i=o.length,r=o.slice(0,e.limit).map(e=>({id:e.id,title:e.title,type:e.type,data:e.data}))}else{const n=await Wt(t,e.project_id,e.query,{type:e.type,limit:e.limit,asset:e.asset});r=n.items,i=n.totalCount}return At(ut(r.map(e=>({id:e.id,title:Ot(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),_t(i,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(tr,{searchHandler:()=>rr});var ir=U({"src/tools/search.ts"(){Ue(),Ft(),Ut()}}),nr={};async function ar(e){try{const t=await xe(),{access_token:r}=await _e(),{items:i,totalCount:n}=await async function(e,t,r,i){const{ATOMS_SUPABASE_URL:n,ATOMS_SUPABASE_ANON_KEY:a}=await Promise.resolve().then(()=>(pe(),le)),s=await fetch(`${n}/functions/v1/embed`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i.accessToken}`,apikey:a},body:JSON.stringify({input:r})});if(!s.ok)throw new Error(`Embed function failed: ${s.status}`);const{embedding:o}=await s.json(),{data:d,error:c}=await e.rpc("semantic_search",{query_embedding:o,p_project_id:t,match_threshold:.3,match_count:i.limit,p_type:i.type||null});if(c)throw new Error(c.message);return{items:d??[],totalCount:(d??[]).length,method:"semantic"}}(t,e.project_id,e.query,{type:e.type,limit:e.limit,accessToken:r});return At(ut(i.map(e=>({id:e.id,title:Ot(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),_t(n,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(nr,{semanticSearchHandler:()=>ar});var sr=U({"src/tools/semantic-search.ts"(){Ue(),ve(),Ft(),Ut()}}),or={};async function dr(e){try{if(e.baseline_id&&e.as_of)return Tt(yt("baseline_id and as_of are mutually exclusive"));const t=await xe();let r;if(e.baseline_id){const i=await qt(t,e.baseline_id);if(!i)return Tt(yt(`Baseline ${e.baseline_id} not found`));if(i.project_id!==e.project_id)return Tt(yt("Baseline does not belong to this project"));r=i.snapshot_at}else e.as_of&&(r=e.as_of);const{covered:i,uncovered:n,total:a}=await async function(e,t,r){let i,n,a;if(r.snapshotAt){const s=(await Lt(e,t,r.snapshotAt)).map(Nt);i=s.filter(e=>"requirement"===e.type),n=s.map(e=>({id:e.id,data:e.data})),a=[]}else{const{data:r,error:s}=await e.from("items").select("*").eq("project_id",t).eq("type","requirement").is("deleted_at",null);if(s)throw new Error(s.message);i=r??[];const{data:o,error:d}=await e.from("items").select("id, data").eq("project_id",t).is("deleted_at",null);if(d)throw new Error(d.message);n=o??[];const{data:c,error:l}=await e.from("item_relationships").select("from_id, to_id, type").eq("project_id",t);if(l)throw new Error(l.message);a=c??[]}const s=i.filter(e=>!(r.domain&&!(e.data?.tags?.domains??[]).includes(r.domain)||r.level&&e.data?.tags?.level!==r.level)),o=Vt(n,a),d=new Set;for(const e of o)"verifies"===e.type?d.add(e.to_id):"verified_by"===e.type&&d.add(e.from_id);return{covered:s.filter(e=>d.has(e.id)),uncovered:s.filter(e=>!d.has(e.id)),total:s.length}}(t,e.project_id,{domain:e.domain,level:e.level,snapshotAt:r}),s=n.map(e=>({id:e.id,title:e.title,type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level}));return At(ut({covered:i.length,uncovered:n.length,total:a,coverage_percent:a>0?Math.round(i.length/a*1e3)/10:100,uncovered_items:s}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(or,{getCoverageHandler:()=>dr});var cr=U({"src/tools/get-coverage.ts"(){Ue(),Ft(),Ut()}}),lr={};async function mr(e){try{const t=await xe(),r=await async function(e,t,r,i){const{data:n,error:a}=await e.from("change_history").select("*").eq("item_id",r).eq("project_id",t).order("changed_at",{ascending:!1}).limit(i);if(a)throw new Error(a.message);return n??[]}(t,e.project_id,e.item_id,e.limit),i=r.map(e=>{const t=[],r=e.old_data,i=e.new_data;if(r&&i){const e=new Set([...Object.keys(r),...Object.keys(i)]);for(const n of e)JSON.stringify(r[n])!==JSON.stringify(i[n])&&t.push(n)}else i?t.push("(created)"):r&&t.push("(deleted)");return{id:e.id,event_type:e.event_type,changed_by:e.changed_by??null,changed_at:e.changed_at,actor:e.actor??"user",session_id:e.session_id??null,fields_changed:t}});return At(ut(i,_t(i.length,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(lr,{getHistoryHandler:()=>mr});var pr=U({"src/tools/get-history.ts"(){Ue(),Ft(),Ut()}}),ur={};async function _r(e){try{if(e.baseline_id&&e.as_of)return Tt(yt("baseline_id and as_of are mutually exclusive"));const t=await xe();let r,i,n;if(e.baseline_id){const i=await qt(t,e.baseline_id);if(!i)return Tt(yt(`Baseline ${e.baseline_id} not found`));if(i.project_id!==e.project_id)return Tt(yt("Baseline does not belong to this project"));r=i.snapshot_at}else e.as_of&&(r=e.as_of);if(r){i=(await Lt(t,e.project_id,r)).map(Nt).map(e=>({id:e.id,title:e.title,type:e.type,data:e.data})),n=Vt(i,[])}else{const{data:r,error:a}=await t.from("items").select("id, title, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(a)throw new Error(a.message);i=r??[];const{data:s,error:o}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(o)throw new Error(o.message);n=Vt(i,s??[])}if(0===i.length)return At(ut({mermaid:"graph TD\n empty[No items in project]"}));const a=new Map;for(const e of i)a.set(e.id,e);const s=["graph TD"],o=new Set,d=new Set,c=(new Set((n??[]).filter(e=>"child"===e.type).map(e=>e.to_id)),new Set((n??[]).filter(e=>"parent"===e.type).map(e=>e.from_id)));let l;l=e.root_item_id?[e.root_item_id]:i.filter(e=>!c.has(e.id)).filter(t=>!(!e.include_tests&&"test-case"===t.type)).map(e=>e.id);const m=l.map(e=>({id:e,depth:0}));for(;m.length>0;){const{id:t,depth:r}=m.shift();if(o.has(t)||r>e.depth)continue;o.add(t);const i=a.get(t);if(!i)continue;if(!e.include_tests&&"test-case"===i.type)continue;const c=fr(i.title),l="requirement"===i.type?`["${c}"]`:"test-case"===i.type?`(["${c}"])`:`("${c}")`;s.push(` ${t}${l}`);for(const i of n??[]){if(i.from_id!==t)continue;const n=a.get(i.to_id);if(!n)continue;if(!e.include_tests&&"test-case"===n.type)continue;const c=`${i.from_id}-${i.type}-${i.to_id}`;d.has(c)||(d.add(c),"child"===i.type?s.push(` ${t} --\x3e ${i.to_id}`):"verified_by"===i.type?s.push(` ${t} -.->|verified_by| ${i.to_id}`):"related"===i.type&&s.push(` ${t} -.- ${i.to_id}`),!o.has(i.to_id)&&r+1<=e.depth&&m.push({id:i.to_id,depth:r+1}))}}return At(ut({mermaid:s.join("\n"),node_count:o.size,edge_count:d.size}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}function fr(e){return e.replace(/"/g,"'").replace(/[[\]{}()]/g,"").substring(0,50)}D(ur,{exportMermaidHandler:()=>_r});var hr=U({"src/tools/export-mermaid.ts"(){Ue(),Ft(),Ut()}}),gr={};function yr(e){const t=pt(e??"");return"sub-system"===t?"subsystem":t}async function br(e,t){const{data:r}=await e.from("metadata").select("domains, levels").eq("project_id",t).maybeSingle();return{domains:r?.domains??[],levels:r?.levels??[]}}async function wr(e,t,r,i){const{error:n}=await e.from("metadata").upsert({project_id:t,domains:r,levels:i},{onConflict:"project_id"});if(n)throw new Error(n.message)}async function vr(e,t,r){const i=pt(r??""),n=yr(i);if(!i)return!1;try{const{domains:r,levels:a}=await br(e,t);return!a.some(e=>yr(e)===n)&&(await wr(e,t,r,[...a,i]),!0)}catch{return!1}}async function jr(e){try{const t=await xe(),{levels:r}=await br(t,e.project_id),{data:i}=await t.from("items").select("data").eq("project_id",e.project_id).is("deleted_at",null),n={};for(const e of i??[]){const t=e.data?.tags,r=yr(t?.level);r&&(n[r]=(n[r]??0)+1)}return At(ut({levels:r.map((e,t)=>({name:e,prefix:`L${t}`,item_count:n[yr(e)]??0})),total:r.length,note:"L# is the level's position in this list (0-based). Names are user-defined."}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function xr(e){try{const t=await xe();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const{domains:r,levels:i}=await br(t,e.project_id),n=pt(e.name),a=yr(n);if(!n)return Tt(yt("Level name cannot be empty."));if(i.some(e=>yr(e)===a))return Tt(yt(`Level "${n}" already exists in this project.`));const s=i.length;return await wr(t,e.project_id,r,[...i,n]),At(ut({level:{name:n,prefix:`L${s}`},message:`Level "${n}" registered as L${s}. Tag items with level="${n}".`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(gr,{createLevelHandler:()=>xr,ensureLevelRegistered:()=>vr,listLevelsHandler:()=>jr});var Sr=U({"src/tools/levels.ts"(){Ue(),Ut()}}),kr={};async function Ir(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=(e.domains??[]).map(pt).filter(e=>e.length>0),n=void 0!==e.level?pt(e.level):void 0;if(i.length>0){const{data:r}=await t.from("metadata").select("domains").eq("project_id",e.project_id).maybeSingle(),n=r?.domains??[];if(n.length>0){const e=new Set(n.map(pt).filter(e=>e.length>0)),t=i.filter(t=>!e.has(t));if(t.length>0)return Tt(yt(`Unknown domain${t.length>1?"s":""}: ${t.join(", ")}. Registered: ${n.join(", ")}. Add new domains via Taxonomy Management in the ATOMS web app first.`))}}const a=await Bt(t,e.project_id,e.type),s=(new Date).toISOString(),o={id:a,type:e.type,title:e.title,summary:e.summary,body:e.body,tags:{domains:i,level:n??""},ownership:{primary:null,additional:[]},relationships:{parents:e.parent_ids??[],children:[],related:[],verifies:[],verified_by:[]},links:[],metadata:{created_at:s,created_by:r,updated_at:s,updated_by:r}},{error:d}=await t.rpc("save_item_with_revision",{p_item_id:a,p_project_id:e.project_id,p_type:e.type,p_title:e.title,p_data:o,p_user_id:r,p_event_source:"mcp",p_event_session_id:wn()??null,p_diff_summary:null});if(d)throw new Error(d.message);if(n&&await vr(t,e.project_id,n),e.parent_ids&&e.parent_ids.length>0)for(const i of e.parent_ids){const{error:n}=await t.rpc("apply_link_change",{p_project_id:e.project_id,p_from_id:a,p_to_id:i,p_type:"parent",p_action:"add",p_user_id:r,p_event_source:"mcp"});n&&process.stderr.write(`[atoms-mcp] Warning: parent link sync failed for ${i}: ${n.message}\n`)}return await t.from("change_history").insert({item_id:a,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:o,actor:vn(),session_id:wn()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),!1===e.echo?At(ut({id:a,type:e.type})):At(ut({id:a,type:e.type,title:e.title,summary:e.summary??"",body:e.body??"",domains:i,level:n??"",project_id:e.project_id}))}catch(e){return Tt(It(e))}}D(kr,{createItemHandler:()=>Ir});var Er=U({"src/tools/create-item.ts"(){Ue(),Ft(),xn(),Ut(),Sr()}}),Or={};async function Ar(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=async(i,n,a,s)=>{const o=await $t(t,e.project_id,i);if(!o)return{updated_fields:[],error:`Item "${i}" not found`};const d=void 0!==n.domains?n.domains.map(pt).filter(e=>e.length>0):void 0,c=void 0!==n.level?pt(n.level):void 0;if(void 0!==d&&d.length>0){const{data:r}=await t.from("metadata").select("domains").eq("project_id",e.project_id).maybeSingle(),i=r?.domains??[];if(i.length>0){const e=new Set(i.map(pt).filter(e=>e.length>0)),t=d.filter(t=>!e.has(t));if(t.length>0)return{updated_fields:[],error:`Unknown domains: ${t.join(", ")}`}}}const l={...o.data},m={...o.data},p=[],u=(e,t)=>JSON.stringify(e)!==JSON.stringify(t);if(void 0!==n.title&&u(o.data.title,n.title)&&(m.title=n.title,p.push("title")),void 0!==n.body&&u(o.data.body,n.body)&&(m.body=n.body,p.push("body")),void 0!==n.summary&&u(o.data.summary,n.summary)&&(m.summary=n.summary,p.push("summary")),void 0!==d&&u(o.data.tags?.domains??[],d)&&(m.tags={...m.tags,domains:d},p.push("domains")),void 0!==c&&u(o.data.tags?.level,c)&&(m.tags={...m.tags,level:c},p.push("level")),void 0!==n.status&&u(o.data.status,n.status)&&(m.status=n.status,p.push("status")),0===p.length)return{updated_fields:[],updatedData:o.data,itemType:o.type};m.metadata={...m.metadata,updated_at:(new Date).toISOString(),updated_by:r};const _=n.title??o.title,{data:f,error:h}=await t.rpc("save_item_with_revision",{p_item_id:i,p_project_id:e.project_id,p_type:o.type,p_title:_,p_data:m,p_user_id:r,p_event_source:"mcp",p_event_session_id:wn()??null,p_diff_summary:null,p_expected_updated_at:n.expected_updated_at??o.updated_at});if(h)return{updated_fields:[],error:h.message};if(!f)return{updated_fields:[],error:`Update silently failed for "${i}" — RPC returned no row. Likely RLS policy denied the write; verify your role and project access.`};p.includes("level")&&c&&await vr(t,e.project_id,c);const g={item_id:i,project_id:e.project_id,changed_by:r,event_type:"updated",old_data:l,new_data:m,actor:vn(),session_id:wn()};return a&&(g.batch_id=a),s&&(g.reason=s),await t.from("change_history").insert(g).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),{updated_fields:p,updatedData:m,itemType:o.type}};if(e.updates&&Array.isArray(e.updates)){const t=crypto.randomUUID(),r=!0===e.continue_on_error,n=[],a=[];for(const s of e.updates){const{updated_fields:o,error:d}=await i(s.item_id,s,t,e.reason);if(d){if(!r)return St(d)?Tt(It(new Error(d))):Tt(yt(`Batch stopped at "${s.item_id}": ${d}. ${n.length} item(s) already updated.`));a.push({item_id:s.item_id,error:d})}else n.push({id:s.item_id,updated_fields:o})}return At(ut({batch_id:t,updated:n.length,results:!1===e.echo?void 0:n,errors:a}))}if(!e.item_id)return Tt(yt("Provide either item_id (single) or updates[] (array form)."));const{updated_fields:n,updatedData:a,itemType:s,error:o}=await i(e.item_id,{title:e.title,body:e.body,summary:e.summary,domains:e.domains,level:e.level,status:e.status,expected_updated_at:e.expected_updated_at},void 0,e.reason);if(o)return o.includes("not found")?Tt(ht("Item",e.item_id)):o.includes("Unknown domain")?Tt(yt(o)):St(o)?Tt(It(new Error(o))):Tt(Et(o));if(!1===e.echo)return At(ut({id:e.item_id,updated_fields:n}));const d=a??{};return At(ut({id:e.item_id,type:s,title:d.title??"",summary:d.summary??"",body:d.body??"",domains:d.tags?.domains??[],level:d.tags?.level??"",updated_fields:n}))}catch(e){return Tt(It(e))}}D(Or,{updateItemHandler:()=>Ar});var Tr,Hr,Ur=U({"src/tools/update-item.ts"(){Ue(),Ft(),xn(),Ut(),Sr()}});function Dr(e){const t=process.env.ATOMS_MCP_REQUIRE_CONFIRMATION?.trim();if(t){const r=t.toLowerCase();if("1"===r||"true"===r||"all"===r)return!0;if(t.split(",").map(e=>e.trim()).filter(Boolean).includes(e))return!0}return!!F().require_confirmation_for.includes(e)}function Rr(e,t=Tr){const r=Date.now()+t,i={...e,exp:r},n=Buffer.from(JSON.stringify(i)).toString("base64url");return{token:`${n}.${l.createHmac("sha256",Hr).update(n).digest("base64url")}`,expires_at:new Date(r).toISOString(),expires_in_seconds:Math.round(t/1e3)}}function Cr(e,t){if("string"!=typeof e||!e.includes("."))return{valid:!1,reason:"Token format invalid"};const[r,i]=e.split(".");if(!r||!i)return{valid:!1,reason:"Token format invalid"};const n=l.createHmac("sha256",Hr).update(r).digest("base64url"),a=Buffer.from(i,"utf8"),s=Buffer.from(n,"utf8");if(a.length!==s.length||!l.timingSafeEqual(a,s))return{valid:!1,reason:"Token signature invalid"};let o;try{o=JSON.parse(Buffer.from(r,"base64url").toString("utf8"))}catch{return{valid:!1,reason:"Token payload malformed"}}return"number"!=typeof o.exp||o.exp<Date.now()?{valid:!1,reason:"Token expired"}:o.tool!==t.tool?{valid:!1,reason:"Token issued for a different tool"}:o.project_id!==t.project_id?{valid:!1,reason:"Token issued for a different project"}:o.target!==t.target?{valid:!1,reason:"Token issued for a different target"}:{valid:!0}}function Pr(e){if(Array.isArray(e))return e.map(Pr);if(null!==e&&"object"==typeof e){const t={};for(const r of Object.keys(e).sort())t[r]=Pr(e[r]);return t}return e}function $r(e){const t=JSON.stringify(Pr(e));return l.createHash("sha256").update(t).digest("base64url").slice(0,16)}var Mr,qr=U({"src/middleware/confirmation.ts"(){z(),Tr=6e4,Hr=(()=>{const e=process.env.ATOMS_MCP_CONFIRMATION_SECRET;return e&&e.length>=16?e:l.randomBytes(32).toString("base64url")})()}}),Nr={};async function Lr(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=await async function(e,t,r){const{data:i,error:n}=await e.from("items").select("*").eq("id",r).eq("project_id",t).maybeSingle();if(n)throw new Error(n.message);return i}(t,e.project_id,e.item_id);if(!i)return Tt(ht("Item",e.item_id));if(i.deleted_at)return Tt(yt(`Item ${e.item_id} is already deleted`));if(Dr(Mr)){const t={tool:Mr,project_id:e.project_id,target:e.item_id};if(!e.confirmation_token){const r=Rr(t);return At(ut({status:"confirmation_required",preview:{id:e.item_id,title:i.title,type:i.type,would_soft_delete:!0},confirmation_token:r.token,expires_at:r.expires_at,expires_in_seconds:r.expires_in_seconds,message:`Re-call atoms_delete_item with the same project_id, item_id, and confirmation_token within ${r.expires_in_seconds}s to execute.`}))}const r=Cr(e.confirmation_token,t);if(!r.valid)return Tt(yt(`confirmation_token rejected: ${r.reason}`))}const{error:n}=await t.from("items").update({deleted_at:(new Date).toISOString()}).eq("id",e.item_id).eq("project_id",e.project_id);if(n)throw new Error(n.message);return await t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"deleted",old_data:i.data,new_data:null,actor:vn(),session_id:wn()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),At(ut({id:e.item_id,title:i.title,type:i.type,deleted:!0,message:`Item ${e.item_id} soft-deleted. It can still be found in audit logs.`}))}catch(e){return Tt(It(e))}}D(Nr,{deleteItemHandler:()=>Lr});var Wr=U({"src/tools/delete-item.ts"(){Ue(),Ft(),xn(),qr(),Ut(),Mr="atoms_delete_item"}}),Vr={};async function Br(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const{data:i,error:n}=await t.from("items").select("*").eq("id",e.item_id).eq("project_id",e.project_id).maybeSingle();if(n)throw new Error(n.message);if(!i)return Tt(ht("Item",e.item_id));if(!i.deleted_at)return Tt(ft(`Item ${e.item_id} is not deleted and cannot be restored.`,["Use atoms_get_item to view the current item state","Use atoms_delete_item first if you intended to delete it"]));const{error:a}=await t.from("items").update({deleted_at:null}).eq("id",e.item_id).eq("project_id",e.project_id);if(a)throw new Error(a.message);return await t.rpc("repair_live_item_relationships_from_jsonb",{p_project_id:e.project_id,p_item_id:e.item_id}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: relationship repair failed: ${e.message}\n`)}),await t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"restored",old_data:null,new_data:i.data,actor:vn(),session_id:wn()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),At(ut({id:e.item_id,title:i.title,type:i.type,restored:!0,message:`Item ${e.item_id} restored successfully.`}))}catch(e){return Tt(It(e))}}D(Vr,{restoreItemHandler:()=>Br});var Fr=U({"src/tools/restore-item.ts"(){Ue(),xn(),Ut()}}),zr={};async function Jr(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=async(i,n,a)=>{const s=await $t(t,e.project_id,i.from_id);if(!s)throw new Error(`Item "${i.from_id}" not found`);const o=await $t(t,e.project_id,i.to_id);if(!o)throw new Error(`Item "${i.to_id}" not found`);const{error:d}=await t.rpc("apply_link_change",{p_project_id:e.project_id,p_from_id:i.from_id,p_to_id:i.to_id,p_type:i.type,p_action:i.action,p_user_id:r,p_event_source:"mcp",p_expected_from_updated_at:i.expected_from_updated_at??s.updated_at,p_expected_to_updated_at:i.expected_to_updated_at??o.updated_at});if(d)throw new Error(d.message);const c={item_id:i.from_id,project_id:e.project_id,changed_by:r,event_type:"updated",old_data:null,new_data:{relationship_change:{action:i.action,type:i.type,from:i.from_id,to:i.to_id}},actor:vn(),session_id:wn()};n&&(c.batch_id=n),a&&(c.reason=a),await t.from("change_history").insert(c).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)})};if(e.operations&&Array.isArray(e.operations)){for(const t of e.operations)if(t.from_id===t.to_id)return Tt(yt(`Self-reference in batch: "${t.from_id}"`));const r=[...new Set(e.operations.flatMap(e=>[e.from_id,e.to_id]))],{data:n}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).in("id",r),a=new Set((n??[]).map(e=>e.id)),s=r.filter(e=>!a.has(e));if(s.length>0)return Tt(ht("Items",s.join(", ")));const{data:o}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id),d=new Map;for(const e of o??[])"parent"===e.type&&(d.has(e.from_id)||d.set(e.from_id,new Set),d.get(e.from_id).add(e.to_id));for(const t of e.operations){if("parent"!==t.type&&"child"!==t.type)continue;const[e,r]="parent"===t.type?[t.from_id,t.to_id]:[t.to_id,t.from_id];"remove"===t.action?d.get(e)?.delete(r):(d.has(e)||d.set(e,new Set),d.get(e).add(r))}const c=0,l=1,m=2,p=new Map,u=new Set;for(const[e,t]of d){u.add(e);for(const e of t)u.add(e)}const _=e=>{if((p.get(e)??c)!==c)return null;const t=[],r=[],i=e=>{p.set(e,l),r.push(e),t.push({node:e,parents:[...d.get(e)??[]],idx:0})};for(i(e);t.length>0;){const e=t[t.length-1];if(e.idx>=e.parents.length){p.set(e.node,m),r.pop(),t.pop();continue}const n=e.parents[e.idx];e.idx++;const a=p.get(n)??c;if(a===l){const e=r.indexOf(n);return[...r.slice(e),n]}a!==m&&i(n)}return null};for(const e of u){const t=_(e);if(t)return Tt(yt(`Batch would create a cycle: ${t.join(" -> ")}. No changes applied.`))}const f=crypto.randomUUID(),h=e.operations.filter(e=>"remove"===e.action),g=e.operations.filter(e=>"add"===e.action);for(const t of[...h,...g])await i(t,f,e.reason);return At(ut({batch_id:f,processed:e.operations.length,operations:!1===e.echo?void 0:e.operations}))}return e.from_id&&e.to_id&&e.type&&e.action?e.from_id===e.to_id?Tt(yt("Cannot create a relationship between an item and itself")):(await i({action:e.action,from_id:e.from_id,to_id:e.to_id,type:e.type,expected_from_updated_at:e.expected_from_updated_at,expected_to_updated_at:e.expected_to_updated_at},void 0,e.reason),!1===e.echo?At(ut({action:e.action,from_id:e.from_id,to_id:e.to_id})):At(ut({action:e.action,type:e.type,from_id:e.from_id,to_id:e.to_id,message:`Relationship ${"add"===e.action?"added":"removed"}: ${e.from_id} --[${e.type}]--\x3e ${e.to_id}`}))):Tt(yt("Provide (from_id, to_id, type, action) for single-op or operations[] for batch."))}catch(e){return Tt(It(e))}}D(zr,{linkItemsHandler:()=>Jr});var Qr=U({"src/tools/link-items.ts"(){Ue(),Ft(),xn(),Ut()}}),Gr=U({"src/core/bulk-parent-links.ts"(){}}),Yr={};async function Kr(e){try{if(!e.items||0===e.items.length)return Tt(yt("items array must contain at least 1 item"));if(e.items.length>100)return Tt(yt(`Maximum 100 items per call (received ${e.items.length}). Split into multiple calls.`));const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=await async function(e,t,r){const{data:i}=await e.from("metadata").select("domains").eq("project_id",t).maybeSingle(),n=i?.domains??[],a=n.length>0?new Set(n.map(pt).filter(e=>e.length>0)):null,s=$r(n.map(pt).filter(e=>e.length>0).sort()),o=[],d=new Set,c={};for(let e=0;e<r.length;e++){const t=r[e];if(c[t.type]=(c[t.type]??0)+1,!a)continue;const i=(t.domains??[]).map(pt).filter(e=>e.length>0);if(0===i.length)continue;const s=i.filter(e=>!a.has(e));s.length>0&&(d.add(e),o.push({index:e,title:t.title,error:`Unknown domain${s.length>1?"s":""}: ${s.join(", ")}. Registered: ${n.join(", ")}.`}))}return{registeredDomains:n,registeredDomainHash:s,invalidDomainIndexes:d,errors:o,typeBreakdown:c}}(t,e.project_id,e.items);if(Dr("atoms_bulk_import")){const t={tool:"atoms_bulk_import",project_id:e.project_id,target:$r({items:e.items,registered_domain_hash:i.registeredDomainHash})};if(!e.confirmation_token){const r=Rr(t);return At(ut({status:"confirmation_required",preview:{total_items:e.items.length,by_type:i.typeBreakdown,sample_titles:e.items.slice(0,5).map(e=>e.title),would_fail:i.errors.length>0,errors:i.errors,registered_domain_hash:i.registeredDomainHash},confirmation_token:r.token,expires_at:r.expires_at,expires_in_seconds:r.expires_in_seconds,message:`Re-call atoms_bulk_import with the same project_id, items array, and confirmation_token within ${r.expires_in_seconds}s to execute. Changing items or project domains will invalidate the token.`}))}const r=Cr(e.confirmation_token,t);if(!r.valid)return Tt(yt(`confirmation_token rejected: ${r.reason}`))}const n=(new Date).toISOString(),a=wn(),s=[],o=[...i.errors],d=[];for(let r=0;r<e.items.length;r++){const n=e.items[r];if(i.invalidDomainIndexes.has(r))continue;const a=(n.domains??[]).map(pt).filter(e=>e.length>0),s=void 0!==n.level?pt(n.level):void 0;try{const i=await Bt(t,e.project_id,n.type);d.push({itemId:i,input:n,sanitizedDomains:a,sanitizedLevel:s,index:r})}catch(e){o.push({index:r,title:n.title,error:`ID generation failed: ${e instanceof Error?e.message:String(e)}`})}}if(0===d.length)return At(ut({created:0,items:[],errors:o}));const c=[],l=new Map;for(const{itemId:t,input:i,sanitizedDomains:a,sanitizedLevel:s}of d){const o={id:t,type:i.type,title:i.title,summary:i.summary,body:i.body,tags:{domains:a,level:s??""},ownership:{primary:null,additional:[]},relationships:{parents:i.parent_id?[i.parent_id]:[],children:[],related:[]},links:[],metadata:{created_at:n,created_by:r,updated_at:n,updated_by:r}};l.set(t,o),c.push({id:t,project_id:e.project_id,type:i.type,title:i.title,data:o,created_by:r,updated_by:r})}const m=c.map(e=>({id:e.id,type:e.type,title:e.title,data:e.data})),{error:p}=await t.rpc("bulk_save_items_with_revisions",{p_project_id:e.project_id,p_items:m,p_user_id:r,p_event_source:"mcp"});if(p)for(const{itemId:i,input:n,index:l}of d){const d=c.find(e=>e.id===i);if(!d)continue;const{error:m}=await t.rpc("save_item_with_revision",{p_item_id:d.id,p_project_id:e.project_id,p_type:d.type,p_title:d.title,p_data:d.data,p_user_id:r,p_event_source:"mcp",p_event_session_id:a,p_diff_summary:null});m?o.push({index:l,title:n.title,error:m.message}):s.push({id:i,title:n.title,type:n.type})}else for(const{itemId:e,input:t}of d)s.push({id:e,title:t.title,type:t.type});const u=d.filter(({input:e})=>e.parent_id).filter(({itemId:e})=>s.some(t=>t.id===e)).map(({itemId:t,input:r})=>({from_id:t,to_id:r.parent_id,type:"parent",project_id:e.project_id}));await async function(e){for(const t of e.links){const{error:r}=await e.client.rpc("apply_link_change",{p_project_id:e.projectId,p_from_id:t.from,p_to_id:t.to,p_type:"parent",p_action:"add",p_user_id:e.userId,p_event_source:"mcp"});r&&e.warn(`bulk parent link sync failed for ${t.to}: ${r.message??"unknown error"}`)}}({client:t,projectId:e.project_id,links:u.map(e=>({from:e.from_id,to:e.to_id})),userId:r,warn:e=>process.stderr.write(`[atoms-mcp] Warning: ${e}\n`)});const _=s.map(t=>({item_id:t.id,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:l.get(t.id)??null,actor:vn(),session_id:a}));_.length>0&&await t.from("change_history").insert(_).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: bulk change_history log failed: ${e.message}\n`)});const f=new Set(s.map(e=>e.id)),h=new Set(d.filter(({itemId:e,sanitizedLevel:t})=>t&&f.has(e)).map(({sanitizedLevel:e})=>e));for(const r of h)await vr(t,e.project_id,r);return At(ut({created:s.length,items:s,errors:o.length>0?o:[],project_id:e.project_id}))}catch(e){return Tt(It(e))}}D(Yr,{bulkImportHandler:()=>Kr});var Zr=U({"src/tools/bulk-import.ts"(){Ue(),Ft(),xn(),qr(),Ut(),Gr(),Sr()}}),Xr={};async function ei(e){try{const t=await xe(),r=Ee(),i=Oe();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const n=await $t(t,e.project_id,e.item_id);if(!n)return Tt(ht("Item",e.item_id));if("test-case"!==n.type)return Tt(yt(`Item '${e.item_id}' is a ${n.type}, not a test-case`));const a=(e.asset??"").trim(),s=(new Date).toISOString(),{data:o,error:d}=await t.rpc("record_test_results_bulk",{p_project_id:e.project_id,p_item_id:e.item_id,p_run_by:i||r,p_actor_user_id:r,p_results:[{result:e.result,note:e.note,asset:a||void 0,date:s}]});if(d)throw new Error(d.message);const c=Array.isArray(o)?o[0]:o?.[0];await t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"test_result_recorded",old_data:null,new_data:{result:e.result,run_at:s,note:e.note,asset:a||null},actor:vn(),session_id:wn()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)});const l=a?` (asset: ${a})`:"";return At(ut({item_id:e.item_id,result:e.result,run_at:s,run_by:i||r,note:e.note??null,asset:a||null,asset_id:c?.asset_id??null,asset_created:c?.asset_created??!1,message:`Test result '${e.result}' recorded for ${e.item_id}${l}`}))}catch(e){return Tt(It(e))}}D(Xr,{recordTestResultHandler:()=>ei});var ti=U({"src/tools/record-test-result.ts"(){Ue(),Ft(),xn(),Ut()}}),ri={};async function ii(e){try{const t=await xe(),r=Ee(),i=Oe();if(!Array.isArray(e.results)||0===e.results.length)return Tt(yt("results must be a non-empty array"));if(e.results.length>100)return Tt(yt(`results must have at most 100 entries (got ${e.results.length})`));try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const n=await $t(t,e.project_id,e.item_id);if(!n)return Tt(ht("Item",e.item_id));if("test-case"!==n.type)return Tt(yt(`Item '${e.item_id}' is a ${n.type}, not a test-case`));const{data:a,error:s}=await t.rpc("record_test_results_bulk",{p_project_id:e.project_id,p_item_id:e.item_id,p_run_by:i||r,p_actor_user_id:r,p_results:e.results});if(s)throw new Error(s.message);return t.from("change_history").insert({item_id:e.item_id,project_id:e.project_id,changed_by:r,event_type:"test_result_recorded",old_data:null,new_data:{bulk:!0,count:e.results.length,results:a},actor:vn(),session_id:wn()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),At(ut({item_id:e.item_id,count:e.results.length,results:a,message:`Recorded ${e.results.length} test results for ${e.item_id}`}))}catch(e){return Tt(It(e))}}D(ri,{recordTestResultsBulkHandler:()=>ii});var ni,ai,si,oi,di=U({"src/tools/record-test-results-bulk.ts"(){Ue(),Ft(),xn(),Ut()}});function ci(e){return Math.min(Math.max(e??5,1),10)}var li=U({"src/core/trace-graph.ts"(){ni=200,ai=new Set(["parent","verifies"]),si=new Set(["child","verified_by"]),oi={parent:"child",child:"parent",verifies:"verified_by",verified_by:"verifies",related:"related"}}}),mi={};async function pi(e){try{const t=ci(e.depth),r=await xe(),i=await $t(r,e.project_id,e.item_id);if(!i)return Tt(ht("Item",e.item_id));const{data:n,error:a}=await r.from("items").select("id, title, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(a)throw new Error(a.message);const{data:s,error:o}=await r.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(o)throw new Error(o.message);const d=Vt(n??[],s??[]);return At(ut(function(e){const t=ci(e.depth),r=e.relationshipTypes?.length?new Set(e.relationshipTypes):null,i=new Map(e.items.map(e=>[e.id,e]));i.has(e.rootItemId)||i.set(e.rootItemId,e.rootItem);const n=new Map,a=(e,t,r)=>{n.has(e)||n.set(e,[]),n.get(e).push({neighbor:t,relType:r})};for(const t of e.relationships){const i=t.type;if(("upstream"===e.direction||"both"===e.direction)&&(!ai.has(i)||r&&!r.has(i)||a(t.from_id,t.to_id,i),si.has(i))){const e=oi[i]??i;r&&!r.has(e)||a(t.to_id,t.from_id,e)}if(("downstream"===e.direction||"both"===e.direction)&&(!si.has(i)||r&&!r.has(i)||a(t.from_id,t.to_id,i),ai.has(i))){const e=oi[i]??i;r&&!r.has(e)||a(t.to_id,t.from_id,e)}"related"!==i||r&&!r.has("related")||(a(t.from_id,t.to_id,"related"),a(t.to_id,t.from_id,"related"))}const s=new Set([e.rootItemId]),o=[],d=[],c=[];for(const t of n.get(e.rootItemId)??[])c.push({id:t.neighbor,depth:1,relType:t.relType,from:e.rootItemId});for(;c.length>0&&o.length<ni;){const{id:e,depth:r,relType:a,from:l}=c.shift();if(s.has(e)||r>t)continue;s.add(e);const m=i.get(e);if(m&&(o.push({id:e,title:m.title,type:m.type,relationship:a,depth:r,from:l}),d.push({from:l,to:e,type:a}),r<t))for(const t of n.get(e)??[])s.has(t.neighbor)||c.push({id:t.neighbor,depth:r+1,relType:t.relType,from:e})}const l={id:e.rootItemId,title:e.rootItem.title,type:e.rootItem.type,relationship:"root",depth:0,from:e.rootItemId};return{root:e.rootItemId,project_id:e.projectId,direction:e.direction,depth_limit:t,items:[l,...o],edges:d,total_count:o.length+1,...o.length>=ni?{truncated:!0,truncation_message:`Results capped at ${ni} items. Use a smaller depth or filter by relationship_types.`}:{}}}({projectId:e.project_id,rootItemId:e.item_id,rootItem:{id:e.item_id,title:i.title??e.item_id,type:i.type??"unknown"},items:n??[],relationships:d??[],direction:e.direction,depth:t,relationshipTypes:e.relationship_types})))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(mi,{traceHandler:()=>pi});var ui=U({"src/tools/trace.ts"(){Ue(),Ft(),li(),Ut()}}),_i={};async function fi(e){try{let t=(await xe()).from("assets").select("id, name, archived_at, created_at").eq("project_id",e.project_id).order("name");e.include_archived||(t=t.is("archived_at",null));const{data:r,error:i}=await t;if(i)throw new Error(i.message);return At(ut({assets:r||[],total:(r||[]).length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(_i,{listAssetsHandler:()=>fi});var hi=U({"src/tools/assets.ts"(){Ue(),Ut()}}),gi={};async function yi(e,t,r,i){if(r&&i)throw new Error("baseline_id and as_of are mutually exclusive");if(i)return i;if(!r)return null;const{data:n,error:a}=await e.from("baselines").select("project_id, snapshot_at, deleted_at").eq("id",r).maybeSingle();if(a)throw new Error(a.message);if(!n||n.project_id!==t||n.deleted_at)throw new Error("Baseline not found for this project");return n.snapshot_at}async function bi(e,t,r){if(r){const{data:i,error:n}=await e.rpc("variables_at",{p_project_id:t,p_snapshot_at:r});if(n)throw new Error(n.message);return i??[]}const{data:i,error:n}=await e.from("project_variables").select("*").eq("project_id",t).order("name");if(n)throw new Error(n.message);return i??[]}async function wi(e,t,r,i){const{data:n,error:a}=await e.rpc("save_variable_with_revision",{p_project_id:t.project_id,p_name:t.name,p_value:t.value,p_unit:t.unit??null,p_description:t.description??null,p_is_signal:t.is_signal??!1,p_range_min:t.range_min??null,p_range_max:t.range_max??null,p_rate_ms:t.rate_ms??null,p_user_id:r,p_event_source:i,p_variable_id:t.id??null});if(a)throw new Error(a.message);return n}async function vi(e){try{const t=await xe(),r=await yi(t,e.project_id,e.baseline_id,e.as_of),i=await bi(t,e.project_id,r);return At(ut({variables:i,total:i.length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function ji(e){try{const t=await xe(),r=await yi(t,e.project_id,e.baseline_id,e.as_of),i=(await bi(t,e.project_id,r)).find(t=>t.name===e.variable_name)??null;if(!i)return Tt(ht("Variable",e.variable_name));const{data:n}=await t.from("items").select("id, title, type").eq("project_id",e.project_id).is("deleted_at",null),a=`{${e.variable_name}}`,s=(n||[]).filter(e=>(e.data?.body||"").includes(a));return At(ut({variable:i,referenced_by:s.map(e=>({id:e.id,title:e.title,type:e.type})),reference_count:s.length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function xi(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=await t.from("project_variables").select("*").eq("project_id",e.project_id).eq("name",e.variable_name).maybeSingle();if(!i.data)return Tt(ht("Variable",e.variable_name));const n={updated_by:r,updated_at:(new Date).toISOString()};return void 0!==e.value&&(n.value=e.value),void 0!==e.unit&&(n.unit=e.unit||null),void 0!==e.description&&(n.description=e.description||null),At(ut({variable:await wi(t,{...i.data,...n,project_id:e.project_id,name:e.variable_name,value:n.value??i.data.value,unit:n.unit??i.data.unit,description:n.description??i.data.description},r,"mcp")}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function Si(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const i=/^[a-zA-Z_][a-zA-Z0-9_]*$/;if(e.variables&&Array.isArray(e.variables)){const n=e.variables.filter(e=>!i.test(e.name));if(n.length>0)return Tt(yt(`Invalid variable name(s): ${n.map(e=>e.name).join(", ")}. Use letters, digits, underscores; must start with letter or underscore.`));const a=e.variables.map(e=>e.name),s=a.filter((e,t)=>a.indexOf(e)!==t);if(s.length>0)return Tt(yt(`Duplicate names in batch: ${[...new Set(s)].join(", ")}`));const{data:o}=await t.from("project_variables").select("name").eq("project_id",e.project_id).in("name",a),d=(o??[]).map(e=>e.name);if(d.length>0)return Tt(yt(`Variable(s) already exist: ${d.join(", ")}. Remove them from the batch or use atoms_update_variable to change values.`));const c=e.variables.map(t=>({project_id:e.project_id,name:t.name,value:t.value,unit:t.unit??null,description:t.description??null,created_by:r,updated_by:r})),l=await async function(e,t,r,i){const{data:n,error:a}=await e.rpc("bulk_save_variables_with_revisions",{p_project_id:t,p_variables:r,p_user_id:i,p_event_source:"mcp"});if(a)throw new Error(a.message);return n??[]}(t,e.project_id,c,r);return At(ut({batch_id:crypto.randomUUID(),created:(l??[]).length,variables:!1===e.echo?(l??[]).map(e=>({name:e.name})):l??[],message:`${(l??[]).length} variable(s) created. Reference them as {name} in requirement bodies.`}))}if(!e.name||!e.value)return Tt(yt("Provide either (name, value) for single variable or variables[] for batch."));if(!i.test(e.name))return Tt(yt(`Variable name "${e.name}" is invalid. Use letters, digits, and underscores only; must start with a letter or underscore.`));const{data:n}=await t.from("project_variables").select("name").eq("project_id",e.project_id).eq("name",e.name).maybeSingle();if(n)return Tt(yt(`Variable "${e.name}" already exists in this project.`));const a=await wi(t,{project_id:e.project_id,name:e.name,value:e.value,unit:e.unit??null,description:e.description??null},r,"mcp");return!1===e.echo?At(ut({name:e.name})):At(ut({variable:a,message:`Variable "${e.name}" created. Reference it in requirement bodies as {${e.name}}.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function ki(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const{data:i}=await t.from("project_variables").select("id").eq("project_id",e.project_id).eq("name",e.name).maybeSingle();if(!i)return Tt(ht("Variable",e.name));const{data:n}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).filter("data->>body","ilike",`%{${e.name}}%`),a=(n??[]).length;if(a>0&&!e.force)return Tt(yt(`Variable "${e.name}" is referenced in ${a} item${a>1?"s":""}. Use force=true to delete anyway, or remove all {${e.name}} references first.`));if(Dr("atoms_delete_variable")){const t={tool:"atoms_delete_variable",project_id:e.project_id,target:e.name};if(!e.confirmation_token){const r=Rr(t);return At(ut({status:"confirmation_required",preview:{variable_name:e.name,referenced_by_items:a,would_orphan_references:a>0},confirmation_token:r.token,expires_at:r.expires_at,expires_in_seconds:r.expires_in_seconds,message:`Re-call atoms_delete_variable with the same project_id, name, and confirmation_token within ${r.expires_in_seconds}s to execute.`}))}const r=Cr(e.confirmation_token,t);if(!r.valid)return Tt(yt(`confirmation_token rejected: ${r.reason}`))}const s=await async function(e,t,r,i){const{data:n,error:a}=await e.rpc("delete_variable_with_revision",{p_project_id:t,p_variable_id:r,p_user_id:i,p_event_source:"mcp"});if(a)throw new Error(a.message);return Boolean(n)}(t,e.project_id,i.id,r);return s?At(ut({deleted:!0,name:e.name,referenced_by_items:a,warning:a>0?`${a} item${a>1?"s":""} still contain {${e.name}} — those references are now unresolved.`:void 0})):Tt(ht("Variable",e.name))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(gi,{createVariableHandler:()=>Si,deleteVariableHandler:()=>ki,getVariableHandler:()=>ji,listVariablesHandler:()=>vi,updateVariableHandler:()=>xi});var Ii=U({"src/tools/variables.ts"(){Ue(),qr(),Ut()}}),Ei={};async function Oi(e,t){const{data:r}=await e.from("metadata").select("domains, levels").eq("project_id",t).maybeSingle();return{domains:r?.domains??[],levels:r?.levels??[]}}async function Ai(e,t,r,i){const{error:n}=await e.from("metadata").upsert({project_id:t,domains:r,levels:i},{onConflict:"project_id"});if(n)throw new Error(n.message)}async function Ti(e){try{const t=await xe(),{domains:r}=await Oi(t,e.project_id),{counts:i,orphans:n}=await async function(e,t,r){const{data:i}=await e.from("items").select("data").eq("project_id",t).is("deleted_at",null),n={};for(const e of r)n[e]=0;const a={};for(const e of i??[]){const t=e.data?.tags?.domains??[];for(const e of t)a[e]=(a[e]??0)+1,e in n&&n[e]++}const s=new Set(r),o={};for(const[e,t]of Object.entries(a))s.has(e)||(o[e]=t);return{counts:n,orphans:o}}(t,e.project_id,r),a=r.map(e=>({name:e,registered:!0,item_count:i[e]??0})),s=Object.entries(n).map(([e,t])=>({name:e,registered:!1,item_count:t})).sort((e,t)=>e.name.localeCompare(t.name)),o=[...a,...s];return At(ut({domains:o,total:o.length,registered_count:a.length,unregistered_count:s.length,note:s.length>0?`${s.length} unregistered tag(s) found on items. Use atoms_create_domain to register them, or atoms_update_item to remove them.`:void 0}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function Hi(e){try{const t=await xe();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const{domains:r,levels:i}=await Oi(t,e.project_id),n=e.name.trim().toLowerCase();return n?r.includes(n)?Tt(yt(`Domain "${n}" already exists in this project.`)):(await Ai(t,e.project_id,[...r,n],i),At(ut({domain:{name:n},message:`Domain "${n}" registered. You can now tag items with domains=["${n}"].`}))):Tt(yt("Domain name cannot be empty."))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function Ui(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const{domains:i,levels:n}=await Oi(t,e.project_id),a=e.name.trim().toLowerCase(),s=e.new_name.trim().toLowerCase();if(!s)return Tt(yt("new_name cannot be empty."));const o=i.includes(a);if(!o){const{data:r}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).filter("data->tags->domains","cs",JSON.stringify([a])).limit(1);if(!r||0===r.length)return Tt(ht("Domain",a))}if(s!==a&&i.includes(s))return Tt(yt(`Domain "${s}" already exists. Choose a different name.`));const{data:d,error:c}=await t.from("items").select("id, data").eq("project_id",e.project_id).is("deleted_at",null).filter("data->tags->domains","cs",JSON.stringify([a]));if(c)throw new Error(c.message);const l=(d??[]).length;if(e.dry_run)return At(ut({dry_run:!0,rename:{from:a,to:s},items_affected:l,message:`Dry run: renaming "${a}" → "${s}" would update ${l} item${1!==l?"s":""}. Call again with dry_run=false to apply.`}));let m;m=o?i.map(e=>e===a?s:e):i.includes(s)?i:[...i,s],await Ai(t,e.project_id,m,n);const{error:p}=await t.rpc("apply_domain_change",{p_project_id:e.project_id,p_old_domain:a,p_new_domain:s,p_user_id:r,p_event_source:"mcp"});if(p)throw new Error(p.message);return At(ut({domain:{name:s},renamed_from:a,items_updated:l,message:`Domain renamed "${a}" → "${s}". ${l} item${1!==l?"s":""} updated.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}async function Di(e){try{const t=await xe(),r=Ee();try{await Ae(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Tt(gt("Viewer"));throw e}const{domains:i,levels:n}=await Oi(t,e.project_id),a=e.name.trim().toLowerCase(),s=i.includes(a),{data:o,error:d}=await t.from("items").select("id, data").eq("project_id",e.project_id).is("deleted_at",null).filter("data->tags->domains","cs",JSON.stringify([a]));if(d)throw new Error(d.message);const c=(o??[]).length;if(!s&&0===c)return Tt(ht("Domain",a));if(c>0&&!e.force)return Tt(yt(`Domain "${a}" is used by ${c} item${c>1?"s":""}. Use force=true to delete and strip the domain from all items, or re-tag them first.`));if(Dr("atoms_delete_domain")){const t={tool:"atoms_delete_domain",project_id:e.project_id,target:a};if(!e.confirmation_token){const e=Rr(t);return At(ut({status:"confirmation_required",preview:{domain_name:a,items_using_count:c,would_strip_from_items:c>0},confirmation_token:e.token,expires_at:e.expires_at,expires_in_seconds:e.expires_in_seconds,message:`Re-call atoms_delete_domain with the same project_id, name, and confirmation_token within ${e.expires_in_seconds}s to execute.`}))}const r=Cr(e.confirmation_token,t);if(!r.valid)return Tt(yt(`confirmation_token rejected: ${r.reason}`))}const l=s?i.filter(e=>e!==a):i;if(await Ai(t,e.project_id,l,n),c>0){const{error:i}=await t.rpc("apply_domain_change",{p_project_id:e.project_id,p_old_domain:a,p_new_domain:null,p_user_id:r,p_event_source:"mcp"});if(i)throw new Error(i.message)}return At(ut({deleted:!0,name:a,items_updated:c,message:c>0?`Domain "${a}" deleted and stripped from ${c} item${c>1?"s":""}.`:`Domain "${a}" deleted.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(Ei,{createDomainHandler:()=>Hi,deleteDomainHandler:()=>Di,listDomainsHandler:()=>Ti,updateDomainHandler:()=>Ui});var Ri=U({"src/tools/domains.ts"(){Ue(),qr(),Ut()}}),Ci={};async function Pi(e){try{const t=await xe(),{data:r,error:i}=await t.from("projects").select("id, name").eq("id",e.project_id).is("deleted_at",null).maybeSingle(),n=r?.name??null,a=i?.message??null,{data:s,error:o}=await t.from("items").select("id, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(o)throw new Error(o.message);const d=s??[];if(!n&&0===d.length){if(a)throw new Error(a);return Tt(ht("Project",e.project_id))}const c={requirements:0,test_cases:0,notes:0},l=[],m=[];for(const e of d)if("requirement"===e.type){c.requirements++;const t=e.data?.tags?.domains??[];m.push({id:e.id,domains:t})}else"test-case"===e.type?(c.test_cases++,l.push(e.id)):"note"===e.type&&c.notes++;const p={passed:0,failed:0,blocked:0,not_run:0};if(l.length>0){const{data:r,error:i}=await t.from("test_results").select("item_id, result, run_at").eq("project_id",e.project_id).order("run_at",{ascending:!1});if(i)throw new Error(i.message);const n=new Map;for(const e of r??[])n.has(e.item_id)||n.set(e.item_id,e.result);for(const e of l){const t=n.get(e);t&&"not-run"!==t?"passed"===t?p.passed++:"failed"===t?p.failed++:"blocked"===t?p.blocked++:p.not_run++:p.not_run++}}const{data:u,error:_}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(_)throw new Error(_.message);const f=Vt(s??[],u??[]),h=new Set;for(const e of f)"verifies"===e.type?h.add(e.to_id):"verified_by"===e.type&&h.add(e.from_id);const g=m.filter(e=>h.has(e.id)).length,y=m.length,b={covered:g,uncovered:y-g,total:y,percent:y>0?Math.round(g/y*1e3)/10:100},w=new Map;for(const e of m){const t=e.domains.length>0?e.domains:["(untagged)"],r=h.has(e.id);for(const e of t){w.has(e)||w.set(e,{covered:0,total:0});const t=w.get(e);t.total++,r&&t.covered++}}const v=Array.from(w.entries()).sort((e,t)=>e[0].localeCompare(t[0])).map(([e,t])=>({domain:e,covered:t.covered,total:t.total,percent:t.total>0?Math.round(t.covered/t.total*1e3)/10:100})),j=new Date;j.setDate(j.getDate()-7);const{count:x,error:S}=await t.from("change_history").select("id",{count:"exact",head:!0}).eq("project_id",e.project_id).gte("changed_at",j.toISOString());if(S)throw new Error(S.message);return At(ut({project_id:e.project_id,project_name:n??`Project ${e.project_id.slice(0,8)}`,counts:c,test_status:p,coverage:b,coverage_by_domain:v,recent_changes:x??0,last_updated:(new Date).toISOString()}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(Ci,{projectSummaryHandler:()=>Pi});var $i,Mi=U({"src/tools/project-summary.ts"(){Ue(),Ft(),Ut()}}),qi={};async function Ni(e){try{const t=await xe();let r,i;if(e.query&&e.query.trim().length>0){const n=await Wt(t,e.project_id,e.query,{type:e.type,limit:e.limit});r=n.items.map(e=>({id:e.id,title:e.title,type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),i=n.totalCount}else{const n=await Pt(t,e.project_id,{type:e.type,domain:e.domain,level:e.level,limit:e.limit,offset:e.offset});r=n.items.map(e=>({id:e.id,title:e.title,type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),i=n.totalCount}const n=await async function(e,t){const{data:r,error:i}=await e.from("items").select("data").eq("project_id",t).is("deleted_at",null);if(i)throw new Error(i.message);const n=new Set,a=new Set;for(const e of r??[]){const t=e.data?.tags;if(t){const e=t.domains;if(e)for(const t of e)n.add(t);const r=t.level;r&&a.add(r)}}return{domains:[...n].sort(),levels:[...a].sort()}}(t,e.project_id);return At(ut({items:r,filters:{domains:n.domains,levels:n.levels,types:$i},project_id:e.project_id},_t(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(qi,{browseHandler:()=>Ni});var Li,Wi,Vi,Bi,Fi=U({"src/tools/browse.ts"(){Ue(),Ft(),Ut(),$i=["requirement","test-case","note","table"]}}),zi={};async function Ji(e){try{const t=e.direction??"downstream",r=null!=e.depth?Math.min(Math.max(e.depth,1),20):null,i=await xe(),n=await $t(i,e.project_id,e.item_id);if(!n)return Tt(ht("Item",e.item_id));const{data:a,error:s}=await i.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(s)throw new Error(s.message);const{data:o,error:d}=await i.from("items").select("id, title, type").eq("project_id",e.project_id).is("deleted_at",null);if(d)throw new Error(d.message);const c=new Map;for(const e of o??[])c.set(e.id,{title:e.title,type:e.type});const l=new Map,m=(e,t,r)=>{l.has(e)||l.set(e,[]),l.get(e).push({neighbor:t,relType:r})};for(const e of a??[]){const r=e.type;"upstream"!==t&&"both"!==t||(Wi.includes(r)&&m(e.from_id,e.to_id,r),Vi.includes(r)&&m(e.to_id,e.from_id,Bi[r]??r)),"downstream"!==t&&"both"!==t||(Vi.includes(r)&&m(e.from_id,e.to_id,r),Wi.includes(r)&&m(e.to_id,e.from_id,Bi[r]??r)),"related"===r&&(m(e.from_id,e.to_id,"related"),m(e.to_id,e.from_id,"related"))}const p=new Set;p.add(e.item_id);const u=[];let _=0;const f=[];for(const t of l.get(e.item_id)??[])f.push({id:t.neighbor,depth:1,path:[{from_id:e.item_id,to_id:t.neighbor,relationship_type:t.relType}]});for(;f.length>0&&u.length<Li;){const{id:e,depth:t,path:i}=f.shift();if(p.has(e))continue;if(null!==r&&t>r)continue;p.add(e);const n=c.get(e);if(n&&(u.push({item_id:e,title:Ot(n.title),type:n.type,depth:t,relationship_path:i}),t>_&&(_=t),null===r||t<r))for(const r of l.get(e)??[])p.has(r.neighbor)||f.push({id:r.neighbor,depth:t+1,path:[...i,{from_id:e,to_id:r.neighbor,relationship_type:r.relType}]})}let h=0;if(e.include_variable_refs){const{data:t}=await i.from("variable_references").select("variable_id").eq("item_id",e.item_id).eq("project_id",e.project_id),r=[...new Set((t??[]).map(e=>e.variable_id))];if(r.length>0){const{data:t}=await i.from("project_variables").select("id, name").in("id",r),n=new Map((t??[]).map(e=>[e.id,e.name])),{data:a}=await i.from("variable_references").select("variable_id, item_id").in("variable_id",r).eq("project_id",e.project_id).neq("item_id",e.item_id);for(const t of a??[]){const r=t.item_id;if(p.has(r))continue;if(u.length>=Li)break;p.add(r);const i=c.get(r);if(!i)continue;const a=n.get(t.variable_id)??t.variable_id;u.push({item_id:r,title:Ot(i.title),type:i.type,depth:1,relationship_path:[{from_id:e.item_id,to_id:r,relationship_type:`variable_ref:${a}`}]}),h++}}}const g={};for(const e of u)g[e.type]=(g[e.type]??0)+1;const y=c.get(e.item_id);return At(ut({root:{item_id:e.item_id,title:Ot(y?.title??n.title),type:y?.type??n.type},impacted_items:u,summary:{total_impacted:u.length,by_type:g,max_depth_reached:_,...h>0?{variable_impact_count:h}:{},...u.length>=Li?{truncated:!0}:{}}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):Tt(Et(e instanceof Error?e.message:String(e)))}}D(zi,{impactAnalysisHandler:()=>Ji});var Qi=U({"src/tools/impact-analysis.ts"(){Ue(),Ft(),Ut(),Li=200,Wi=["parent","verifies","traces_to","allocated_from","decomposed_from"],Vi=["child","verified_by","allocated_to","decomposes_to","traced_by"],Bi={parent:"child",child:"parent",verifies:"verified_by",verified_by:"verifies",related:"related",traces_to:"traced_by",traced_by:"traces_to",allocated_to:"allocated_from",allocated_from:"allocated_to",decomposes_to:"decomposed_from",decomposed_from:"decomposes_to"}}}),Gi=U({"src/core/relationship-sync.ts"(){}}),Yi={};function Ki(e){return e instanceof Error&&e.message.includes("Not authenticated")?Tt(bt()):null}async function Zi(e,t,r){const{data:i,error:n}=await e.from("items").select("id, deleted_at").eq("project_id",t).eq("id",r).maybeSingle();if(n)throw new Error(n.message);if(a=i,!Boolean(a?.id&&null===a.deleted_at))return;var a;const{error:s}=await e.rpc("sync_item_relationships_from_jsonb",{p_project_id:t,p_item_id:r});if(s)throw new Error(s.message)}async function Xi(e,t,r,i){if(await Zi(e,t,r.id),"test-case"===r.type){const{error:n}=await e.rpc("sync_test_results_for_item",{p_project_id:t,p_item_id:r.id,p_actor_user_id:i});if(n)throw new Error(n.message)}}async function en(e,t,r,i){await e.from("change_history").insert({item_id:r.id,project_id:t,changed_by:i,event_type:"restored",old_data:r.oldData,new_data:r.data,actor:vn(),session_id:wn()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)})}async function tn(e,t,r){const{data:i,error:n}=await e.from("projects").select("org_id").eq("id",t).maybeSingle();if(n)return Tt(Et(n.message));if(!i)return Tt(ht("Project",t));const{data:a,error:s}=await e.from("organizations").select("feature_flags").eq("id",i.org_id).maybeSingle();return s?Tt(Et(s.message)):!0!==(a?.feature_flags??{})[r]?Tt(ft(`Feature '${r}' is not enabled for this organization`,["Ask an org admin to enable the flag in Org Settings."])):null}async function rn(e){try{const t=await xe(),r=await async function(e,t){const{data:r,error:i}=await e.from("baselines").select("id, project_id, name, description, snapshot_at, created_at, created_by, locked_at, locked_by").eq("project_id",t).is("deleted_at",null).order("snapshot_at",{ascending:!1});if(i)throw new Error(`listBaselines: ${i.message}`);return r??[]}(t,e.project_id),i=Math.max(e.offset??0,0),n=Math.min(Math.max(e.limit??50,1),100);return At(ut({baselines:r.slice(i,i+n).map(e=>({id:e.id,name:e.name,description:e.description,snapshot_at:e.snapshot_at,created_at:e.created_at,created_by:e.created_by,locked:null!==e.locked_at,locked_at:e.locked_at})),total:r.length,offset:i,limit:n}))}catch(e){return Ki(e)??Tt(Et(e instanceof Error?e.message:String(e)))}}async function nn(e){try{const t=await xe(),r=await tn(t,e.project_id,"baselines_enabled");if(r)return r;const{data:i,error:n}=await t.from("baselines").insert({project_id:e.project_id,name:e.name.trim(),description:e.description??null,snapshot_at:(new Date).toISOString()}).select().single();return n?"23505"===n.code?Tt(ft(`Baseline name "${e.name}" already exists in this project`,["Pick a different name"])):Tt(Et(n.message)):At(ut({id:i.id,name:i.name,description:i.description,snapshot_at:i.snapshot_at,created_at:i.created_at,locked:!1}))}catch(e){return Ki(e)??Tt(Et(e instanceof Error?e.message:String(e)))}}async function an(e){try{const t=await xe(),r=await qt(t,e.baseline_id);if(!r)return Tt(ht("Baseline",e.baseline_id));const i=await tn(t,r.project_id,"baselines_enabled");if(i)return i;if(null!==r.locked_at)return Tt(ft("Baseline is already locked",["Locks are one-way; nothing to do"]));const{data:n}=await t.from("projects").select("org_id").eq("id",r.project_id).maybeSingle();if(!n)return Tt(ht("Project",r.project_id));const{data:a}=await t.auth.getUser(),s=a?.user?.id;if(!s)return Tt(bt());const{data:o}=await t.from("org_members").select("role").eq("user_id",s).eq("org_id",n.org_id).maybeSingle();if(!o||"admin"!==o.role)return Tt(ft("Only admins can lock baselines",["Ask an org admin to lock this baseline"]));const{data:d,error:c}=await t.from("baselines").update({locked_at:(new Date).toISOString(),locked_by:s}).eq("id",e.baseline_id).select().single();return c?Tt(Et(c.message)):At(ut({id:d.id,locked_at:d.locked_at,locked_by:d.locked_by}))}catch(e){return Ki(e)??Tt(Et(e instanceof Error?e.message:String(e)))}}async function sn(e){try{const t=await xe(),[r,i]=await Promise.all([qt(t,e.baseline_a_id),qt(t,e.baseline_b_id)]);if(!r||!i)return Tt(ht("Baseline","one of the baseline_ids"));if(r.project_id!==i.project_id)return Tt(ft("Both baselines must belong to the same project",["Verify both baseline_ids are from the same project_id"]));const[n,a]=await Promise.all([Lt(t,r.project_id,r.snapshot_at),Lt(t,i.project_id,i.snapshot_at)]),s=new Map(n.map(e=>[e.id,e])),o=new Map(a.map(e=>[e.id,e])),d=Array.from(new Set([...s.keys(),...o.keys()])).sort(),c=[];let l=0,m=0,p=0;for(const e of d){const t=s.get(e),r=o.get(e);if(!t&&r)l++,c.push({item_id:e,change_type:"added"});else if(t&&!r)m++,c.push({item_id:e,change_type:"removed"});else if(t&&r){const i=[];t.title!==r.title&&i.push("title"),t.type!==r.type&&i.push("type");JSON.stringify(t.data??{})!==JSON.stringify(r.data??{})&&i.push("data"),i.length>0&&(p++,c.push({item_id:e,change_type:"modified",fields_changed:i}))}}const u=Math.max(e.offset??0,0),_=Math.min(Math.max(e.limit??50,1),200),f=e.detail?c.slice(u,u+_):c.slice(u,u+_).map(({item_id:e,change_type:t,fields_changed:r})=>({item_id:e,change_type:t,fields_changed:r}));return At(ut({summary:{added:l,removed:m,modified:p,total_compared:d.length},items:f,page_info:{offset:u,limit:_,total:c.length},baselines:{a:{id:r.id,name:r.name,snapshot_at:r.snapshot_at},b:{id:i.id,name:i.name,snapshot_at:i.snapshot_at}}}))}catch(e){return Ki(e)??Tt(Et(e instanceof Error?e.message:String(e)))}}async function on(e){try{const t=await xe(),r=await qt(t,e.baseline_id);if(!r)return Tt(ht("Baseline",e.baseline_id));const i=await tn(t,r.project_id,"baselines_enabled");if(i)return i;const{data:n}=await t.from("projects").select("org_id").eq("id",r.project_id).maybeSingle();if(!n)return Tt(ht("Project",r.project_id));const{data:a}=await t.auth.getUser(),s=a?.user?.id;if(!s)return Tt(bt());const{data:o}=await t.from("org_members").select("role").eq("user_id",s).eq("org_id",n.org_id).maybeSingle();if(!o||"admin"!==o.role)return Tt(ft("Only admins can restore from a baseline",["Ask an org admin to run the restore"]));const d=await Lt(t,r.project_id,r.snapshot_at),c=d.map(e=>e.id),{data:l,error:m}=c.length>0?await t.from("items").select("*").eq("project_id",r.project_id).in("id",c):{data:[],error:null};if(m)return Tt(Et(m.message));const p=function(e,t){const r=new Map(t.map(e=>[e.id,e]));return e.flatMap(e=>{const t=r.get(e.id),i=[];var n,a;return t?(null!==t.deleted_at&&i.push("deleted_at"),t.type!==e.type&&i.push("type"),t.title!==e.title&&i.push("title"),n=t.data,a=e.data,JSON.stringify(n??{})!==JSON.stringify(a??{})&&i.push("data")):i.push("missing"),0===i.length?[]:[{id:e.id,type:e.type,title:e.title,data:e.data??{},diff_summary:{changed_fields:i,restored:!0},oldData:t?.data??null}]})}(d,l??[]),{data:u,error:_}=await t.rpc("variables_at",{p_project_id:r.project_id,p_snapshot_at:r.snapshot_at});if(_)return Tt(Et(_.message));const f=u??[];if(!0!==e.confirm)return At(ut({preview:!0,items_to_restore:p.length,variables_to_restore:f.length}));let h=0;if(p.length>0){const e=p.map(({oldData:e,...t})=>t),{data:i,error:n}=await t.rpc("bulk_save_items_with_revisions",{p_project_id:r.project_id,p_items:e,p_user_id:s,p_event_source:"restore"});if(n)return Tt(Et(n.message));h="number"==typeof i?i:p.length;for(const e of p)await Xi(t,r.project_id,e,s),await en(t,r.project_id,e,s)}if(f.length>0){const{error:e}=await t.rpc("bulk_save_variables_with_revisions",{p_project_id:r.project_id,p_variables:f,p_user_id:s,p_event_source:"restore"});if(e)return Tt(Et(e.message))}return At(ut({ok:!0,items_restored:h,variables_restored:f.length}))}catch(e){return Ki(e)??Tt(Et(e instanceof Error?e.message:String(e)))}}D(Yi,{createBaselineHandler:()=>nn,diffBaselinesHandler:()=>sn,listBaselinesHandler:()=>rn,lockBaselineHandler:()=>an,restoreBaselineHandler:()=>on,syncRestoredItemRelationships:()=>Zi});var dn,cn,ln,mn,pn,un,_n,fn,hn=U({"src/tools/baselines.ts"(){Ue(),xn(),Ut(),Ft(),Gi()}}),gn={};function yn(t){G(t.toolName)&&function(t,r){const i=`ui://${r.toolName}/${r.htmlFile}`,n=function(e,t){const r=_(import.meta.url),i=s.dirname(r);return s.resolve(i,"apps","src","apps",e,t)}(r.appName,r.htmlFile),a=[...Ht.resourceDomains,...r.extraResourceDomains??[]];g(t,r.toolName,{title:r.title,description:r.description,inputSchema:r.inputSchema,annotations:r.annotations,_meta:{ui:{resourceUri:i}}},r.handler),y(t,`${r.title} UI`,i,{mimeType:b},async()=>{const t=await e.readFile(n,"utf-8");return{contents:[{uri:i,mimeType:b,text:t,_meta:{ui:{csp:{resourceDomains:a}}}}]}})}(mn,t)}function bn(e,t){return async r=>{const i=P(F(),e);if(!i.allowed)return Tt(ft(`Tool ${e} is blocked by org policy.`,[M(i.reason),"Contact your org admin to adjust the MCP access policy."]));try{!function(e){const t=Ke();if(!t)return;if("object"!=typeof e||null===e)return;const r=e.project_id;if("string"==typeof r&&r!==t)throw new Qe(t,r)}(r)}catch(e){if(e instanceof Qe)return Tt(vt(e.expectedProjectId));throw e}{const e=F(),t=r?.project_id;if(null!==e.project_scope&&"string"==typeof t&&!e.project_scope.includes(t))return Tt(ft("Tool call rejected: project_id is outside your org's MCP project scope.",[`Allowed projects: ${e.project_scope.length} project${1===e.project_scope.length?"":"s"} configured by your admin`,"Use atoms_list_projects to see which projects you can access in this session"]))}let n,a;try{a=await xe(),n=Ee()}catch{return t(r)}const s=await async function(e,t){const r=parseInt(process.env.ATOMS_RATE_LIMIT_RPM??"",10)||Be,i=new Date,n=new Date(i.getTime()-Fe);try{const{count:a,error:s}=await t.from("mcp_rate_limits").select("id",{count:"exact",head:!0}).eq("user_id",e).gte("requested_at",n.toISOString());if(s)throw new Error(s.message);if((a??0)>=r){const{data:r,error:a}=await t.from("mcp_rate_limits").select("requested_at").eq("user_id",e).gte("requested_at",n.toISOString()).order("requested_at",{ascending:!0}).limit(1).maybeSingle();if(a)throw new Error(a.message);const s=(r?new Date(r.requested_at).getTime():i.getTime())+Fe-i.getTime();return{allowed:!1,retryAfterSeconds:Math.max(1,Math.ceil(s/1e3))}}t.from("mcp_rate_limits").insert({user_id:e,requested_at:i.toISOString()}).then(({error:e})=>{e&&process.stderr.write(`[rate-limiter] insert failed: ${e.message}\n`)});const o=new Date(i.getTime()-2*Fe);return t.from("mcp_rate_limits").delete().eq("user_id",e).lt("requested_at",o.toISOString()).then(()=>{}),{allowed:!0}}catch(e){return process.stderr.write(`[rate-limiter] DB check failed (${e instanceof Error?e.message:String(e)}), falling back to allow.\n`),{allowed:!0}}}(n,a);if(!s.allowed)return Tt(jt(s.retryAfterSeconds));if("1"===process.env.ATOMS_MCP_LOCKDOWN||"true"===process.env.ATOMS_MCP_LOCKDOWN){const e=r?.project_id;if("string"==typeof e)try{const{requireWriteAccess:t}=await Promise.resolve().then(()=>(Ue(),je));await t(a,e)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message){const{accessDeniedError:e}=await Promise.resolve().then(()=>(Ut(),lt));return Tt(e("Viewer (lockdown mode: read access requires editor or admin)"))}}}const o=function(){const e=performance.now();return()=>Math.round(performance.now()-e)}();let d,c="success";try{const e=await t(r),i=e;if(!0===i?.isError){c="error";const e=i?.content;if(e?.[0]?.text)try{d=JSON.parse(e[0].text).message}catch{}}return e}catch(e){throw c="error",d=e instanceof Error?e.message:String(e),e}finally{const t={tool_name:e,params:r,status:c,duration_ms:o(),error_msg:d,project_id:r.project_id,session_id:un,client_name:_n};try{!function(e,t,r){const i=Ve(r.params);e.from("mcp_audit_log").insert({user_id:t,tool_name:r.tool_name,params:i,status:r.status,duration_ms:r.duration_ms,error_msg:r.error_msg??null,project_id:r.project_id??null,session_id:r.session_id??null,client_name:r.client_name??null}).then(({error:e})=>{e&&process.stderr.write(`[audit] Failed to log: ${e.message}\n`)})}(await xe(),n,t)}catch{}}}}function wn(){return un}function vn(){return fn}function jn(){return _n}D(gn,{ATOMS_MCP_SERVER_VERSION:()=>ln,getMcpActor:()=>vn,getMcpClientName:()=>jn,getMcpSessionId:()=>wn,server:()=>mn});var xn=U({"src/server.ts"(){Ge(),Ye(),Z(),Xe(),z(),V(),ot(),dt(),Ue(),Ut(),Dt(),dn=v(import.meta.url),cn=dn("../package.json"),ln=cn.version,mn=new w({name:"atoms-mcp-server",version:ln}),pn=(e,t,r)=>{if(G(e))return mn.registerTool(e,t,r)},un=m(),_n=et(process.env.ATOMS_CLIENT_NAME),fn=function(){const e=et(_n);return"cursor"===e?"mcp_cursor":"copilot"===e?"mcp_copilot":"mcp_claude"}(),pn("atoms_status",{title:"ATOMS MCP Health Check",description:'Check the health and authentication status of the ATOMS MCP server.\n\nCall this FIRST if other atoms tools fail or return auth errors.\nThis tool works WITHOUT authentication — it checks whether credentials\nexist and are valid, and tells the user exactly what to do if not.\n\nArgs: None\n\nReturns:\n { status: "authenticated"|"not_authenticated"|"expired", email, message, next_steps }\n\nExamples:\n - "Is ATOMS MCP working?" → atoms_status()\n - "Why are atoms tools failing?" → atoms_status()',inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},async()=>{try{const{readCredentials:e,isTokenValid:t}=await Promise.resolve().then(()=>(ce(),X)),r=await e();if(!r)return{content:[{type:"text",text:JSON.stringify({status:"not_authenticated",message:"No ATOMS credentials found. You need to login first.",next_steps:["1. Open a terminal on your machine","2. Run: npx @atoms-tech/atoms-mcp login","3. Complete the login in your browser","4. Restart Claude Desktop (quit and reopen)","5. Try your query again"]},null,2)}]};if(!t(r))try{const{getValidToken:e}=await Promise.resolve().then(()=>(ve(),ue)),{email:t}=await e(),{invalidateClient:r}=await Promise.resolve().then(()=>(Ue(),je));return r(),{content:[{type:"text",text:JSON.stringify({status:"authenticated",email:t,message:"Token was expired but has been refreshed. You're good to go!"},null,2)}]}}catch{return{content:[{type:"text",text:JSON.stringify({status:"expired",email:r.user_email??"unknown",message:"Your session has expired and could not be refreshed.",next_steps:["1. Open a terminal on your machine","2. Run: npx @atoms-tech/atoms-mcp logout","3. Run: npx @atoms-tech/atoms-mcp login","4. Complete the login in your browser","5. Restart Claude Desktop (quit and reopen)"]},null,2)}]}}return{content:[{type:"text",text:JSON.stringify({status:"authenticated",email:r.user_email??"unknown",message:"ATOMS MCP is connected and authenticated. All tools are ready."},null,2)}]}}catch(e){return{content:[{type:"text",text:JSON.stringify({status:"error",message:`Health check failed: ${e instanceof Error?e.message:String(e)}`,next_steps:["1. Open a terminal","2. Run: npx @atoms-tech/atoms-mcp login","3. Restart Claude Desktop"]},null,2)}]}}}),pn("atoms_list_projects",{title:"List ATOMS Projects",description:'List all projects the authenticated user has access to.\n\nThis is the ENTRY POINT tool. Call this first to discover project IDs,\nthen use those IDs with all other tools.\n\nArgs: None\n\nReturns:\n { status: "success", data: [{ id, name, description, org_id, created_at }] }\n\nExamples:\n - "What projects do I have?" → atoms_list_projects()\n - "Show my projects" → atoms_list_projects()\n\nWorkflow:\n atoms_list_projects → atoms_list_items(project_id) → atoms_get_item(project_id, item_id)',inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_projects",async()=>{const{listProjectsHandler:e}=await Promise.resolve().then(()=>(Qt(),zt));return e()})),pn("atoms_list_items",{title:"List ATOMS Items",description:'List items in an ATOMS project with optional type/domain/level filters.\n\nThis is the primary ENTRY POINT for discovering items. Use it to browse\nproject contents before drilling into specific items with atoms_get_item.\n\nArgs:\n - project_id (string, UUID): The project to list items from\n - type (string, optional): Filter: requirement|test-case|note|table\n - domain (string, optional): Filter by domain tag\n - level (string, optional): Filter by level (System, Subsystem, Component)\n - limit (number, optional): Max results, 1-200 (default 50)\n - offset (number, optional): Pagination offset (default 0)\n\nReturns:\n { status: "success", data: [{ id, title, type, status, domains, level }], meta: { total_count, limit, offset, has_more } }\n\nExamples:\n - "Show me all requirements" → atoms_list_items(project_id, type="requirement")\n - "What test cases exist?" → atoms_list_items(project_id, type="test-case")\n\nErrors:\n - "Project not found" → verify project_id\n - "Access denied" → check org membership',inputSchema:{project_id:h.string().uuid("Must be a valid project UUID").describe("UUID of the project"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:h.string().max(64).optional().describe("Filter by domain tag (e.g., 'Safety', 'Performance')"),level:h.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),limit:h.number().int().min(1).max(200).default(50).describe("Max results (default 50, max 200)"),offset:h.number().int().min(0).default(0).describe("Pagination offset (default 0)"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID. Returns items as they were at that baseline's moment. Mutually exclusive with as_of."),as_of:h.string().datetime().optional().describe("Optional ISO 8601 timestamp. Returns items as they were at that moment. Mutually exclusive with baseline_id.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_items",async e=>{const{listItemsHandler:t}=await Promise.resolve().then(()=>(Kt(),Gt));return t(e)})),pn("atoms_get_item",{title:"Get ATOMS Item Details",description:'Get full details of a single item including relationships, test history, and ownership.\n\nUse after atoms_list_items or atoms_search to drill into a specific item.\n\nArgs:\n - project_id (string, UUID): The project containing the item\n - item_id (string): Item ID (e.g., "REQ-001", "TC-050")\n\nReturns:\n Full WorkItem with relationships, test runs, ownership, metadata.\n\nExamples:\n - "Show me requirement REQ-001" → atoms_get_item(project_id, "REQ-001")\n - "Get test case details" → atoms_get_item(project_id, "TC-050")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID (e.g., REQ-001)"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID. Returns the item as it was at that baseline's moment. Mutually exclusive with as_of."),as_of:h.string().datetime().optional().describe("Optional ISO 8601 timestamp. Returns the item as it was at that moment. Mutually exclusive with baseline_id.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_get_item",async e=>{const{getItemHandler:t}=await Promise.resolve().then(()=>(er(),Zt));return t(e)})),pn("atoms_search",{title:"Search ATOMS Items",description:'Full-text search across items in a project. Searches title, body, and summary.\n\nAlternative entry point to atoms_list_items when you know what you\'re looking for.\n\nArgs:\n - project_id (string, UUID): The project to search in\n - query (string): Search text (max 1000 chars)\n - type (string, optional): Filter by type\n - asset (string, optional): Filter to items with at least one run on the given asset\n (VIN, module, lot, ...). Case-sensitive exact match.\n - limit (number, optional): Max results, 1-100 (default 25)\n\nReturns:\n Matching items with @basic fields, ranked by relevance.\n\nExamples:\n - "Find braking requirements" → atoms_search(project_id, "braking system")\n - "Search for safety tests" → atoms_search(project_id, "safety", type="test-case")\n - "Tests that ran on this VIN" → atoms_search(project_id, "", asset="WEV...0005")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),query:h.string().min(1).max(1e3).describe("Search text"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),asset:h.string().max(200).optional().describe("Filter to items with at least one run on this asset (case-sensitive exact)"),limit:h.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID. Searches within the project state at that baseline's moment.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_search",async e=>{const{searchHandler:t}=await Promise.resolve().then(()=>(ir(),tr));return t(e)})),pn("atoms_semantic_search",{title:"Semantic Search ATOMS Items",description:'Meaning-based search across items using pgvector embeddings (GTE-small, 384d).\n\nUse when atoms_search (exact/substring) misses conceptually related items.\nBest for natural-language concept queries; atoms_search is better for exact phrases or IDs.\n\nArgs:\n - project_id (string, UUID): The project to search in\n - query (string): Natural-language concept query (max 1000 chars)\n - type (string, optional): Filter by type\n - limit (number, optional): Max results, 1-50 (default 10)\n\nReturns:\n Semantically similar items ranked by cosine similarity.\n\nExamples:\n - "Find braking-related requirements" → atoms_semantic_search(project_id, "braking deceleration stopping distance")\n - "Safety-related test cases" → atoms_semantic_search(project_id, "hazard risk mitigation safety", type="test-case")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),query:h.string().min(1).max(1e3).describe("Natural-language concept query"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),limit:h.number().int().min(1).max(50).default(10).describe("Max results (default 10, max 50)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_semantic_search",async e=>{const{semanticSearchHandler:t}=await Promise.resolve().then(()=>(sr(),nr));return t(e)})),yn({appName:"coverage",htmlFile:"coverage-app.html",toolName:"atoms_get_coverage",title:"Get Test Coverage Report",description:'Find requirements without linked test cases (coverage gaps).\n\nEssential for compliance reporting in safety-critical industries.\n\nIn MCP App hosts (Claude, ChatGPT), renders a visual coverage heatmap.\nFalls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): The project to analyze\n - domain (string, optional): Filter by domain\n - level (string, optional): Filter by level\n - baseline_id (string, UUID, optional): coverage at that baseline\'s moment\n - as_of (string ISO 8601, optional): coverage at that moment\n baseline_id and as_of are mutually exclusive.\n\nReturns:\n { covered, uncovered, total, coverage_percent, uncovered_items: [...] }\n\nExamples:\n - "What\'s our test coverage?" → atoms_get_coverage(project_id)\n - "Safety coverage gaps?" → atoms_get_coverage(project_id, domain="Safety")\n - "Coverage at PDR submission" → atoms_get_coverage(project_id, baseline_id="...")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),domain:h.string().max(64).optional().describe("Filter by domain tag"),level:h.string().max(32).optional().describe("Filter by level"),baseline_id:h.string().uuid().optional().describe("Compute coverage as of this baseline's snapshot moment"),as_of:h.string().datetime().optional().describe("Compute coverage as of this ISO 8601 moment")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:bn("atoms_get_coverage",async e=>{const{getCoverageHandler:t}=await Promise.resolve().then(()=>(cr(),or));return t(e)})}),pn("atoms_get_history",{title:"Get Item Change History",description:"Audit trail for an item — who changed what, when, and whether it was human or AI.\n\nShows actor attribution (user vs mcp_<client>) and session grouping.\n\nArgs:\n - project_id (string, UUID): The project containing the item\n - item_id (string): Item ID\n - limit (number, optional): Max entries (default 20, max 100)\n\nReturns:\n Change entries with actor, session_id, event_type, changed_at, fields_changed.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID"),limit:h.number().int().min(1).max(100).default(20).describe("Max entries (default 20)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_get_history",async e=>{const{getHistoryHandler:t}=await Promise.resolve().then(()=>(pr(),lr));return t(e)})),yn({appName:"mermaid",htmlFile:"mermaid-app.html",toolName:"atoms_export_mermaid",title:"Export Mermaid Diagram",description:"Generate a Mermaid diagram of the requirement/test hierarchy.\n\nOutput is paste-ready for markdown docs. Shows parent-child relationships\nand requirement-to-test-case verification links.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive pan/zoom diagram.\nFalls back to Mermaid text for non-UI clients.\n\nArgs:\n - project_id (string, UUID): The project to visualize\n - root_item_id (string, optional): Start node (default: all roots)\n - depth (number, optional): Max depth (default 3, max 10)\n - include_tests (boolean, optional): Show test case links (default true)\n - baseline_id (string, UUID, optional): render the graph at that baseline's moment\n - as_of (string ISO 8601, optional): render the graph at that moment\n baseline_id and as_of are mutually exclusive.\n\nReturns:\n Mermaid graph string.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),root_item_id:h.string().max(20).optional().describe("Root item ID to start from"),depth:h.number().int().min(1).max(10).default(3).describe("Max depth"),include_tests:h.boolean().default(!0).describe("Include test case links"),baseline_id:h.string().uuid().optional().describe("Render the graph as of this baseline's snapshot moment"),as_of:h.string().datetime().optional().describe("Render the graph as of this ISO 8601 moment")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},extraResourceDomains:["cdn.jsdelivr.net","mermaid.ink"],handler:bn("atoms_export_mermaid",async e=>{const{exportMermaidHandler:t}=await Promise.resolve().then(()=>(hr(),ur));return t(e)})}),pn("atoms_create_item",{title:"Create ATOMS Item",description:"Create a new requirement, test case, or note in a project.\n\nRequires editor or admin role. Changes are logged with AI actor attribution.\n\nArgs:\n - project_id (string, UUID): Target project\n - type (string): requirement|test-case|note\n - title (string): Item title (max 500 chars)\n - body (string, optional): Rich text body\n - summary (string, optional): Brief summary\n - domains (string[], optional): Domain tags\n - level (string, optional): System|Subsystem|Component\n - parent_ids (string[], optional): Parent item IDs to link\n\nReturns:\n Created item with generated ID (e.g., \"REQ-058\")\n\nSide effects:\n - Logs to change_history with actor='mcp_<client>'\n - Creates relationships if parent_ids provided",inputSchema:{project_id:h.string().uuid().describe("UUID of the target project"),type:h.enum(["requirement","test-case","note"]).describe("Item type"),title:h.string().min(1).max(500).describe("Item title"),body:h.string().max(5e4).optional().describe("Rich text body"),summary:h.string().max(2e3).optional().describe("Brief summary"),domains:h.array(h.string().max(64)).max(20).optional().describe("Domain tags"),level:h.string().max(32).optional().describe("System|Subsystem|Component"),parent_ids:h.array(h.string().max(20)).max(50).optional().describe("Parent item IDs to link"),echo:h.boolean().optional().describe("false → lean {id,type} response, saves ~580 tokens")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_create_item",async e=>{const{createItemHandler:t}=await Promise.resolve().then(()=>(Er(),kr));return t(e)})),pn("atoms_update_item",{title:"Update ATOMS Item",description:"Update an existing item's fields. Only provided fields are changed.\n\nRequires editor or admin role. Logs old/new data diff to change history.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Item to update\n - title, body, summary, domains, level, status: Fields to update (all optional)\n - expected_updated_at (string, optional): Pass metadata.updated_at from atoms_get_item to reject stale writes\n\nReturns:\n Updated item.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).optional().describe("Item ID (single form)"),title:h.string().min(1).max(500).optional().describe("New title"),body:h.string().max(5e4).optional().describe("New body"),summary:h.string().max(2e3).optional().describe("New summary"),domains:h.array(h.string().max(64)).max(20).optional().describe("New domain tags"),level:h.string().max(32).optional().describe("New level"),status:h.enum(["passed","failed","blocked","not-run"]).optional().describe("Test case status"),expected_updated_at:tt.optional().describe("metadata.updated_at value from atoms_get_item; rejects stale writes"),echo:h.boolean().optional().describe("false → lean {id,updated_fields} response, saves ~580 tokens"),reason:h.string().max(500).optional().describe("Change rationale, persisted in audit history"),updates:h.array(h.object({item_id:h.string().min(1).max(20),title:h.string().min(1).max(500).optional(),body:h.string().max(5e4).optional(),summary:h.string().max(2e3).optional(),domains:h.array(h.string().max(64)).max(20).optional(),level:h.string().max(32).optional(),status:h.enum(["passed","failed","blocked","not-run"]).optional(),expected_updated_at:tt.optional()})).optional().describe("Array form: update many items in one call"),idempotency_key:h.string().max(128).optional().describe("Safe retry key (24h TTL)"),continue_on_error:h.boolean().optional().describe("Allow partial success (default false = atomic)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_update_item",async e=>{const{updateItemHandler:t}=await Promise.resolve().then(()=>(Ur(),Or));return t(e)})),pn("atoms_delete_item",{title:"Delete ATOMS Item",description:'Soft-delete an item (sets deleted_at, preserves for audit trail).\n\nRequires editor or admin role. The item is never truly removed.\n\nWhen ATOMS_MCP_REQUIRE_CONFIRMATION is set on the server, this tool uses a\ntwo-step flow: the first call returns a preview + confirmation_token; the\nsecond call (with the token) executes. Tokens expire in 60s and are bound\nto (tool, project_id, item_id).\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Item to delete\n - confirmation_token (string, optional): Token from a prior preview call\n\nReturns:\n Confirmation with deleted item summary, or { status: "confirmation_required",\n preview, confirmation_token } when the confirmation gate is active.',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID to soft-delete"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!1}},bn("atoms_delete_item",async e=>{const{deleteItemHandler:t}=await Promise.resolve().then(()=>(Wr(),Nr));return t(e)})),pn("atoms_restore_item",{title:"Restore ATOMS Item",description:'Undo a soft-delete — restores an item that was previously deleted with atoms_delete_item.\n\nOnly works on items that have been soft-deleted (still in the database with deleted_at set).\nHas no effect if the item is not deleted. Logs the restoration to change_history.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): ID of the deleted item to restore\n\nReturns:\n Confirmation with the restored item\'s title and type.\n\nExamples:\n - "Restore the deleted requirement REQ-042" → atoms_restore_item(project_id, "REQ-042")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().max(256).describe("ID of the soft-deleted item to restore")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_restore_item",async e=>{const{restoreItemHandler:t}=await Promise.resolve().then(()=>(Fr(),Vr));return t(e)})),pn("atoms_link_items",{title:"Link ATOMS Items",description:"Add or remove relationships between items.\n\nSupports: parent, child, related, verifies, verified_by.\nUpdates both items' JSONB data and the shadow table.\n\nArgs:\n - project_id (string, UUID): Project containing both items\n - from_id (string): Source item\n - to_id (string): Target item\n - type (string): parent|child|related|verifies|verified_by\n - action (string): add|remove\n - expected_from_updated_at / expected_to_updated_at (string, optional): pass metadata.updated_at values from atoms_get_item to reject stale link commands\n\nReturns:\n Updated relationship state for both items.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),from_id:h.string().min(1).max(20).optional().describe("Source item ID (single form)"),to_id:h.string().min(1).max(20).optional().describe("Target item ID (single form)"),type:h.enum(["parent","child","related","verifies","verified_by"]).optional().describe("Relationship type"),action:h.enum(["add","remove"]).optional().describe("Add or remove"),expected_from_updated_at:tt.optional().describe("metadata.updated_at for from_id from atoms_get_item"),expected_to_updated_at:tt.optional().describe("metadata.updated_at for to_id from atoms_get_item"),echo:h.boolean().optional().describe("false → lean response"),reason:h.string().max(500).optional().describe("Change rationale, persisted in audit history"),operations:h.array(h.object({action:h.enum(["add","remove"]),from_id:h.string().min(1).max(20),to_id:h.string().min(1).max(20),type:h.enum(["parent","child","related","verifies","verified_by"]),expected_from_updated_at:tt.optional(),expected_to_updated_at:tt.optional()})).optional().describe("Array form: batch relationship ops. Always atomic with DAG cycle detection."),idempotency_key:h.string().max(128).optional().describe("Safe retry key (24h TTL)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_link_items",async e=>{const{linkItemsHandler:t}=await Promise.resolve().then(()=>(Qr(),zr));return t(e)})),yn({appName:"import",htmlFile:"import-app.html",toolName:"atoms_bulk_import",title:"Bulk Import ATOMS Items",description:"Bulk create multiple items in a single tool call. Essential for AI agents\nthat generate 20+ requirements at once.\n\nChecks write access once, generates sequential IDs, batch inserts all items,\nand reports per-item errors without aborting the entire batch.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive results table.\nFalls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): Target project\n - items (array): Up to 100 items to create, each with:\n - type (string): requirement|test-case|note\n - title (string): Item title (max 500 chars)\n - body (string, optional): Rich text body\n - summary (string, optional): Brief summary\n - domains (string[], optional): Domain tags\n - level (string, optional): System|Subsystem|Component\n - parent_id (string, optional): Parent item ID to link\n\nReturns:\n { created: number, items: [{ id, title, type }], errors: [] }\n\nLimits:\n - Maximum 100 items per call\n - If any item fails, others still succeed — errors reported separately\n\nSide effects:\n - Logs to change_history with actor='mcp_<client>' for each created item\n - Creates parent relationships for items with parent_id",inputSchema:{project_id:h.string().uuid().describe("UUID of the target project"),items:h.array(h.object({type:h.enum(["requirement","test-case","note"]).describe("Item type"),title:h.string().min(1).max(500).describe("Item title"),body:h.string().max(5e4).optional().describe("Rich text body"),summary:h.string().max(2e3).optional().describe("Brief summary"),domains:h.array(h.string().max(64)).max(20).optional().describe("Domain tags"),level:h.string().max(32).optional().describe("System|Subsystem|Component"),parent_id:h.string().max(20).optional().describe("Parent item ID to link")})).min(1).max(100).describe("Items to create (1-100)"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},handler:bn("atoms_bulk_import",async e=>{const{bulkImportHandler:t}=await Promise.resolve().then(()=>(Zr(),Yr));return t(e)})}),pn("atoms_record_test_result",{title:"Record Test Result",description:"Record a pass/fail/blocked result for a test case.\n\nAppends to canonical items.data.runs and projects test_results from that\nhistory. Use atoms_get_item to see all results.\n\nArgs:\n - project_id (string, UUID): Project containing the test case\n - item_id (string): Test case ID (e.g., \"TC-001\")\n - result (string): passed|failed|blocked|not-run\n - note (string, optional): Reason or comment for this result\n - asset (string, optional): Asset identifier this run was executed against\n (VIN, module ID, lot number, serial number, etc). Max 200 chars.\n Auto-trimmed; empty/whitespace is treated as omitted. If the asset\n name does not yet exist in the project, it is auto-registered.\n\nReturns:\n Recorded result with timestamp. When asset is provided, also returns\n asset_id and asset_created.\n\nSide effects:\n - Appends to items.data.runs through save_item_with_revision\n - Projects public.test_results from items.data.runs\n - Inserts into assets table if asset name is new in this project\n - Logs to change_history with actor='mcp_<client>'",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Test case ID (e.g., TC-001)"),result:h.enum(["passed","failed","blocked","not-run"]).describe("Test result"),note:h.string().max(1e3).optional().describe("Reason or comment for this result"),asset:h.string().max(200).optional().describe("Asset identifier this run was executed against (VIN, module ID, lot, etc). Auto-registered if new.")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_record_test_result",async e=>{const{recordTestResultHandler:t}=await Promise.resolve().then(()=>(ti(),Xr));return t(e)})),pn("atoms_record_test_results",{title:"Record Test Results (bulk)",description:'Record many runs on one test case in a single atomic call.\n\nOptimized for multi-asset campaigns. Each entry can have its own asset and\nresult. Asset lookup-or-create happens per entry; new asset names are\nauto-registered. Runs are appended to canonical items.data.runs, then\ntest_results is projected from that history. The whole call is one DB\ntransaction; any entry failure rolls back the rest.\n\nUse the singular atoms_record_test_result for one-off runs.\n\nArgs:\n - project_id (string, UUID): Project containing the test case\n - item_id (string): Test case ID (e.g., "TC-001")\n - results (array, 1-100 entries): Each entry has:\n - asset (string, optional): Asset identifier (VIN, module ID, lot)\n - result (string, required): passed|failed|blocked|not-run\n - note (string, optional): Run note\n - date (string, optional): ISO 8601 datetime, default now()\n\nReturns:\n Per-entry outcomes with asset_id, asset_created, run_at, ok flags.\n\nSide effects:\n - Appends to items.data.runs through save_item_with_revision\n - Projects public.test_results from items.data.runs\n - Inserts new rows into assets for any asset name not already registered\n - Logs a single change_history entry summarizing the batch',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Test case ID (e.g., TC-001)"),results:h.array(h.object({asset:h.string().max(200).optional().describe("Asset identifier this run was executed against. Auto-registered if new."),result:h.enum(["passed","failed","blocked","not-run"]).describe("Test result"),note:h.string().max(500).optional().describe("Run note"),date:h.string().optional().describe("ISO 8601 datetime (default: now())")})).min(1).max(100).describe("1 to 100 result entries on this test case")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_record_test_results",async e=>{const{recordTestResultsBulkHandler:t}=await Promise.resolve().then(()=>(di(),ri));return t(e)})),yn({appName:"trace",htmlFile:"trace-app.html",toolName:"atoms_trace",title:"Trace Item Relationships",description:'Walk the traceability graph from a starting item.\n\nAnswers questions like "which requirements does TC-003 verify?" or\n"if I change REQ-001, what\'s affected downstream?"\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive graph visualization.\nFalls back to flat list for non-UI clients.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Starting item ID\n - direction (string): upstream|downstream|both\n - upstream: follow parent/verifies links (dependencies)\n - downstream: follow child/verified_by links (dependents)\n - both: trace in both directions\n - depth (number, optional): Max traversal depth (default 5, max 10)\n - relationship_types (string[], optional): Filter to specific types (parent, child, related, verifies, verified_by)\n\nReturns:\n { root, direction, items: [{ id, title, type, relationship, depth }], total_count }\n\nExamples:\n - "What does TC-003 verify?" → atoms_trace(project_id, "TC-003", "upstream")\n - "What\'s affected by REQ-001?" → atoms_trace(project_id, "REQ-001", "downstream")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Starting item ID"),direction:h.enum(["upstream","downstream","both"]).describe("Traversal direction"),depth:h.number().int().min(1).max(10).default(5).describe("Max traversal depth (default 5)"),relationship_types:h.array(h.enum(["parent","child","related","verifies","verified_by"])).optional().describe("Filter to specific relationship types")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:bn("atoms_trace",async e=>{const{traceHandler:t}=await Promise.resolve().then(()=>(ui(),mi));return t(e)})}),pn("atoms_list_assets",{title:"List Project Assets",description:"List asset identifiers registered in a project.\n\nAssets are free-form per-instance IDs (VINs, module names, lot numbers,\nserial numbers) that test runs are tagged against. They are auto-registered\nthe first time they appear on a run via atoms_record_test_result.\n\nArgs:\n - project_id (string, UUID): Project to list assets for\n - include_archived (boolean, optional): Include archived assets (default false)\n\nReturns:\n { assets: [{ id, name, archived_at, created_at }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),include_archived:h.boolean().optional().describe("Include archived assets (default false)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_assets",async e=>{const{listAssetsHandler:t}=await Promise.resolve().then(()=>(hi(),_i));return t(e)})),pn("atoms_list_variables",{title:"List Project Variables",description:"List all parameterized variables defined in a project.\n\nVariables are shared values (e.g., backup_camera_latency = 2s) that can be\nreferenced in requirement bodies as {variable_name}.\n\nArgs:\n - project_id (string, UUID): Project to list variables for\n - baseline_id (string, UUID, optional): list variables as they were at that baseline\n - as_of (string ISO 8601, optional): list variables as they were at that moment\n\nReturns:\n { variables: [{ id, name, value, unit, description }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID for time-travel reads"),as_of:h.string().datetime().optional().describe("Optional ISO timestamp for time-travel reads")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_variables",async e=>{const{listVariablesHandler:t}=await Promise.resolve().then(()=>(Ii(),gi));return t(e)})),pn("atoms_get_variable",{title:"Get Project Variable",description:'Get a specific variable by name, including which items reference it.\n\nArgs:\n - project_id (string, UUID): Project containing the variable\n - variable_name (string): Variable name (e.g., "backup_camera_latency")\n - baseline_id (string, UUID, optional): get the variable as it was at that baseline\n - as_of (string ISO 8601, optional): get the variable as it was at that moment\n\nReturns:\n { variable, referenced_by: [{ id, title, type }], reference_count }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),variable_name:h.string().min(1).max(64).describe("Variable name"),baseline_id:h.string().uuid().optional().describe("Optional baseline UUID for time-travel reads"),as_of:h.string().datetime().optional().describe("Optional ISO timestamp for time-travel reads")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_get_variable",async e=>{const{getVariableHandler:t}=await Promise.resolve().then(()=>(Ii(),gi));return t(e)})),pn("atoms_update_variable",{title:"Update Project Variable",description:"Update a variable's value, unit, or description.\n\nArgs:\n - project_id (string, UUID): Project containing the variable\n - variable_name (string): Variable name\n - value (string, optional): New value\n - unit (string, optional): Unit abbreviation (e.g., 's', 'Hz')\n - description (string, optional): Human-readable description\n\nReturns:\n { variable }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),variable_name:h.string().min(1).max(64).describe("Variable name"),value:h.string().max(500).optional().describe("New value"),unit:h.string().max(32).optional().describe("Unit abbreviation"),description:h.string().max(2e3).optional().describe("Description")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_update_variable",async e=>{const{updateVariableHandler:t}=await Promise.resolve().then(()=>(Ii(),gi));return t(e)})),pn("atoms_create_variable",{title:"Create Project Variable",description:'Create one or many parameterized variables for a project.\n\nSingle form: provide name + value.\nArray form: provide variables[] for bulk creation (one atomic INSERT).\n\nVariables can then be embedded in requirement bodies as {variable_name}.\n\nArgs (single form):\n - project_id (string, UUID): Project to add the variable to\n - name (string): Variable name, letters, digits, underscores; must start with letter/underscore\n - value (string): Initial value (e.g., "100", "true", "nominal")\n - unit (string, optional): Unit abbreviation (e.g., "Hz", "ms", "m/s²")\n - description (string, optional): Human-readable description\n - echo (bool, optional): false → lean {name} response (default true)\n\nArgs (array form):\n - project_id (string, UUID): Project UUID\n - variables[]: Array of { name, value, unit?, description? }\n - echo (bool, optional): false → lean {name} list (default true)\n - idempotency_key (string, optional): Dedup key\n\nReturns (single): { variable, message }\nReturns (array): { batch_id, created, variables, message }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).optional().describe("Variable name (single form)"),value:h.string().min(1).max(500).optional().describe("Initial value (single form)"),unit:h.string().max(32).optional().describe("Unit abbreviation"),description:h.string().max(2e3).optional().describe("Description"),echo:h.boolean().optional().describe("false → lean response (default true)"),variables:h.array(h.object({name:h.string().min(1).max(64).describe("Variable name"),value:h.string().min(1).max(500).describe("Initial value"),unit:h.string().max(32).optional().describe("Unit abbreviation"),description:h.string().max(2e3).optional().describe("Description")})).optional().describe("Array form: batch variable creation. Atomic — all or none."),idempotency_key:h.string().max(128).optional().describe("Dedup key for retries")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_create_variable",async e=>{const{createVariableHandler:t}=await Promise.resolve().then(()=>(Ii(),gi));return t(e)})),pn("atoms_delete_variable",{title:"Delete Project Variable",description:"Delete a project variable. Refuses by default if any items reference it.\n\nArgs:\n - project_id (string, UUID): Project containing the variable\n - name (string): Variable name to delete\n - force (bool, optional): Delete even if items still reference it (default false)\n\nReturns:\n { deleted, name, referenced_by_items, warning? }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Variable name to delete"),force:h.boolean().optional().describe("Delete even if items reference it (default false)"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1}},bn("atoms_delete_variable",async e=>{const{deleteVariableHandler:t}=await Promise.resolve().then(()=>(Ii(),gi));return t(e)})),pn("atoms_list_domains",{title:"List Project Domains",description:"List all domain tags registered in a project's taxonomy, with item counts.\n\nArgs:\n - project_id (string, UUID): Project to inspect\n\nReturns:\n { domains: [{ name, item_count }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_domains",async e=>{const{listDomainsHandler:t}=await Promise.resolve().then(()=>(Ri(),Ei));return t(e)})),pn("atoms_create_domain",{title:"Create Project Domain",description:'Register a new domain tag in the project taxonomy.\n\nOnce created, items can be tagged with this domain via atoms_create_item or atoms_update_item.\n\nArgs:\n - project_id (string, UUID): Project to add the domain to\n - name (string): Domain name (lowercased automatically, e.g., "safety", "can-bus")\n\nReturns:\n { domain: { name }, message }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Domain name")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_create_domain",async e=>{const{createDomainHandler:t}=await Promise.resolve().then(()=>(Ri(),Ei));return t(e)})),pn("atoms_list_levels",{title:"List Project Levels",description:"List the project's level taxonomy in order, with each level's L# (positional index) and item count.\n\nArgs:\n - project_id (string, UUID): Project to list levels for\n\nReturns:\n { levels: [{ name, prefix, item_count }], total }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_levels",async e=>{const{listLevelsHandler:t}=await Promise.resolve().then(()=>(Sr(),gr));return t(e)})),pn("atoms_create_level",{title:"Create Project Level",description:'Register a new level in the project taxonomy so items can be tagged with it and it shows in the Strata view. Appended to the ordered level list; its L# is the next index. Names are user-defined labels (lowercased automatically); matching is case-insensitive.\n\nArgs:\n - project_id (string, UUID): Project to add the level to\n - name (string): Level name, e.g. "feature", "system", "subsystem"\n\nReturns:\n { level: { name, prefix }, message }',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Level name (user-defined label)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_create_level",async e=>{const{createLevelHandler:t}=await Promise.resolve().then(()=>(Sr(),gr));return t(e)})),pn("atoms_update_domain",{title:"Rename Project Domain",description:"Rename a domain tag. Updates the taxonomy and propagates to all tagged items.\n\nUse dry_run=true first to see how many items will be affected before committing.\n\nArgs:\n - project_id (string, UUID): Project containing the domain\n - name (string): Current domain name\n - new_name (string): New domain name\n - dry_run (bool, optional): If true, return impact count without making changes (default false)\n\nReturns (dry_run=false):\n { domain: { name }, renamed_from, items_updated, message }\n\nReturns (dry_run=true):\n { dry_run: true, rename: { from, to }, items_affected, message }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Current domain name"),new_name:h.string().min(1).max(64).describe("New domain name"),dry_run:h.boolean().optional().describe("Preview impact without applying (default false)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_update_domain",async e=>{const{updateDomainHandler:t}=await Promise.resolve().then(()=>(Ri(),Ei));return t(e)})),pn("atoms_delete_domain",{title:"Delete Project Domain",description:"Delete a domain tag from the project taxonomy.\n\nRefuses by default if any items are tagged with it. With force=true, deletes the\ndomain and strips the tag from all items.\n\nArgs:\n - project_id (string, UUID): Project containing the domain\n - name (string): Domain name to delete\n - force (bool, optional): Strip from items and delete anyway (default false)\n\nReturns:\n { deleted, name, items_updated, message }",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(64).describe("Domain name to delete"),force:h.boolean().optional().describe("Strip from all items and delete (default false)"),confirmation_token:h.string().max(500).optional().describe("Token from a prior preview call (only used when ATOMS_MCP_REQUIRE_CONFIRMATION is set)")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1}},bn("atoms_delete_domain",async e=>{const{deleteDomainHandler:t}=await Promise.resolve().then(()=>(Ri(),Ei));return t(e)})),yn({appName:"summary",htmlFile:"summary-app.html",toolName:"atoms_project_summary",title:"Project Compliance Summary",description:'One-call project health and compliance dashboard.\n\nReturns item counts by type, test execution status, requirement coverage\n(overall and by domain), and recent change activity.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive dashboard with charts.\nFalls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): Project to summarize\n\nReturns:\n { project_name, counts, test_status, coverage, coverage_by_domain, recent_changes, last_updated }\n\nExamples:\n - "Give me a compliance snapshot" → atoms_project_summary(project_id)\n - "How\'s our test coverage?" → atoms_project_summary(project_id)',inputSchema:{project_id:h.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:bn("atoms_project_summary",async e=>{const{projectSummaryHandler:t}=await Promise.resolve().then(()=>(Mi(),Ci));return t(e)})}),yn({appName:"browse",htmlFile:"browse-app.html",toolName:"atoms_browse",title:"Browse ATOMS Items",description:'View and filter items in an ATOMS project with a visual browser.\n\nIn MCP App hosts (Claude, ChatGPT), renders an interactive filterable list\nwith expandable item details. Falls back to JSON for non-UI clients.\n\nArgs:\n - project_id (string, UUID): The project to browse\n - type (string, optional): Filter by type (requirement, test-case, note, table)\n - domain (string, optional): Filter by domain tag\n - level (string, optional): Filter by level (System, Subsystem, Component)\n - query (string, optional): Full-text search query\n - limit (number, optional): Max results (default 25, max 100)\n - offset (number, optional): Pagination offset (default 0)\n\nReturns:\n { items: [...], filters: { domains, levels, types }, meta: { total_count, limit, offset, has_more } }\n\nExamples:\n - "Show me the requirements" → atoms_browse(project_id, type="requirement")\n - "Browse safety items" → atoms_browse(project_id, domain="Safety")\n - "Find braking requirements" → atoms_browse(project_id, query="braking")',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),type:h.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:h.string().max(64).optional().describe("Filter by domain tag"),level:h.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),query:h.string().max(1e3).optional().describe("Full-text search query"),limit:h.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)"),offset:h.number().int().min(0).default(0).describe("Pagination offset (default 0)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:bn("atoms_browse",async e=>{const{browseHandler:t}=await Promise.resolve().then(()=>(Fi(),qi));return t(e)})}),pn("atoms_impact_analysis",{title:"Analyze Impact of Changing an Item",description:'Analyze the blast radius of changing a specific item.\n\nTraverses the relationship graph from the given item and returns every item that\nwould be affected by a change — with their types, depths, and full relationship paths.\n\nArgs:\n - project_id (string, UUID): Project containing the item\n - item_id (string): Item to analyze (e.g., "REQ-001")\n - direction (string, optional): "downstream" (default) | "upstream" | "both"\n - downstream: what this item affects (children, verified_by)\n - upstream: what affects this item (parents, verifies)\n - both: full bidirectional traversal\n - depth (integer, optional): Max traversal depth. Omit for full depth (all reachable nodes).\n - include_variable_refs (boolean, optional): Reserved for future variable-aware impact. Default true.\n\nReturns:\n { root: { item_id, title, type }, impacted_items: [{ item_id, title, type, depth, relationship_path }], summary: { total_impacted, by_type, max_depth_reached } }\n\nExamples:\n - "What breaks if I change REQ-001?" → atoms_impact_analysis(project_id, "REQ-001")\n - "What\'s upstream of TC-005?" → atoms_impact_analysis(project_id, "TC-005", direction="upstream")\n - "Show direct dependencies only" → atoms_impact_analysis(project_id, "REQ-001", depth=1)\n\nErrors:\n - "Item not found" → verify item_id exists in the project',inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),item_id:h.string().min(1).max(20).describe("Item ID to analyze (e.g., REQ-001)"),direction:h.enum(["downstream","upstream","both"]).default("downstream").describe("Traversal direction (default: downstream)"),depth:h.number().int().min(1).optional().describe("Max traversal depth. Omit for full depth."),include_variable_refs:h.boolean().default(!0).describe("Reserved for variable-aware impact (future). Default true.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_impact_analysis",async e=>{const{impactAnalysisHandler:t}=await Promise.resolve().then(()=>(Qi(),zi));return t(e)})),pn("atoms_list_baselines",{title:"List ATOMS Baselines",description:"List named baselines (named project snapshots) for a project, newest first.\n\nA baseline is a labeled moment in time. Use atoms_create_baseline to make one,\natoms_diff_baselines to compare two, and atoms_restore_baseline to roll back.",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),limit:h.number().int().min(1).max(100).default(50).describe("Max results (default 50, max 100)"),offset:h.number().int().min(0).default(0).describe("Pagination offset")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_list_baselines",async e=>{const{listBaselinesHandler:t}=await Promise.resolve().then(()=>(hn(),Yi));return t(e)})),pn("atoms_create_baseline",{title:"Create ATOMS Baseline",description:"Create a named baseline (snapshot label) at the current moment for a project.\n\nThe baseline records a name + timestamp. It does NOT copy item data; the project\nstate at that moment is reconstructed via temporal queries against item_revisions.\nNames must be unique per project (case-insensitive).",inputSchema:{project_id:h.string().uuid().describe("UUID of the project"),name:h.string().min(1).max(200).describe("Baseline name (1-200 chars, unique per project)"),description:h.string().max(2e3).optional().describe("Optional description (max 2000 chars)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_create_baseline",async e=>{const{createBaselineHandler:t}=await Promise.resolve().then(()=>(hn(),Yi));return t(e)})),pn("atoms_lock_baseline",{title:"Lock ATOMS Baseline",description:"Lock a baseline so it becomes audit-grade immutable. Admin only. One-way: there is no unlock.\n\nAfter locking, the baseline's name, description, and snapshot_at cannot be changed,\nand it cannot be deleted. Use for regulatory submissions and DRBs.",inputSchema:{baseline_id:h.string().uuid().describe("UUID of the baseline to lock")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},bn("atoms_lock_baseline",async e=>{const{lockBaselineHandler:t}=await Promise.resolve().then(()=>(hn(),Yi));return t(e)})),pn("atoms_diff_baselines",{title:"Diff ATOMS Baselines",description:"Compute the diff between two baselines: which items were added, removed, or modified.\n\nReturns a summary plus a paginated items array. Pass detail=true to include\nper-item field-level diffs (slower for large projects).",inputSchema:{baseline_a_id:h.string().uuid().describe("UUID of the first (older) baseline"),baseline_b_id:h.string().uuid().describe("UUID of the second (newer) baseline"),detail:h.boolean().default(!1).describe("Include per-item field-level diffs (default false)"),limit:h.number().int().min(1).max(200).default(50).describe("Max items per page (default 50)"),offset:h.number().int().min(0).default(0).describe("Pagination offset")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},bn("atoms_diff_baselines",async e=>{const{diffBaselinesHandler:t}=await Promise.resolve().then(()=>(hn(),Yi));return t(e)})),pn("atoms_restore_baseline",{title:"Restore from ATOMS Baseline",description:"Restore project state to a baseline. Admin only. Preview-by-default.\n\nCall with confirm=false (default) for a dry run that returns items_to_restore count.\nCall with confirm=true to execute: each item present at the baseline gets a new\nrevision matching its historical state, event_source='restore'. Items added AFTER\nthe baseline are NOT deleted. Always recoverable via further history.",inputSchema:{baseline_id:h.string().uuid().describe("UUID of the baseline to restore from"),confirm:h.boolean().default(!1).describe("Set true to execute; false (default) returns a preview")},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1}},bn("atoms_restore_baseline",async e=>{const{restoreBaselineHandler:t}=await Promise.resolve().then(()=>(hn(),Yi));return t(e)}))}});Z(),z(),Ue(),B();var Sn=v(import.meta.url)("../package.json"),kn=process.argv.slice(2)[0];(async function(){switch(kn){case"login":{const{login:e}=await Promise.resolve().then(()=>(Le(),De));try{const{email:t}=await e();process.stderr.write(`\n ✓ Authenticated as ${t}\n`),process.stderr.write(" ✓ Credentials saved to ~/.atoms/credentials.json\n\n"),process.stderr.write(" Next steps:\n"),process.stderr.write(" $ claude mcp add atoms -- npx -y @atoms-tech/atoms-mcp\n"),process.stderr.write("\n Or add to Claude Desktop (Settings → MCP → Edit Config):\n"),process.stderr.write(' {\n "mcpServers": {\n "atoms": {\n "command": "npx",\n "args": ["-y", "@atoms-tech/atoms-mcp"]\n }\n }\n }\n\n')}catch(e){process.stderr.write(`\n ✗ ${e instanceof Error?e.message:String(e)}\n\n`),process.exit(1)}break}case"whoami":{const{getValidToken:e}=await Promise.resolve().then(()=>(ve(),ue));try{const{email:t,user_id:r}=await e();process.stderr.write(`\n ✓ Logged in as ${t}\n User ID: ${r}\n\n`)}catch(e){process.stderr.write("\n ✗ Not authenticated.\n Run: npx @atoms-tech/atoms-mcp login\n\n"),process.exit(1)}break}case"logout":{const{clearCredentials:e}=await Promise.resolve().then(()=>(ce(),X));await e(),process.stderr.write("Logged out. Credentials removed.\n"),process.stderr.write("Run 'npx @atoms-tech/atoms-mcp login' to authenticate again.\n");break}case"--version":case"-v":process.stderr.write(`@atoms-tech/atoms-mcp v${Sn.version}\n`);break;case"--help":case"-h":process.stderr.write("ATOMS MCP Server — AI agent integration for requirements management\n\nUsage:\n npx @atoms-tech/atoms-mcp Start MCP server (stdio)\n npx @atoms-tech/atoms-mcp login Authenticate with ATOMS.tech\n npx @atoms-tech/atoms-mcp logout Switch account / clear credentials\n npx @atoms-tech/atoms-mcp whoami Show current user\n npx @atoms-tech/atoms-mcp --version Show version\n");break;default:{process.stderr.write("[atoms-mcp] Starting MCP server via stdio...\n");try{const{getValidToken:e}=await Promise.resolve().then(()=>(ve(),ue)),{email:t}=await e();process.stderr.write(`[atoms-mcp] Authenticated as ${t}\n`)}catch{process.stderr.write("[atoms-mcp] No credentials found. Starting login flow...\n");try{const{login:e}=await Promise.resolve().then(()=>(Le(),De)),{email:t}=await e();process.stderr.write(`[atoms-mcp] Authenticated as ${t}\n`)}catch(e){process.stderr.write(`[atoms-mcp] Login failed: ${e instanceof Error?e.message:String(e)}\n[atoms-mcp] Starting server without auth. Tools will fail until you run: npx @atoms-tech/atoms-mcp login\n`)}}try{const t=await xe(),r=Ee(),i=await async function(e,t){const r=process.env.ATOMS_MCP_ORG_ID?.trim();if(r)return r;const{data:i,error:n}=await e.from("org_members").select("org_id, joined_at").eq("user_id",t).order("joined_at",{ascending:!0}).limit(1).maybeSingle();return n?(process.stderr.write(`[atoms-mcp] Could not resolve session org: ${n.message}\n`),null):i?.org_id??null}(t,r),n=await async function(e,t,r){if(!r)return{role:null,is_member:!1,effective:q};const{data:i,error:n}=await e.rpc("mcp_resolve_policy",{p_user_id:t,p_org_id:r});return n?(process.stderr.write(`[atoms-mcp] mcp_resolve_policy failed (${n.code}): ${n.message}. Falling back to fail-closed policy.\n`),{role:null,is_member:!1,effective:N}):function(e){if(!e||"object"!=typeof e)return{role:null,is_member:!1,effective:q};const t=e,r="string"==typeof t.role?t.role:null,i=!0===t.is_member,n=t.effective??{},a=(e,t)=>Array.isArray(e)?e.filter(e=>"string"==typeof e):t,s=(e,t)=>"boolean"==typeof e?e:t,o=n.project_scope,d=Array.isArray(o)&&o.length>0?o.filter(e=>"string"==typeof e):null;return{role:r,is_member:i,effective:{read_only:s(n.read_only,q.read_only),lockdown:s(n.lockdown,q.lockdown),block_destructive:s(n.block_destructive,q.block_destructive),block_bulk_import:s(n.block_bulk_import,q.block_bulk_import),cross_project_browse:s(n.cross_project_browse,q.cross_project_browse),allowed_toolsets:a(n.allowed_toolsets,q.allowed_toolsets),forbidden_tools:a(n.forbidden_tools,q.forbidden_tools),project_scope:d,require_confirmation_for:a(n.require_confirmation_for,q.require_confirmation_for)}}}(i)}(t,r,i);e={effective:n.effective,role:n.role,org_id:i},L=e.effective,W=e.org_id,n.is_member&&i?process.stderr.write(`[atoms-mcp] Org policy loaded: org=${i.slice(0,8)}… role=${n.role??"?"} read_only=${n.effective.read_only} lockdown=${n.effective.lockdown}\n`):process.stderr.write("[atoms-mcp] No org membership detected — running with permissive default policy.\n")}catch(e){process.stderr.write(`[atoms-mcp] Could not resolve session policy (${e instanceof Error?e.message:String(e)}). Running with permissive default — RLS still enforces row-level access.\n`)}process.stderr.write(`[atoms-mcp] Tool registry: ${function(){const e=Q(),t=J(),r=[];r.push(e?"read-only":"read+write"),null===t?r.push("all toolsets"):(r.push(`toolsets=${[...t.allowed].sort().join(",")||"(none — only always-on tools)"}`),t.unknown.length>0&&r.push(`unknown=${t.unknown.join(",")}`));const i=process.env.ATOMS_MCP_PROJECT_ID?.trim();return i&&r.push(`project_scope=${i.slice(0,8)}…`),("1"===process.env.ATOMS_MCP_LOCKDOWN||"true"===process.env.ATOMS_MCP_LOCKDOWN)&&r.push("lockdown=on"),r.join(" | ")}()}\n`);const{server:t}=await Promise.resolve().then(()=>(xn(),gn)),r=new j;await t.connect(r),process.stderr.write("[atoms-mcp] MCP server running. Waiting for tool calls...\n")}}var e})().catch(e=>{process.stderr.write(`[atoms-mcp] Fatal: ${e instanceof Error?e.message:String(e)}\n`),process.exit(1)});