@atoms-tech/atoms-mcp 0.6.0 → 0.6.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.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var e=require("fs/promises"),t=require("os"),r=require("path"),i=require("@supabase/supabase-js"),n=require("@modelcontextprotocol/ext-apps/server"),a=require("url"),o=require("crypto"),s=require("@modelcontextprotocol/sdk/server/mcp.js"),d=require("zod"),c=require("http"),l=require("@modelcontextprotocol/sdk/server/stdio.js"),m="undefined"!=typeof document?document.currentScript:null;function p(e){return e&&e.__esModule?e:{default:e}}var u,_,f=p(e),h=p(r),g=p(o),y=Object.defineProperty,b=Object.getOwnPropertyNames,w=(e,t)=>function(){return e&&(t=(0,e[b(e)[0]])(e=0)),t},v=(e,t)=>{for(var r in t)y(e,r,{get:t[r],enumerable:!0})};function j(e,t=0){if(t>5)return e;if("string"==typeof e){for(const t of _)if(t.test(e.trim()))return"[redacted]";return e}if(Array.isArray(e))return e.map(e=>j(e,t+1));if(null!==e&&"object"==typeof e){const r={};for(const[i,n]of Object.entries(e))u.has(i.toLowerCase())?r[i]="[redacted]":r[i]=j(n,t+1);return r}return e}function x(e){return j(e)}var S,k,I,E,O,T,C,U,q,H=w({"src/middleware/audit.ts"(){u=new Set(["access_token","refresh_token","password","api_key","secret","token","authorization","credential","credentials","private_key"]),_=[/^[^\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_-]+$/]}}),D=w({"src/middleware/rate-limiter.ts"(){S=60,k=6e4}});function R(e){return q.get(e)}var A=w({"src/core/tool-catalog.ts"(){I=new Set(["atoms_status","atoms_list_projects"]),E=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_create_variable","atoms_update_variable","atoms_delete_variable","atoms_create_domain","atoms_update_domain","atoms_delete_domain"]),O=new Set(["atoms_delete_item","atoms_delete_variable","atoms_delete_domain"]),T=new Set(["atoms_bulk_import"]),C={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_project_summary"],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"]},U=Object.keys(C),q=new Map;for(const[e,t]of Object.entries(C))for(const r of t)q.set(r,e)}});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)U.includes(e)?r.add(e):i.push(e);return{allowed:r,unknown:i}}function P(){const e=process.env.ATOMS_MCP_READ_ONLY;return"1"===e||"true"===e?.toLowerCase()}function M(e){if(P()&&E.has(e))return!1;if(I.has(e))return!0;const t=z();if(null===t)return!0;const r=R(e);return!!r&&t.allowed.has(r)}var $,N,W,L=w({"src/middleware/tool-registry.ts"(){A()}});function F(){const e=process.env.ATOMS_MCP_PROJECT_ID?.trim();return e?$.test(e)?e:(N||(process.stderr.write(`[atoms-mcp] WARNING: ATOMS_MCP_PROJECT_ID="${e.slice(0,50)}" is not a valid UUID — scope ignored.\n`),N=!0),null):null}var V,J,B=w({"src/middleware/project-scope.ts"(){$=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,N=!1,W=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}}}}),Q=w({"src/core/types.ts"(){V={read_only:!1,lockdown:!1,block_destructive:!1,block_bulk_import:!1,cross_project_browse:!0,allowed_toolsets:["items","traceability","coverage","variables","domains"],forbidden_tools:[],project_scope:null,require_confirmation_for:[]}}});function G(){return J}var Y=w({"src/middleware/session-policy.ts"(){Q(),J=V}});function K(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 Z,X,ee=w({"src/core/policy.ts"(){A()}}),te={};async function re(){try{const t=await e.readFile(X,"utf-8"),r=JSON.parse(t);return r.access_token&&r.refresh_token?r:null}catch{return null}}async function ie(t){await e.mkdir(Z,{recursive:!0,mode:448}),await e.writeFile(X,JSON.stringify(t,null,2),{mode:384})}async function ne(){const e=await re();return!!e&&e.expires_at>Date.now()+6e4}function ae(e){return e.expires_at>Date.now()+6e4}async function oe(){try{await e.unlink(X)}catch{}}function se(){return X}v(te,{clearCredentials:()=>oe,getCredentialsPath:()=>se,hasValidCredentials:()=>ne,isTokenValid:()=>ae,readCredentials:()=>re,writeCredentials:()=>ie});var de,ce,le,me=w({"src/auth/token-store.ts"(){Z=r.join(t.homedir(),".atoms"),X=r.join(Z,"credentials.json")}}),pe={};v(pe,{ATOMS_APP_URL:()=>le,ATOMS_SUPABASE_ANON_KEY:()=>ce,ATOMS_SUPABASE_URL:()=>de});var ue=w({"src/config.ts"(){de="https://gmebjyhomsbvhrxffzre.supabase.co",ce="sb_publishable_b49MUAB8XCrQiF7x5b9lUA_w1aplSBH",le=process.env.ATOMS_APP_URL??"https://x.atoms.tech"}}),_e={};async function fe(){const e=process.env.ATOMS_ACCESS_TOKEN;if(e){const t=he(e);return{access_token:e,refresh_token:"",user_id:t.sub,email:t.email??""}}const t=await re();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=he(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=i.createClient(de,ce),{data:n,error:a}=await r.auth.refreshSession({refresh_token:t.refresh_token});if(a||!n.session)throw new Error(`Token refresh failed: ${a?.message??"No session returned"}. Re-run 'npx @atoms-tech/atoms-mcp login'.`);const o=n.session;return await ie({access_token:o.access_token,refresh_token:o.refresh_token,expires_at:Date.now()+1e3*(o.expires_in??3600),user_email:o.user?.email}),{access_token:o.access_token,refresh_token:o.refresh_token,user_id:o.user?.id??"",email:o.user?.email??""}}function he(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}v(_e,{getValidToken:()=>fe});var ge,ye,be,we,ve=w({"src/auth/refresh.ts"(){me(),ue()}}),je={};async function xe(){if(ge&&we>Date.now()+6e4)return ge;ge&&process.stderr.write("[atoms-mcp] Token expiring, refreshing client...\n");const{access_token:e,user_id:t,email:r}=await fe(),n=e.split(".");if(3===n.length)try{const e=JSON.parse(Buffer.from(n[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString());we=1e3*(e.exp??0)}catch{we=Date.now()+36e5}const a=i.createClient(de,ce,{global:{headers:{Authorization:`Bearer ${e}`}}});return ye=t,be=r,ge=a}function Se(){if(!ye)throw new Error("Not authenticated. Call getClient() first.");return ye}function ke(){return be??""}async function Ie(e,t){const r=Se(),{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}v(je,{getClient:()=>xe,getUserEmail:()=>ke,getUserId:()=>Se,requireWriteAccess:()=>Ie});var Ee,Oe,Te,Ce,Ue,qe=w({"src/db/client.ts"(){ve(),ue(),ge=null,ye=null,be=null,we=0}}),He={};function De(e,t=Oe){if("string"!=typeof e)return"";const r=e.replace(Ce,"").replace(Ue,"");return r.length<=t?r:r.slice(0,t)+"..."}function Re(e,t){return{status:"success",data:e,...t?{meta:t}:{}}}function Ae(e,t,r){return{total_count:e,limit:t,offset:r,has_more:r+t<e}}function ze(e,t){return{status:"error",message:e,next_steps:t}}function Pe(e,t){return ze(`${e} '${De(t)}' not found`,[`Verify the ${e.toLowerCase()} ID is correct`,"Use atoms_list_items or atoms_search to find valid IDs"])}function Me(e){return ze(`${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 $e(e){return ze(`Validation error: ${De(e,Te)}`,["Check the parameter types and constraints in the tool description","Ensure all required parameters are provided"])}function Ne(){return ze("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 We(e){return ze("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 Le(e){return ze("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 Fe(e){return ze(`Parse error: ${De(e,Te)}`,["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 Ve(e){const t=e instanceof Error?e.message:String(e);return/not authenticated/i.test(t)?Ne():/rate.?limit|too many requests|429/i.test(t)?Le():/invalid input syntax|invalid json|unexpected token|malformed|json parse/i.test(t)?Fe(t):Je(t)}function Je(e){return/row.level security|permission denied for/i.test(e)?ze("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"]):ze(`Database error: ${De(e,Te)}`,["This may be a temporary issue — try again","If the error persists, check that the project_id is valid"])}function Be(e){return null==e?e:`<user_content>${e}</user_content>`}function Qe(e){const t=JSON.stringify(e,null,2);if(t.length>Ee&&"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 Ge(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}],structuredContent:e,isError:!0}}v(He,{CHARACTER_LIMIT:()=>Ee,accessDeniedError:()=>Me,authError:()=>Ne,classifyError:()=>Ve,dbError:()=>Je,errorResponse:()=>ze,formatErrorResult:()=>Ge,formatToolResult:()=>Qe,notFoundError:()=>Pe,paginationMeta:()=>Ae,parseError:()=>Fe,rateLimitError:()=>Le,sanitizeForEcho:()=>De,scopeError:()=>We,success:()=>Re,validationError:()=>$e,wrapUserContent:()=>Be});var Ye,Ke=w({"src/tools/_base.ts"(){Ee=25e3,Oe=50,Te=200,Ce=new RegExp("[\\u0000-\\u001F\\u007F]","g"),Ue=new RegExp("[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\uFEFF]","g")}});var Ze=w({"src/apps/register.ts"(){Ye={resourceDomains:["fonts.googleapis.com","fonts.gstatic.com"]}}});async function Xe(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 et(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 tt(e,t,r,i){const n=r.trim();if(!n)return{items:[],totalCount:0};let a=e.from("items").select("*",{count:"exact"}).eq("project_id",t).is("deleted_at",null).or(`title.ilike.%${n}%,data->>body.ilike.%${n}%,data->>summary.ilike.%${n}%`).limit(i.limit);i.type&&(a=a.eq("type",i.type));const{data:o,error:s,count:d}=await a;if(s)throw new Error(s.message);return{items:o??[],totalCount:d??0}}async function rt(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 it=w({"src/db/queries.ts"(){}}),nt={};async function at(){try{const e=await xe(),t=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),r=F(),i=G().project_scope,n=i?new Set(i):null;return Qe(Re(t.filter(e=>!r||e.id===r).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 Ge(Je(e instanceof Error?e.message:"Unknown error"))}}v(nt,{listProjectsHandler:()=>at});var ot=w({"src/tools/list-projects.ts"(){qe(),it(),B(),Y(),Ke()}}),st={};async function dt(e){try{const t=await xe(),{items:r,totalCount:i}=await Xe(t,e.project_id,{type:e.type,domain:e.domain,level:e.level,limit:e.limit,offset:e.offset});return Qe(Re(r.map(e=>({id:e.id,title:Be(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),Ae(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(st,{listItemsHandler:()=>dt});var ct=w({"src/tools/list-items.ts"(){qe(),it(),Ke()}}),lt={};async function mt(e){try{const t=await xe(),r=await et(t,e.project_id,e.item_id);if(!r)return Ge(Pe("Item",e.item_id));const i=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),n={parents:[],children:[],related:[],verified_by:[],verifies:[]};for(const t of i)if(t.from_id===e.item_id)t.type in n&&n[t.type].push(t.to_id);else{const e={parent:"children",child:"parents",verified_by:"verifies",verifies:"verified_by",related:"related"}[t.type]??t.type;e in n&&n[e].push(t.from_id)}let a=[];if("test-case"===r.type){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 Qe(Re({id:r.id,type:r.type,title:Be(r.title),summary:Be(r.data?.summary),body:Be(r.data?.body),tags:r.data?.tags??{domains:[]},ownership:r.data?.ownership??{primary:null,additional:[]},relationships:n,links:r.data?.links??[],status:r.data?.status,..."test-case"===r.type?{latest_result:a[0]?.result??"not-run",test_results:a}:{},metadata:{created_at:r.created_at,created_by:r.created_by,updated_at:r.updated_at,updated_by:r.updated_by,source_id:r.data?.metadata?.source_id}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(lt,{getItemHandler:()=>mt});var pt=w({"src/tools/get-item.ts"(){qe(),it(),Ke()}}),ut={};async function _t(e){try{const t=await xe(),{items:r,totalCount:i}=await tt(t,e.project_id,e.query,{type:e.type,limit:e.limit});return Qe(Re(r.map(e=>({id:e.id,title:Be(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),Ae(i,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(ut,{searchHandler:()=>_t});var ft=w({"src/tools/search.ts"(){qe(),it(),Ke()}}),ht={};async function gt(e){try{const t=await xe(),{items:r,totalCount:i}=await async function(e,t,r,i){const{ATOMS_SUPABASE_URL:n,ATOMS_SUPABASE_ANON_KEY:a}=await Promise.resolve().then(()=>(ue(),pe)),o=await fetch(`${n}/functions/v1/embed`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${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});return Qe(Re(r.map(e=>({id:e.id,title:Be(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),Ae(i,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(ht,{semanticSearchHandler:()=>gt});var yt=w({"src/tools/semantic-search.ts"(){qe(),it(),Ke()}}),bt={};async function wt(e){try{const t=await xe(),{covered:r,uncovered:i,total:n}=await async function(e,t,r){const{data:i,error:n}=await e.from("items").select("*").eq("project_id",t).eq("type","requirement").is("deleted_at",null);if(n)throw new Error(n.message);const a=(i??[]).filter(e=>!(r.domain&&!(e.data?.tags?.domains??[]).includes(r.domain)||r.level&&e.data?.tags?.level!==r.level)),{data:o,error:s}=await e.from("item_relationships").select("from_id").eq("project_id",t).eq("type","verified_by");if(s)throw new Error(s.message);const d=new Set((o??[]).map(e=>e.from_id));return{covered:a.filter(e=>d.has(e.id)),uncovered:a.filter(e=>!d.has(e.id)),total:a.length}}(t,e.project_id,{domain:e.domain,level:e.level}),a=i.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 Qe(Re({covered:r.length,uncovered:i.length,total:n,coverage_percent:n>0?Math.round(r.length/n*1e3)/10:100,uncovered_items:a}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(bt,{getCoverageHandler:()=>wt});var vt=w({"src/tools/get-coverage.ts"(){qe(),it(),Ke()}}),jt={};async function xt(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 Qe(Re(i,Ae(i.length,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(jt,{getHistoryHandler:()=>xt});var St=w({"src/tools/get-history.ts"(){qe(),it(),Ke()}}),kt={};async function It(e){try{const t=await xe(),{data:r,error:i}=await t.from("items").select("id, title, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(i)throw new Error(i.message);if(!r||0===r.length)return Qe(Re({mermaid:"graph TD\n empty[No items in project]"}));const{data:n,error:a}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(a)throw new Error(a.message);const o=new Map;for(const e of r)o.set(e.id,e);const s=["graph TD"],d=new Set,c=new Set,l=(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 m;m=e.root_item_id?[e.root_item_id]:r.filter(e=>!l.has(e.id)).filter(t=>!(!e.include_tests&&"test-case"===t.type)).map(e=>e.id);const p=m.map(e=>({id:e,depth:0}));for(;p.length>0;){const{id:t,depth:r}=p.shift();if(d.has(t)||r>e.depth)continue;d.add(t);const i=o.get(t);if(!i)continue;if(!e.include_tests&&"test-case"===i.type)continue;const a=Et(i.title),l="requirement"===i.type?`["${a}"]`:"test-case"===i.type?`(["${a}"])`:`("${a}")`;s.push(` ${t}${l}`);for(const i of n??[]){if(i.from_id!==t)continue;const n=o.get(i.to_id);if(!n)continue;if(!e.include_tests&&"test-case"===n.type)continue;const a=`${i.from_id}-${i.type}-${i.to_id}`;c.has(a)||(c.add(a),"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}`),!d.has(i.to_id)&&r+1<=e.depth&&p.push({id:i.to_id,depth:r+1}))}}return Qe(Re({mermaid:s.join("\n"),node_count:d.size,edge_count:c.size}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}function Et(e){return e.replace(/"/g,"'").replace(/[[\]{}()]/g,"").substring(0,50)}v(kt,{exportMermaidHandler:()=>It});var Ot=w({"src/tools/export-mermaid.ts"(){qe(),Ke()}}),Tt={};async function Ct(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}if(e.domains&&e.domains.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 t=e.domains.filter(e=>!i.includes(e));if(t.length>0)return Ge($e(`Unknown domain${t.length>1?"s":""}: ${t.join(", ")}. Registered: ${i.join(", ")}. Add new domains via Taxonomy Management in the ATOMS web app first.`))}}const i=await rt(t,e.project_id,e.type),n=(new Date).toISOString(),a={id:i,type:e.type,title:e.title,summary:e.summary,body:e.body,tags:{domains:e.domains??[],level:e.level},ownership:{primary:null,additional:[]},relationships:{parents:e.parent_ids??[],children:[],related:[]},links:[],metadata:{created_at:n,created_by:r,updated_at:n,updated_by:r}},{error:o}=await t.from("items").insert({id:i,project_id:e.project_id,type:e.type,title:e.title,data:a,created_by:r,updated_by:r}).select().single();if(o)throw new Error(o.message);if(e.parent_ids&&e.parent_ids.length>0){const r=e.parent_ids.map(t=>({from_id:i,to_id:t,type:"parent",project_id:e.project_id}));await t.from("item_relationships").insert(r).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: relationship sync failed: ${e.message}\n`)})}return await t.from("change_history").insert({item_id:i,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:a,actor:"mcp_claude",session_id:Gr()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),!1===e.echo?Qe(Re({id:i,type:e.type})):Qe(Re({id:i,type:e.type,title:e.title,summary:e.summary??"",body:e.body??"",domains:e.domains??[],level:e.level??"",project_id:e.project_id}))}catch(e){return Ge(Ve(e))}}v(Tt,{createItemHandler:()=>Ct});var Ut=w({"src/tools/create-item.ts"(){qe(),it(),Zr(),Ke()}}),qt={};async function Ht(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const i=async(i,n,a,o)=>{const s=await et(t,e.project_id,i);if(!s)return{updated_fields:[],error:`Item "${i}" not found`};if(void 0!==n.domains&&n.domains.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=n.domains.filter(e=>!i.includes(e));if(e.length>0)return{updated_fields:[],error:`Unknown domains: ${e.join(", ")}`}}}const d={...s.data},c={...s.data},l=[];void 0!==n.title&&(c.title=n.title,l.push("title")),void 0!==n.body&&(c.body=n.body,l.push("body")),void 0!==n.summary&&(c.summary=n.summary,l.push("summary")),void 0!==n.domains&&(c.tags={...c.tags,domains:n.domains},l.push("domains")),void 0!==n.level&&(c.tags={...c.tags,level:n.level},l.push("level")),void 0!==n.status&&(c.status=n.status,l.push("status")),c.metadata={...c.metadata,updated_at:(new Date).toISOString(),updated_by:r};const{error:m}=await t.from("items").update({title:n.title??s.title,data:c,updated_by:r}).eq("id",i).eq("project_id",e.project_id);if(m)return{updated_fields:[],error:m.message};const p={item_id:i,project_id:e.project_id,changed_by:r,event_type:"updated",old_data:d,new_data:c,actor:"mcp_claude",session_id:Gr()};return a&&(p.batch_id=a),o&&(p.reason=o),await t.from("change_history").insert(p).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),{updated_fields:l,updatedData:c,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 Ge($e(`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 Qe(Re({batch_id:t,updated:n.length,results:!1===e.echo?void 0:n,errors:a}))}if(!e.item_id)return Ge($e("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},void 0,e.reason);if(s)return s.includes("not found")?Ge(Pe("Item",e.item_id)):s.includes("Unknown domain")?Ge($e(s)):Ge(Je(s));if(!1===e.echo)return Qe(Re({id:e.item_id,updated_fields:n}));const d=a??{};return Qe(Re({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 Ge(Ve(e))}}v(qt,{updateItemHandler:()=>Ht});var Dt,Rt,At=w({"src/tools/update-item.ts"(){qe(),it(),Zr(),Ke()}});function zt(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!!G().require_confirmation_for.includes(e)}function Pt(e,t=Dt){const r=Date.now()+t,i={...e,exp:r},n=Buffer.from(JSON.stringify(i)).toString("base64url");return{token:`${n}.${g.default.createHmac("sha256",Rt).update(n).digest("base64url")}`,expires_at:new Date(r).toISOString(),expires_in_seconds:Math.round(t/1e3)}}function Mt(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=g.default.createHmac("sha256",Rt).update(r).digest("base64url"),a=Buffer.from(i,"utf8"),o=Buffer.from(n,"utf8");if(a.length!==o.length||!g.default.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 $t(e){if(Array.isArray(e))return e.map($t);if(null!==e&&"object"==typeof e){const t={};for(const r of Object.keys(e).sort())t[r]=$t(e[r]);return t}return e}function Nt(e){const t=JSON.stringify($t(e));return g.default.createHash("sha256").update(t).digest("base64url").slice(0,16)}var Wt,Lt=w({"src/middleware/confirmation.ts"(){Y(),Dt=6e4,Rt=(()=>{const e=process.env.ATOMS_MCP_CONFIRMATION_SECRET;return e&&e.length>=16?e:g.default.randomBytes(32).toString("base64url")})()}}),Ft={};async function Vt(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const i=await et(t,e.project_id,e.item_id);if(!i)return Ge(Pe("Item",e.item_id));if(zt(Wt)){const t={tool:Wt,project_id:e.project_id,target:e.item_id};if(!e.confirmation_token){const r=Pt(t);return Qe(Re({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=Mt(e.confirmation_token,t);if(!r.valid)return Ge($e(`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:"mcp_claude",session_id:Gr()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Qe(Re({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 Ge(Ve(e))}}v(Ft,{deleteItemHandler:()=>Vt});var Jt=w({"src/tools/delete-item.ts"(){qe(),it(),Zr(),Lt(),Ke(),Wt="atoms_delete_item"}}),Bt={};async function Qt(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("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 Ge(Pe("Item",e.item_id));if(!i.deleted_at)return Ge(ze(`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.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:"mcp_claude",session_id:Gr()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Qe(Re({id:e.item_id,title:i.title,type:i.type,restored:!0,message:`Item ${e.item_id} restored successfully.`}))}catch(e){return Ge(Ve(e))}}v(Bt,{restoreItemHandler:()=>Qt});var Gt,Yt,Kt=w({"src/tools/restore-item.ts"(){qe(),Zr(),Ke()}}),Zt={};async function Xt(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const i=async(i,n,a)=>{const o=await et(t,e.project_id,i.from_id);if(!o)throw new Error(`Item "${i.from_id}" not found`);const s=await et(t,e.project_id,i.to_id);if(!s)throw new Error(`Item "${i.to_id}" not found`);const d=Gt(i.type);if("add"===i.action){const{data:n}=await t.from("item_relationships").select("from_id").eq("from_id",i.from_id).eq("to_id",i.to_id).eq("type",i.type).eq("project_id",e.project_id).maybeSingle();if(!n){const{error:r}=await t.from("item_relationships").insert({from_id:i.from_id,to_id:i.to_id,type:i.type,project_id:e.project_id});if(r)throw new Error(r.message)}const a=JSON.parse(JSON.stringify(o.data));a.relationships=a.relationships??{};const c=a.relationships[Yt(i.type)]??[];c.includes(i.to_id)||(a.relationships[Yt(i.type)]=[...c,i.to_id]),await t.from("items").update({data:a,updated_by:r}).eq("id",i.from_id).eq("project_id",e.project_id);const l=JSON.parse(JSON.stringify(s.data));l.relationships=l.relationships??{};const m=l.relationships[Yt(d)]??[];m.includes(i.from_id)||(l.relationships[Yt(d)]=[...m,i.from_id]),await t.from("items").update({data:l,updated_by:r}).eq("id",i.to_id).eq("project_id",e.project_id)}else{await Promise.all([t.from("item_relationships").delete().eq("from_id",i.from_id).eq("to_id",i.to_id).eq("type",i.type).eq("project_id",e.project_id),t.from("item_relationships").delete().eq("from_id",i.to_id).eq("to_id",i.from_id).eq("type",d).eq("project_id",e.project_id)]);const n=JSON.parse(JSON.stringify(o.data));n.relationships?.[Yt(i.type)]&&(n.relationships[Yt(i.type)]=n.relationships[Yt(i.type)].filter(e=>e!==i.to_id)),await t.from("items").update({data:n,updated_by:r}).eq("id",i.from_id).eq("project_id",e.project_id);const a=JSON.parse(JSON.stringify(s.data));a.relationships?.[Yt(d)]&&(a.relationships[Yt(d)]=a.relationships[Yt(d)].filter(e=>e!==i.from_id)),await t.from("items").update({data:a,updated_by:r}).eq("id",i.to_id).eq("project_id",e.project_id)}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:"mcp_claude",session_id:Gr()};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 Ge($e(`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 Ge(Pe("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=e=>{const t=new Set,r=[e];for(;r.length;){const e=r.pop();if(t.has(e))return e;t.add(e);for(const t of d.get(e)??[])r.push(t)}return null};for(const e of d.keys()){const t=c(e);if(t)return Ge($e(`Batch would create a cycle at item "${t}". No changes applied.`))}const l=crypto.randomUUID(),m=e.operations.filter(e=>"remove"===e.action),p=e.operations.filter(e=>"add"===e.action);for(const t of[...m,...p])await i(t,l,e.reason);return Qe(Re({batch_id:l,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?Ge($e("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},void 0,e.reason),!1===e.echo?Qe(Re({action:e.action,from_id:e.from_id,to_id:e.to_id})):Qe(Re({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}`}))):Ge($e("Provide (from_id, to_id, type, action) for single-op or operations[] for batch."))}catch(e){return Ge(Ve(e))}}v(Zt,{linkItemsHandler:()=>Xt});var er=w({"src/tools/link-items.ts"(){qe(),it(),Zr(),Ke(),Gt=e=>"parent"===e?"child":"child"===e?"parent":"verifies"===e?"verified_by":"verified_by"===e?"verifies":"related",Yt=e=>"parent"===e?"parents":"child"===e?"children":e}}),tr={};async function rr(e){try{if(!e.items||0===e.items.length)return Ge($e("items array must contain at least 1 item"));if(e.items.length>100)return Ge($e(`Maximum 100 items per call (received ${e.items.length}). Split into multiple calls.`));const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}if(zt("atoms_bulk_import")){const t={tool:"atoms_bulk_import",project_id:e.project_id,target:Nt(e.items)};if(!e.confirmation_token){const r=Pt(t),i={};for(const t of e.items)i[t.type]=(i[t.type]??0)+1;return Qe(Re({status:"confirmation_required",preview:{total_items:e.items.length,by_type:i,sample_titles:e.items.slice(0,5).map(e=>e.title)},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 will invalidate the token.`}))}const r=Mt(e.confirmation_token,t);if(!r.valid)return Ge($e(`confirmation_token rejected: ${r.reason}`))}const i=(new Date).toISOString(),n=Gr(),a=[],o=[],s=[];for(let r=0;r<e.items.length;r++){const i=e.items[r];try{const n=await rt(t,e.project_id,i.type);s.push({itemId:n,input:i,index:r})}catch(e){o.push({index:r,title:i.title,error:`ID generation failed: ${e instanceof Error?e.message:String(e)}`})}}if(0===s.length)return Qe(Re({created:0,items:[],errors:o}));const d=[],c=new Map;for(const{itemId:t,input:n}of s){const a={id:t,type:n.type,title:n.title,summary:n.summary,body:n.body,tags:{domains:n.domains??[],level:n.level},ownership:{primary:null,additional:[]},relationships:{parents:n.parent_id?[n.parent_id]:[],children:[],related:[]},links:[],metadata:{created_at:i,created_by:r,updated_at:i,updated_by:r}};c.set(t,a),d.push({id:t,project_id:e.project_id,type:n.type,title:n.title,data:a,created_by:r,updated_by:r})}const{error:l}=await t.from("items").insert(d);if(l)for(const{itemId:e,input:r,index:i}of s){const n=d.find(t=>t.id===e);if(!n)continue;const{error:s}=await t.from("items").insert(n);s?o.push({index:i,title:r.title,error:s.message}):a.push({id:e,title:r.title,type:r.type})}else for(const{itemId:e,input:t}of s)a.push({id:e,title:t.title,type:t.type});const m=s.filter(({input:e})=>e.parent_id).filter(({itemId:e})=>a.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}));m.length>0&&await t.from("item_relationships").insert(m).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: bulk relationship sync failed: ${e.message}\n`)});const p=a.map(t=>({item_id:t.id,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:c.get(t.id)??null,actor:"mcp_claude",session_id:n}));return p.length>0&&await t.from("change_history").insert(p).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: bulk change_history log failed: ${e.message}\n`)}),Qe(Re({created:a.length,items:a,errors:o.length>0?o:[],project_id:e.project_id}))}catch(e){return Ge(Ve(e))}}v(tr,{bulkImportHandler:()=>rr});var ir=w({"src/tools/bulk-import.ts"(){qe(),it(),Zr(),Lt(),Ke()}}),nr={};async function ar(e){try{const t=await xe(),r=Se(),i=ke();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const n=await et(t,e.project_id,e.item_id);if(!n)return Ge(Pe("Item",e.item_id));if("test-case"!==n.type)return Ge($e(`Item '${e.item_id}' is a ${n.type}, not a test-case`));const a=(new Date).toISOString(),{error:o}=await t.from("test_results").insert({item_id:e.item_id,project_id:e.project_id,result:e.result,run_by:i||r,run_at:a,note:e.note??null});if(o)throw new Error(o.message);return 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:a,note:e.note},actor:"mcp_claude",session_id:Gr()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Qe(Re({item_id:e.item_id,result:e.result,run_at:a,run_by:i||r,note:e.note??null,message:`Test result '${e.result}' recorded for ${e.item_id}`}))}catch(e){return Ge(Ve(e))}}v(nr,{recordTestResultHandler:()=>ar});var or,sr,dr,cr,lr=w({"src/tools/record-test-result.ts"(){qe(),it(),Zr(),Ke()}}),mr={};async function pr(e){try{const t=Math.min(Math.max(e.depth??5,1),10),r=await xe(),i=await et(r,e.project_id,e.item_id);if(!i)return Ge(Pe("Item",e.item_id));const{data:n,error:a}=await r.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(a)throw new Error(a.message);const{data:o,error:s}=await r.from("items").select("id, title, type").eq("project_id",e.project_id).is("deleted_at",null);if(s)throw new Error(s.message);const d=new Map;for(const e of o??[])d.set(e.id,{title:e.title,type:e.type});const c=new Map,l=(e,t,r)=>{c.has(e)||c.set(e,[]),c.get(e).push({neighbor:t,relType:r})},m=e.relationship_types?new Set(e.relationship_types):null;for(const t of n??[]){const r=t.type;if(("upstream"===e.direction||"both"===e.direction)&&(sr.includes(r)&&(m&&!m.has(r)||l(t.from_id,t.to_id,r)),dr.includes(r))){const e=cr[r]??r;m&&!m.has(e)||l(t.to_id,t.from_id,e)}if(("downstream"===e.direction||"both"===e.direction)&&(dr.includes(r)&&(m&&!m.has(r)||l(t.from_id,t.to_id,r)),sr.includes(r))){const e=cr[r]??r;m&&!m.has(e)||l(t.to_id,t.from_id,e)}"related"===r&&(m&&!m.has("related")||(l(t.from_id,t.to_id,"related"),l(t.to_id,t.from_id,"related")))}const p=new Set;p.add(e.item_id);const u=[],_=[],f=[],h=c.get(e.item_id)??[];for(const t of h)f.push({id:t.neighbor,depth:1,relType:t.relType,from:e.item_id});for(;f.length>0&&u.length<or;){const{id:e,depth:r,relType:i,from:n}=f.shift();if(p.has(e)||r>t)continue;p.add(e);const a=d.get(e);if(a&&(u.push({id:e,title:a.title,type:a.type,relationship:i,depth:r,from:n}),_.push({from:n,to:e,type:i}),r<t)){const t=c.get(e)??[];for(const i of t)p.has(i.neighbor)||f.push({id:i.neighbor,depth:r+1,relType:i.relType,from:e})}}const g=d.get(e.item_id),y=g?{id:e.item_id,title:g.title,type:g.type,relationship:"root",depth:0,from:e.item_id}:{id:e.item_id,title:i.title??e.item_id,type:i.type??"unknown",relationship:"root",depth:0,from:e.item_id};return Qe(Re({root:e.item_id,direction:e.direction,depth_limit:t,items:[y,...u],edges:_,total_count:u.length+1,...u.length>=or?{truncated:!0,truncation_message:`Results capped at ${or} items. Use a smaller depth or filter by relationship_types.`}:{}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(mr,{traceHandler:()=>pr});var ur=w({"src/tools/trace.ts"(){qe(),it(),Ke(),or=200,sr=["parent","verifies"],dr=["child","verified_by"],cr={parent:"child",child:"parent",verifies:"verified_by",verified_by:"verifies",related:"related"}}}),_r={};async function fr(e){try{const t=await xe(),{data:r,error:i}=await t.from("project_variables").select("*").eq("project_id",e.project_id).order("name");if(i)throw new Error(i.message);return Qe(Re({variables:r||[],total:(r||[]).length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function hr(e){try{const t=await xe(),{data:r,error:i}=await t.from("project_variables").select("*").eq("project_id",e.project_id).eq("name",e.variable_name).maybeSingle();if(i)throw new Error(i.message);if(!r)return Ge(Pe("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 Qe(Re({variable:r,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")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function gr(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}if(!(await t.from("project_variables").select("*").eq("project_id",e.project_id).eq("name",e.variable_name).maybeSingle()).data)return Ge(Pe("Variable",e.variable_name));const i={updated_by:r,updated_at:(new Date).toISOString()};void 0!==e.value&&(i.value=e.value),void 0!==e.unit&&(i.unit=e.unit||null),void 0!==e.description&&(i.description=e.description||null);const{data:n,error:a}=await t.from("project_variables").update(i).eq("project_id",e.project_id).eq("name",e.variable_name).select().single();if(a)throw new Error(a.message);return Qe(Re({variable:n}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function yr(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("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 Ge($e(`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 Ge($e(`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 Ge($e(`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})),{data:l,error:m}=await t.from("project_variables").insert(c).select();if(m)throw new Error(m.message);return Qe(Re({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 Ge($e("Provide either (name, value) for single variable or variables[] for batch."));if(!i.test(e.name))return Ge($e(`Variable name "${e.name}" is invalid. Use letters, digits, and underscores only; must start with a letter or underscore.`));const{data:n,error:a}=await t.from("project_variables").insert({project_id:e.project_id,name:e.name,value:e.value,unit:e.unit??null,description:e.description??null,created_by:r,updated_by:r}).select().single();if(a){if(a.message.includes("unique")||a.message.includes("duplicate"))return Ge($e(`Variable "${e.name}" already exists in this project.`));throw new Error(a.message)}return!1===e.echo?Qe(Re({name:e.name})):Qe(Re({variable:n,message:`Variable "${e.name}" created. Reference it in requirement bodies as {${e.name}}.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function br(e){try{const t=await xe();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const{data:r}=await t.from("project_variables").select("id").eq("project_id",e.project_id).eq("name",e.name).maybeSingle();if(!r)return Ge(Pe("Variable",e.name));const{data:i}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).filter("data->>body","ilike",`%{${e.name}}%`),n=(i??[]).length;if(n>0&&!e.force)return Ge($e(`Variable "${e.name}" is referenced in ${n} item${n>1?"s":""}. Use force=true to delete anyway, or remove all {${e.name}} references first.`));if(zt("atoms_delete_variable")){const t={tool:"atoms_delete_variable",project_id:e.project_id,target:e.name};if(!e.confirmation_token){const r=Pt(t);return Qe(Re({status:"confirmation_required",preview:{variable_name:e.name,referenced_by_items:n,would_orphan_references:n>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=Mt(e.confirmation_token,t);if(!r.valid)return Ge($e(`confirmation_token rejected: ${r.reason}`))}const{error:a}=await t.from("project_variables").delete().eq("project_id",e.project_id).eq("name",e.name);if(a)throw new Error(a.message);return Qe(Re({deleted:!0,name:e.name,referenced_by_items:n,warning:n>0?`${n} item${n>1?"s":""} still contain {${e.name}} — those references are now unresolved.`:void 0}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(_r,{createVariableHandler:()=>yr,deleteVariableHandler:()=>br,getVariableHandler:()=>hr,listVariablesHandler:()=>fr,updateVariableHandler:()=>gr});var wr=w({"src/tools/variables.ts"(){qe(),Lt(),Ke()}}),vr={};async function jr(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 xr(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 Sr(e){try{const t=await xe(),{domains:r}=await jr(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 Qe(Re({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")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function kr(e){try{const t=await xe();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const{domains:r,levels:i}=await jr(t,e.project_id),n=e.name.trim().toLowerCase();return n?r.includes(n)?Ge($e(`Domain "${n}" already exists in this project.`)):(await xr(t,e.project_id,[...r,n],i),Qe(Re({domain:{name:n},message:`Domain "${n}" registered. You can now tag items with domains=["${n}"].`}))):Ge($e("Domain name cannot be empty."))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function Ir(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const{domains:i,levels:n}=await jr(t,e.project_id),a=e.name.trim().toLowerCase(),o=e.new_name.trim().toLowerCase();if(!o)return Ge($e("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 Ge(Pe("Domain",a))}if(o!==a&&i.includes(o))return Ge($e(`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 Qe(Re({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;return m=s?i.map(e=>e===a?o:e):i.includes(o)?i:[...i,o],await xr(t,e.project_id,m,n),(new Date).toISOString(),await Promise.all((d??[]).map(i=>{const n=(i.data?.tags?.domains??[]).map(e=>e===a?o:e),s={...i.data,tags:{...i.data?.tags??{},domains:n}};return t.from("items").update({data:s,updated_by:r}).eq("id",i.id).eq("project_id",e.project_id)})),Qe(Re({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")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}async function Er(e){try{const t=await xe(),r=Se();try{await Ie(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return Ge(Me("Viewer"));throw e}const{domains:i,levels:n}=await jr(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 Ge(Pe("Domain",a));if(c>0&&!e.force)return Ge($e(`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(zt("atoms_delete_domain")){const t={tool:"atoms_delete_domain",project_id:e.project_id,target:a};if(!e.confirmation_token){const e=Pt(t);return Qe(Re({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=Mt(e.confirmation_token,t);if(!r.valid)return Ge($e(`confirmation_token rejected: ${r.reason}`))}const l=o?i.filter(e=>e!==a):i;return await xr(t,e.project_id,l,n),(new Date).toISOString(),c>0&&await Promise.all((s??[]).map(i=>{const n={...i.data,tags:{...i.data?.tags??{},domains:(i.data?.tags?.domains??[]).filter(e=>e!==a)}};return t.from("items").update({data:n,updated_by:r}).eq("id",i.id).eq("project_id",e.project_id)})),Qe(Re({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")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(vr,{createDomainHandler:()=>kr,deleteDomainHandler:()=>Er,listDomainsHandler:()=>Sr,updateDomainHandler:()=>Ir});var Or=w({"src/tools/domains.ts"(){qe(),Lt(),Ke()}}),Tr={};async function Cr(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();if(i)throw new Error(i.message);if(!r)return Ge(Pe("Project",e.project_id));const{data:n,error:a}=await t.from("items").select("id, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(a)throw new Error(a.message);const o=n??[],s={requirements:0,test_cases:0,notes:0},d=[],c=[];for(const e of o)if("requirement"===e.type){s.requirements++;const t=e.data?.tags?.domains??[];c.push({id:e.id,domains:t})}else"test-case"===e.type?(s.test_cases++,d.push(e.id)):"note"===e.type&&s.notes++;const l={passed:0,failed:0,blocked:0,not_run:0};if(d.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 d){const t=n.get(e);t&&"not-run"!==t?"passed"===t?l.passed++:"failed"===t?l.failed++:"blocked"===t?l.blocked++:l.not_run++:l.not_run++}}const{data:m,error:p}=await t.from("item_relationships").select("from_id").eq("project_id",e.project_id).eq("type","verified_by");if(p)throw new Error(p.message);const u=new Set((m??[]).map(e=>e.from_id)),_=c.filter(e=>u.has(e.id)).length,f=c.length,h={covered:_,uncovered:f-_,total:f,percent:f>0?Math.round(_/f*1e3)/10:100},g=new Map;for(const e of c){const t=e.domains.length>0?e.domains:["(untagged)"],r=u.has(e.id);for(const e of t){g.has(e)||g.set(e,{covered:0,total:0});const t=g.get(e);t.total++,r&&t.covered++}}const y=Array.from(g.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})),b=new Date;b.setDate(b.getDate()-7);const{count:w,error:v}=await t.from("change_history").select("id",{count:"exact",head:!0}).eq("project_id",e.project_id).gte("changed_at",b.toISOString());if(v)throw new Error(v.message);return Qe(Re({project_id:e.project_id,project_name:r.name,counts:s,test_status:l,coverage:h,coverage_by_domain:y,recent_changes:w??0,last_updated:(new Date).toISOString()}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(Tr,{projectSummaryHandler:()=>Cr});var Ur,qr=w({"src/tools/project-summary.ts"(){qe(),Ke()}}),Hr={};async function Dr(e){try{const t=await xe();let r,i;if(e.query&&e.query.trim().length>0){const n=await tt(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 Xe(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 Qe(Re({items:r,filters:{domains:n.domains,levels:n.levels,types:Ur},project_id:e.project_id},Ae(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v(Hr,{browseHandler:()=>Dr});var Rr,Ar,zr,Pr,Mr=w({"src/tools/browse.ts"(){qe(),it(),Ke(),Ur=["requirement","test-case","note","table"]}}),$r={};async function Nr(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 et(i,e.project_id,e.item_id);if(!n)return Ge(Pe("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||(Ar.includes(r)&&m(e.from_id,e.to_id,r),zr.includes(r)&&m(e.to_id,e.from_id,Pr[r]??r)),"downstream"!==t&&"both"!==t||(zr.includes(r)&&m(e.from_id,e.to_id,r),Ar.includes(r)&&m(e.to_id,e.from_id,Pr[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<Rr;){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:Be(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>=Rr)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:Be(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 Qe(Re({root:{item_id:e.item_id,title:Be(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>=Rr?{truncated:!0}:{}}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?Ge(Ne()):Ge(Je(e instanceof Error?e.message:String(e)))}}v($r,{impactAnalysisHandler:()=>Nr});var Wr,Lr,Fr,Vr,Jr=w({"src/tools/impact-analysis.ts"(){qe(),it(),Ke(),Rr=200,Ar=["parent","verifies","traces_to","allocated_from","decomposed_from"],zr=["child","verified_by","allocated_to","decomposes_to","traced_by"],Pr={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"}}});function Br(e){M(e.toolName)&&function(e,t){const r=`ui://${t.toolName}/${t.htmlFile}`,i=function(e,t){const r=a.fileURLToPath("undefined"==typeof document?require("url").pathToFileURL(__filename).href:m&&"SCRIPT"===m.tagName.toUpperCase()&&m.src||new URL("index.cjs",document.baseURI).href),i=h.default.dirname(r);return h.default.resolve(i,"apps","src","apps",e,t)}(t.appName,t.htmlFile),o=[...Ye.resourceDomains,...t.extraResourceDomains??[]];n.registerAppTool(e,t.toolName,{title:t.title,description:t.description,inputSchema:t.inputSchema,annotations:t.annotations,_meta:{ui:{resourceUri:r}}},t.handler),n.registerAppResource(e,`${t.title} UI`,r,{mimeType:n.RESOURCE_MIME_TYPE},async()=>{const e=await f.default.readFile(i,"utf-8");return{contents:[{uri:r,mimeType:n.RESOURCE_MIME_TYPE,text:e,_meta:{ui:{csp:{resourceDomains:o}}}}]}})}(Wr,e)}function Qr(e,t){return async r=>{const i=function(e,t){if(I.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&&E.has(t))return{allowed:!1,reason:"read_only"};if(e.block_destructive&&O.has(t))return{allowed:!1,reason:"block_destructive"};if(e.block_bulk_import&&T.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"}}(G(),e);if(!i.allowed)return Ge(ze(`Tool ${e} is blocked by org policy.`,[K(i.reason),"Contact your org admin to adjust the MCP access policy."]));try{!function(e){const t=F();if(!t)return;if("object"!=typeof e||null===e)return;const r=e.project_id;if("string"==typeof r&&r!==t)throw new W(t,r)}(r)}catch(e){if(e instanceof W)return Ge(We(e.expectedProjectId));throw e}{const e=G(),t=r?.project_id;if(null!==e.project_scope&&"string"==typeof t&&!e.project_scope.includes(t))return Ge(ze("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=Se()}catch{return t(r)}const o=await async function(e,t){const r=parseInt(process.env.ATOMS_RATE_LIMIT_RPM??"",10)||S,i=new Date,n=new Date(i.getTime()-k);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())+k-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*k);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 Ge(Le(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(()=>(qe(),je));await t(a,e)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message){const{accessDeniedError:e}=await Promise.resolve().then(()=>(Ke(),He));return Ge(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:Fr,client_name:Vr};try{!function(e,t,r){const i=x(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 Gr(){return Fr}var Yr,Kr,Zr=w({"src/server.ts"(){H(),D(),L(),B(),Y(),ee(),qe(),Ke(),Ze(),Wr=new s.McpServer({name:"atoms-mcp-server",version:"0.5.0"}),Lr=(e,t,r)=>{if(M(e))return Wr.registerTool(e,t,r)},Fr=o.randomUUID(),Vr=process.env.ATOMS_CLIENT_NAME??"unknown",Lr("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(()=>(me(),te)),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(),_e)),{email:t}=await e();return{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)}]}}}),Lr("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}},Qr("atoms_list_projects",async()=>{const{listProjectsHandler:e}=await Promise.resolve().then(()=>(ot(),nt));return e()})),Lr("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:d.z.string().uuid("Must be a valid project UUID").describe("UUID of the project"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:d.z.string().max(64).optional().describe("Filter by domain tag (e.g., 'Safety', 'Performance')"),level:d.z.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),limit:d.z.number().int().min(1).max(200).default(50).describe("Max results (default 50, max 200)"),offset:d.z.number().int().min(0).default(0).describe("Pagination offset (default 0)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_list_items",async e=>{const{listItemsHandler:t}=await Promise.resolve().then(()=>(ct(),st));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID (e.g., REQ-001)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_get_item",async e=>{const{getItemHandler:t}=await Promise.resolve().then(()=>(pt(),lt));return t(e)})),Lr("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 - 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")',inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),query:d.z.string().min(1).max(1e3).describe("Search text"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),limit:d.z.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_search",async e=>{const{searchHandler:t}=await Promise.resolve().then(()=>(ft(),ut));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),query:d.z.string().min(1).max(1e3).describe("Natural-language concept query"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),limit:d.z.number().int().min(1).max(50).default(10).describe("Max results (default 10, max 50)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_semantic_search",async e=>{const{semanticSearchHandler:t}=await Promise.resolve().then(()=>(yt(),ht));return t(e)})),Br({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\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")',inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),domain:d.z.string().max(64).optional().describe("Filter by domain tag"),level:d.z.string().max(32).optional().describe("Filter by level")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Qr("atoms_get_coverage",async e=>{const{getCoverageHandler:t}=await Promise.resolve().then(()=>(vt(),bt));return t(e)})}),Lr("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_claude) 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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID"),limit:d.z.number().int().min(1).max(100).default(20).describe("Max entries (default 20)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_get_history",async e=>{const{getHistoryHandler:t}=await Promise.resolve().then(()=>(St(),jt));return t(e)})),Br({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\nReturns:\n Mermaid graph string.",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),root_item_id:d.z.string().max(20).optional().describe("Root item ID to start from"),depth:d.z.number().int().min(1).max(10).default(3).describe("Max depth"),include_tests:d.z.boolean().default(!0).describe("Include test case links")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},extraResourceDomains:["cdn.jsdelivr.net","mermaid.ink"],handler:Qr("atoms_export_mermaid",async e=>{const{exportMermaidHandler:t}=await Promise.resolve().then(()=>(Ot(),kt));return t(e)})}),Lr("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_claude'\n - Creates relationships if parent_ids provided",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the target project"),type:d.z.enum(["requirement","test-case","note"]).describe("Item type"),title:d.z.string().min(1).max(500).describe("Item title"),body:d.z.string().max(5e4).optional().describe("Rich text body"),summary:d.z.string().max(2e3).optional().describe("Brief summary"),domains:d.z.array(d.z.string().max(64)).max(20).optional().describe("Domain tags"),level:d.z.string().max(32).optional().describe("System|Subsystem|Component"),parent_ids:d.z.array(d.z.string().max(20)).max(50).optional().describe("Parent item IDs to link"),echo:d.z.boolean().optional().describe("false → lean {id,type} response, saves ~580 tokens")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Qr("atoms_create_item",async e=>{const{createItemHandler:t}=await Promise.resolve().then(()=>(Ut(),Tt));return t(e)})),Lr("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\nReturns:\n Updated item.",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).optional().describe("Item ID (single form)"),title:d.z.string().min(1).max(500).optional().describe("New title"),body:d.z.string().max(5e4).optional().describe("New body"),summary:d.z.string().max(2e3).optional().describe("New summary"),domains:d.z.array(d.z.string().max(64)).max(20).optional().describe("New domain tags"),level:d.z.string().max(32).optional().describe("New level"),status:d.z.enum(["passed","failed","blocked","not-run"]).optional().describe("Test case status"),echo:d.z.boolean().optional().describe("false → lean {id,updated_fields} response, saves ~580 tokens"),reason:d.z.string().max(500).optional().describe("Change rationale, persisted in audit history"),updates:d.z.array(d.z.object({item_id:d.z.string().min(1).max(20),title:d.z.string().min(1).max(500).optional(),body:d.z.string().max(5e4).optional(),summary:d.z.string().max(2e3).optional(),domains:d.z.array(d.z.string().max(64)).max(20).optional(),level:d.z.string().max(32).optional(),status:d.z.enum(["passed","failed","blocked","not-run"]).optional()})).optional().describe("Array form: update many items in one call"),idempotency_key:d.z.string().max(128).optional().describe("Safe retry key (24h TTL)"),continue_on_error:d.z.boolean().optional().describe("Allow partial success (default false = atomic)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_update_item",async e=>{const{updateItemHandler:t}=await Promise.resolve().then(()=>(At(),qt));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID to soft-delete"),confirmation_token:d.z.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}},Qr("atoms_delete_item",async e=>{const{deleteItemHandler:t}=await Promise.resolve().then(()=>(Jt(),Ft));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().max(256).describe("ID of the soft-deleted item to restore")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_restore_item",async e=>{const{restoreItemHandler:t}=await Promise.resolve().then(()=>(Kt(),Bt));return t(e)})),Lr("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\nReturns:\n Updated relationship state for both items.",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),from_id:d.z.string().min(1).max(20).optional().describe("Source item ID (single form)"),to_id:d.z.string().min(1).max(20).optional().describe("Target item ID (single form)"),type:d.z.enum(["parent","child","related","verifies","verified_by"]).optional().describe("Relationship type"),action:d.z.enum(["add","remove"]).optional().describe("Add or remove"),echo:d.z.boolean().optional().describe("false → lean response"),reason:d.z.string().max(500).optional().describe("Change rationale, persisted in audit history"),operations:d.z.array(d.z.object({action:d.z.enum(["add","remove"]),from_id:d.z.string().min(1).max(20),to_id:d.z.string().min(1).max(20),type:d.z.enum(["parent","child","related","verifies","verified_by"])})).optional().describe("Array form: batch relationship ops. Always atomic with DAG cycle detection."),idempotency_key:d.z.string().max(128).optional().describe("Safe retry key (24h TTL)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_link_items",async e=>{const{linkItemsHandler:t}=await Promise.resolve().then(()=>(er(),Zt));return t(e)})),Br({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_claude' for each created item\n - Creates parent relationships for items with parent_id",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the target project"),items:d.z.array(d.z.object({type:d.z.enum(["requirement","test-case","note"]).describe("Item type"),title:d.z.string().min(1).max(500).describe("Item title"),body:d.z.string().max(5e4).optional().describe("Rich text body"),summary:d.z.string().max(2e3).optional().describe("Brief summary"),domains:d.z.array(d.z.string().max(64)).max(20).optional().describe("Domain tags"),level:d.z.string().max(32).optional().describe("System|Subsystem|Component"),parent_id:d.z.string().max(20).optional().describe("Parent item ID to link")})).min(1).max(100).describe("Items to create (1-100)"),confirmation_token:d.z.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:Qr("atoms_bulk_import",async e=>{const{bulkImportHandler:t}=await Promise.resolve().then(()=>(ir(),tr));return t(e)})}),Lr("atoms_record_test_result",{title:"Record Test Result",description:"Record a pass/fail/blocked result for a test case.\n\nAppends to the test results history. 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\nReturns:\n Recorded result with timestamp.\n\nSide effects:\n - Appends to test_results table\n - Logs to change_history with actor='mcp_claude'",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Test case ID (e.g., TC-001)"),result:d.z.enum(["passed","failed","blocked","not-run"]).describe("Test result"),note:d.z.string().max(1e3).optional().describe("Reason or comment for this result")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Qr("atoms_record_test_result",async e=>{const{recordTestResultHandler:t}=await Promise.resolve().then(()=>(lr(),nr));return t(e)})),Br({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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Starting item ID"),direction:d.z.enum(["upstream","downstream","both"]).describe("Traversal direction"),depth:d.z.number().int().min(1).max(10).default(5).describe("Max traversal depth (default 5)"),relationship_types:d.z.array(d.z.enum(["parent","child","related","verifies","verified_by"])).optional().describe("Filter to specific relationship types")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Qr("atoms_trace",async e=>{const{traceHandler:t}=await Promise.resolve().then(()=>(ur(),mr));return t(e)})}),Lr("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\nReturns:\n { variables: [{ id, name, value, unit, description }], total }",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_list_variables",async e=>{const{listVariablesHandler:t}=await Promise.resolve().then(()=>(wr(),_r));return t(e)})),Lr("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\nReturns:\n { variable, referenced_by: [{ id, title, type }], reference_count }',inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),variable_name:d.z.string().min(1).max(64).describe("Variable name")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_get_variable",async e=>{const{getVariableHandler:t}=await Promise.resolve().then(()=>(wr(),_r));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),variable_name:d.z.string().min(1).max(64).describe("Variable name"),value:d.z.string().max(500).optional().describe("New value"),unit:d.z.string().max(32).optional().describe("Unit abbreviation"),description:d.z.string().max(2e3).optional().describe("Description")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_update_variable",async e=>{const{updateVariableHandler:t}=await Promise.resolve().then(()=>(wr(),_r));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).optional().describe("Variable name (single form)"),value:d.z.string().min(1).max(500).optional().describe("Initial value (single form)"),unit:d.z.string().max(32).optional().describe("Unit abbreviation"),description:d.z.string().max(2e3).optional().describe("Description"),echo:d.z.boolean().optional().describe("false → lean response (default true)"),variables:d.z.array(d.z.object({name:d.z.string().min(1).max(64).describe("Variable name"),value:d.z.string().min(1).max(500).describe("Initial value"),unit:d.z.string().max(32).optional().describe("Unit abbreviation"),description:d.z.string().max(2e3).optional().describe("Description")})).optional().describe("Array form: batch variable creation. Atomic — all or none."),idempotency_key:d.z.string().max(128).optional().describe("Dedup key for retries")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Qr("atoms_create_variable",async e=>{const{createVariableHandler:t}=await Promise.resolve().then(()=>(wr(),_r));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Variable name to delete"),force:d.z.boolean().optional().describe("Delete even if items reference it (default false)"),confirmation_token:d.z.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}},Qr("atoms_delete_variable",async e=>{const{deleteVariableHandler:t}=await Promise.resolve().then(()=>(wr(),_r));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_list_domains",async e=>{const{listDomainsHandler:t}=await Promise.resolve().then(()=>(Or(),vr));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Domain name")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Qr("atoms_create_domain",async e=>{const{createDomainHandler:t}=await Promise.resolve().then(()=>(Or(),vr));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Current domain name"),new_name:d.z.string().min(1).max(64).describe("New domain name"),dry_run:d.z.boolean().optional().describe("Preview impact without applying (default false)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Qr("atoms_update_domain",async e=>{const{updateDomainHandler:t}=await Promise.resolve().then(()=>(Or(),vr));return t(e)})),Lr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Domain name to delete"),force:d.z.boolean().optional().describe("Strip from all items and delete (default false)"),confirmation_token:d.z.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}},Qr("atoms_delete_domain",async e=>{const{deleteDomainHandler:t}=await Promise.resolve().then(()=>(Or(),vr));return t(e)})),Br({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:d.z.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Qr("atoms_project_summary",async e=>{const{projectSummaryHandler:t}=await Promise.resolve().then(()=>(qr(),Tr));return t(e)})}),Br({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:d.z.string().uuid().describe("UUID of the project"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:d.z.string().max(64).optional().describe("Filter by domain tag"),level:d.z.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),query:d.z.string().max(1e3).optional().describe("Full-text search query"),limit:d.z.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)"),offset:d.z.number().int().min(0).default(0).describe("Pagination offset (default 0)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Qr("atoms_browse",async e=>{const{browseHandler:t}=await Promise.resolve().then(()=>(Mr(),Hr));return t(e)})}),Lr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID to analyze (e.g., REQ-001)"),direction:d.z.enum(["downstream","upstream","both"]).default("downstream").describe("Traversal direction (default: downstream)"),depth:d.z.number().int().min(1).optional().describe("Max traversal depth. Omit for full depth."),include_variable_refs:d.z.boolean().default(!0).describe("Reserved for variable-aware impact (future). Default true.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Qr("atoms_impact_analysis",async e=>{const{impactAnalysisHandler:t}=await Promise.resolve().then(()=>(Jr(),$r));return t(e)}))}}),Xr={};async function ei(){const e=o.randomBytes(32).toString("base64url"),t=o.createHash("sha256").update(e).digest("base64url"),r=`http://localhost:${Yr}${Kr}`,i=new a.URL(`${le}/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 o(){clearTimeout(n)}const s=c.createServer(async(i,n)=>{const d=new a.URL(i.url??"/",`http://localhost:${Yr}`);if(d.pathname!==Kr)return n.writeHead(404),void n.end("Not found");try{if("access_denied"===d.searchParams.get("error"))return n.writeHead(200,{"Content-Type":"text/html"}),n.end(ri("Authorization was cancelled.")),o(),s.close(),void r(new Error("Authorization cancelled by user."));const i=d.searchParams.get("code");if(i){const a=await fetch(`${de}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{"Content-Type":"application/json",apikey:ce},body:JSON.stringify({auth_code:i,code_verifier:e})}),d=await a.json();if(!a.ok||!d.access_token){const e=d.error_description??d.msg??"Unknown error";return n.writeHead(200,{"Content-Type":"text/html"}),n.end(ri(`Code exchange failed: ${e}`)),o(),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(de,ce,{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(ri('No ATOMS account found for this Google account.<br><br>Please sign up at <a href="https://x.atoms.tech">x.atoms.tech</a> first, then run this login command again.')),o(),s.close(),void r(new Error("No ATOMS account found. Sign up at x.atoms.tech first."))):(await ie({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(ti(c)),o(),s.close(),void t({email:c}))}const a=d.searchParams.get("access_token"),c=d.searchParams.get("refresh_token"),l=parseInt(d.searchParams.get("expires_in")??"3600",10);a&&c?(await ie({access_token:a,refresh_token:c,expires_at:Date.now()+1e3*l}),n.writeHead(200,{"Content-Type":"text/html"}),n.end(ti("authenticated")),o(),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"),o(),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 ie({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})),o(),s.close(),t({email:e.user_email??"authenticated"})}catch(e){i.writeHead(500),i.end("Failed to store credentials"),o(),s.close(),r(e)}})}}),s.listen(Yr,"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 ti(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 ri(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>`}v(Xr,{login:()=>ei});var ii=w({"src/auth/login.ts"(){me(),ue(),Yr=19275,Kr="/callback"}});Zr(),L(),Y(),qe(),Q();var ni=process.argv.slice(2)[0];(async function(){switch(ni){case"login":{const{login:e}=await Promise.resolve().then(()=>(ii(),Xr));try{const{email:t}=await e();process.stderr.write(`\n [32m✓[0m Authenticated as ${t}\n`),process.stderr.write(" [32m✓[0m Credentials saved to ~/.atoms/credentials.json\n\n"),process.stderr.write(" [1mNext steps:[0m\n"),process.stderr.write(" [36m$[0m 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(' [90m{\n "mcpServers": {\n "atoms": {\n "command": "npx",\n "args": ["-y", "@atoms-tech/atoms-mcp"]\n }\n }\n }[0m\n\n')}catch(e){process.stderr.write(`\n [31m✗[0m ${e instanceof Error?e.message:String(e)}\n\n`),process.exit(1)}break}case"whoami":{const{getValidToken:e}=await Promise.resolve().then(()=>(ve(),_e));try{const{email:t,user_id:r}=await e();process.stderr.write(`\n [32m✓[0m Logged in as ${t}\n [90mUser ID: ${r}[0m\n\n`)}catch(e){process.stderr.write("\n [31m✗[0m 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(()=>(me(),te));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 v0.4.0\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(),_e)),{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(()=>(ii(),Xr)),{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`)}}process.stderr.write(`[atoms-mcp] Tool registry: ${function(){const e=P(),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`);try{const t=await xe(),r=Se(),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:V};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:V}):function(e){if(!e||"object"!=typeof e)return{role:null,is_member:!1,effective:V};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,V.read_only),lockdown:o(n.lockdown,V.lockdown),block_destructive:o(n.block_destructive,V.block_destructive),block_bulk_import:o(n.block_bulk_import,V.block_bulk_import),cross_project_browse:o(n.cross_project_browse,V.cross_project_browse),allowed_toolsets:a(n.allowed_toolsets,V.allowed_toolsets),forbidden_tools:a(n.forbidden_tools,V.forbidden_tools),project_scope:d,require_confirmation_for:a(n.require_confirmation_for,V.require_confirmation_for)}}}(i)}(t,r,i);e={effective:n.effective,role:n.role,org_id:i},J=e.effective,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`)}const t=new l.StdioServerTransport;await Wr.connect(t),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
|
+
"use strict";var e=require("fs/promises"),t=require("os"),r=require("path"),i=require("@supabase/supabase-js"),n=require("@modelcontextprotocol/ext-apps/server"),a=require("url"),o=require("crypto"),s=require("@modelcontextprotocol/sdk/server/mcp.js"),d=require("zod"),c=require("http"),l=require("@modelcontextprotocol/sdk/server/stdio.js"),m="undefined"!=typeof document?document.currentScript:null;function p(e){return e&&e.__esModule?e:{default:e}}var u,_,f=p(e),h=p(r),g=p(o),y=Object.defineProperty,w=Object.getOwnPropertyNames,b=(e,t)=>function(){return e&&(t=(0,e[w(e)[0]])(e=0)),t},v=(e,t)=>{for(var r in t)y(e,r,{get:t[r],enumerable:!0})};function j(e,t=0){if(t>5)return e;if("string"==typeof e){for(const t of _)if(t.test(e.trim()))return"[redacted]";return e}if(Array.isArray(e))return e.map(e=>j(e,t+1));if(null!==e&&"object"==typeof e){const r={};for(const[i,n]of Object.entries(e))u.has(i.toLowerCase())?r[i]="[redacted]":r[i]=j(n,t+1);return r}return e}function x(e){return j(e)}var S,k,I,E,O,T,C,U,q,D=b({"src/middleware/audit.ts"(){u=new Set(["access_token","refresh_token","password","api_key","secret","token","authorization","credential","credentials","private_key"]),_=[/^[^\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_-]+$/]}}),H=b({"src/middleware/rate-limiter.ts"(){S=60,k=6e4}});function R(e){return q.get(e)}var A=b({"src/core/tool-catalog.ts"(){I=new Set(["atoms_status","atoms_list_projects"]),E=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_create_variable","atoms_update_variable","atoms_delete_variable","atoms_create_domain","atoms_update_domain","atoms_delete_domain"]),O=new Set(["atoms_delete_item","atoms_delete_variable","atoms_delete_domain"]),T=new Set(["atoms_bulk_import"]),C={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_project_summary"],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"]},U=Object.keys(C),q=new Map;for(const[e,t]of Object.entries(C))for(const r of t)q.set(r,e)}});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)U.includes(e)?r.add(e):i.push(e);return{allowed:r,unknown:i}}function P(){const e=process.env.ATOMS_MCP_READ_ONLY;return"1"===e||"true"===e?.toLowerCase()}function M(e){if(P()&&E.has(e))return!1;if(I.has(e))return!0;const t=z();if(null===t)return!0;const r=R(e);return!!r&&t.allowed.has(r)}var $,N,W,L=b({"src/middleware/tool-registry.ts"(){A()}});function F(){const e=process.env.ATOMS_MCP_PROJECT_ID?.trim();return e?$.test(e)?e:(N||(process.stderr.write(`[atoms-mcp] WARNING: ATOMS_MCP_PROJECT_ID="${e.slice(0,50)}" is not a valid UUID — scope ignored.\n`),N=!0),null):null}var V,J,B=b({"src/middleware/project-scope.ts"(){$=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,N=!1,W=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}}}}),Q=b({"src/core/types.ts"(){V={read_only:!1,lockdown:!1,block_destructive:!1,block_bulk_import:!1,cross_project_browse:!0,allowed_toolsets:["items","traceability","coverage","variables","domains"],forbidden_tools:[],project_scope:null,require_confirmation_for:[]}}});function G(){return J}var Y=b({"src/middleware/session-policy.ts"(){Q(),J=V}});function K(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 Z,X,ee=b({"src/core/policy.ts"(){A()}}),te={};async function re(){try{const t=await e.readFile(X,"utf-8"),r=JSON.parse(t);return r.access_token&&r.refresh_token?r:null}catch{return null}}async function ie(t){await e.mkdir(Z,{recursive:!0,mode:448}),await e.writeFile(X,JSON.stringify(t,null,2),{mode:384})}async function ne(){const e=await re();return!!e&&e.expires_at>Date.now()+6e4}function ae(e){return e.expires_at>Date.now()+6e4}async function oe(){try{await e.unlink(X)}catch{}}function se(){return X}v(te,{clearCredentials:()=>oe,getCredentialsPath:()=>se,hasValidCredentials:()=>ne,isTokenValid:()=>ae,readCredentials:()=>re,writeCredentials:()=>ie});var de,ce,le,me=b({"src/auth/token-store.ts"(){Z=r.join(t.homedir(),".atoms"),X=r.join(Z,"credentials.json")}}),pe={};v(pe,{ATOMS_APP_URL:()=>le,ATOMS_SUPABASE_ANON_KEY:()=>ce,ATOMS_SUPABASE_URL:()=>de});var ue=b({"src/config.ts"(){de="https://gmebjyhomsbvhrxffzre.supabase.co",ce="sb_publishable_b49MUAB8XCrQiF7x5b9lUA_w1aplSBH",le=process.env.ATOMS_APP_URL??"https://atoms.tech"}}),_e={};async function fe(){const e=process.env.ATOMS_ACCESS_TOKEN;if(e){const t=he(e);return{access_token:e,refresh_token:"",user_id:t.sub,email:t.email??""}}const t=await re();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=he(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=i.createClient(de,ce),{data:n,error:a}=await r.auth.refreshSession({refresh_token:t.refresh_token});if(a||!n.session)throw new Error(`Token refresh failed: ${a?.message??"No session returned"}. Re-run 'npx @atoms-tech/atoms-mcp login'.`);const o=n.session;return await ie({access_token:o.access_token,refresh_token:o.refresh_token,expires_at:Date.now()+1e3*(o.expires_in??3600),user_email:o.user?.email}),{access_token:o.access_token,refresh_token:o.refresh_token,user_id:o.user?.id??"",email:o.user?.email??""}}function he(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}v(_e,{getValidToken:()=>fe});var ge,ye,we,be,ve,je=b({"src/auth/refresh.ts"(){me(),ue()}}),xe={};async function Se(){if(ge)try{const e=await re();e&&e.expires_at>ve&&(process.stderr.write("[atoms-mcp] Credentials file refreshed elsewhere — rebuilding client.\n"),ke())}catch{}if(ge&&be>Date.now()+6e4)return ge;ge&&process.stderr.write("[atoms-mcp] Token expiring, refreshing client...\n");const{access_token:e,user_id:t,email:r}=await fe(),n=e.split(".");if(3===n.length)try{const e=JSON.parse(Buffer.from(n[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString());be=1e3*(e.exp??0)}catch{be=Date.now()+36e5}try{const e=await re();ve=e?.expires_at??0}catch{ve=0}const a=i.createClient(de,ce,{global:{headers:{Authorization:`Bearer ${e}`}}});return ye=t,we=r,ge=a}function ke(){ge=null,ye=null,we=null,be=0,ve=0}function Ie(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 Ee(e){try{return await e()}catch(t){if(!Ie(t))throw t;return process.stderr.write("[atoms-mcp] Data tier rejected JWT — invalidating client and retrying once.\n"),ke(),await e()}}function Oe(){if(!ye)throw new Error("Not authenticated. Call getClient() first.");return ye}function Te(){return we??""}async function Ce(e,t){const r=Oe(),{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}v(xe,{getClient:()=>Se,getUserEmail:()=>Te,getUserId:()=>Oe,invalidateClient:()=>ke,isJwtExpiredError:()=>Ie,requireWriteAccess:()=>Ce,withJwtRetry:()=>Ee});var Ue,qe,De,He,Re,Ae=b({"src/db/client.ts"(){je(),me(),ue(),ge=null,ye=null,we=null,be=0,ve=0}}),ze={};function Pe(e,t=qe){if("string"!=typeof e)return"";const r=e.replace(He,"").replace(Re,"");return r.length<=t?r:r.slice(0,t)+"..."}function Me(e,t){return{status:"success",data:e,...t?{meta:t}:{}}}function $e(e,t,r){return{total_count:e,limit:t,offset:r,has_more:r+t<e}}function Ne(e,t){return{status:"error",message:e,next_steps:t}}function We(e,t){return Ne(`${e} '${Pe(t)}' not found`,[`Verify the ${e.toLowerCase()} ID is correct`,"Use atoms_list_items or atoms_search to find valid IDs"])}function Le(e){return Ne(`${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 Fe(e){return Ne(`Validation error: ${Pe(e,De)}`,["Check the parameter types and constraints in the tool description","Ensure all required parameters are provided"])}function Ve(){return Ne("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 Je(){return Ne("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 Be(e){return Ne("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 Qe(e){return Ne("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 Ge(e){return Ne(`Parse error: ${Pe(e,De)}`,["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 Ye(e){const t=e instanceof Error?e.message:String(e);return/not authenticated/i.test(t)?Ve():/jwt\s*expired|invalid.?jwt|token.*(?:has\s+)?expired|jwt.*invalid|pgrst301/i.test(t)?Je():/rate.?limit|too many requests|429/i.test(t)?Qe():/invalid input syntax|invalid json|unexpected token|malformed|json parse/i.test(t)?Ge(t):Ke(t)}function Ke(e){return/row.level security|permission denied for/i.test(e)?Ne("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"]):Ne(`Database error: ${Pe(e,De)}`,["This may be a temporary issue — try again","If the error persists, check that the project_id is valid"])}function Ze(e){return null==e?e:`<user_content>${e}</user_content>`}function Xe(e){const t=JSON.stringify(e,null,2);if(t.length>Ue&&"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 et(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}],structuredContent:e,isError:!0}}v(ze,{CHARACTER_LIMIT:()=>Ue,accessDeniedError:()=>Le,authError:()=>Ve,classifyError:()=>Ye,dbError:()=>Ke,errorResponse:()=>Ne,formatErrorResult:()=>et,formatToolResult:()=>Xe,jwtExpiredError:()=>Je,notFoundError:()=>We,paginationMeta:()=>$e,parseError:()=>Ge,rateLimitError:()=>Qe,sanitizeForEcho:()=>Pe,scopeError:()=>Be,success:()=>Me,validationError:()=>Fe,wrapUserContent:()=>Ze});var tt,rt=b({"src/tools/_base.ts"(){Ue=25e3,qe=50,De=200,He=new RegExp("[\\u0000-\\u001F\\u007F]","g"),Re=new RegExp("[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\uFEFF]","g")}});var it=b({"src/apps/register.ts"(){tt={resourceDomains:["fonts.googleapis.com","fonts.gstatic.com"]}}});async function nt(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 at(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 ot(e,t,r,i){const n=r.trim();if(!n)return{items:[],totalCount:0};let a=e.from("items").select("*",{count:"exact"}).eq("project_id",t).is("deleted_at",null).or(`title.ilike.%${n}%,data->>body.ilike.%${n}%,data->>summary.ilike.%${n}%`).limit(i.limit);i.type&&(a=a.eq("type",i.type));const{data:o,error:s,count:d}=await a;if(s)throw new Error(s.message);return{items:o??[],totalCount:d??0}}async function st(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 dt=b({"src/db/queries.ts"(){}}),ct={};async function lt(){try{const e=await Ee(async()=>{const e=await Se();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=F(),r=G().project_scope,i=r?new Set(r):null;return Xe(Me(e.filter(e=>!t||e.id===t).filter(e=>!i||i.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 et(Ye(e))}}v(ct,{listProjectsHandler:()=>lt});var mt=b({"src/tools/list-projects.ts"(){Ae(),dt(),B(),Y(),rt()}}),pt={};async function ut(e){try{const t=await Se(),{items:r,totalCount:i}=await nt(t,e.project_id,{type:e.type,domain:e.domain,level:e.level,limit:e.limit,offset:e.offset});return Xe(Me(r.map(e=>({id:e.id,title:Ze(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),$e(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(pt,{listItemsHandler:()=>ut});var _t=b({"src/tools/list-items.ts"(){Ae(),dt(),rt()}}),ft={};async function ht(e){try{const t=await Se(),r=await at(t,e.project_id,e.item_id);if(!r)return et(We("Item",e.item_id));const i=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),n={parents:[],children:[],related:[],verified_by:[],verifies:[]};for(const t of i)if(t.from_id===e.item_id)t.type in n&&n[t.type].push(t.to_id);else{const e={parent:"children",child:"parents",verified_by:"verifies",verifies:"verified_by",related:"related"}[t.type]??t.type;e in n&&n[e].push(t.from_id)}let a=[];if("test-case"===r.type){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 Xe(Me({id:r.id,type:r.type,title:Ze(r.title),summary:Ze(r.data?.summary),body:Ze(r.data?.body),tags:r.data?.tags??{domains:[]},ownership:r.data?.ownership??{primary:null,additional:[]},relationships:n,links:r.data?.links??[],status:r.data?.status,..."test-case"===r.type?{latest_result:a[0]?.result??"not-run",test_results:a}:{},metadata:{created_at:r.created_at,created_by:r.created_by,updated_at:r.updated_at,updated_by:r.updated_by,source_id:r.data?.metadata?.source_id}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(ft,{getItemHandler:()=>ht});var gt=b({"src/tools/get-item.ts"(){Ae(),dt(),rt()}}),yt={};async function wt(e){try{const t=await Se(),{items:r,totalCount:i}=await ot(t,e.project_id,e.query,{type:e.type,limit:e.limit});return Xe(Me(r.map(e=>({id:e.id,title:Ze(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),$e(i,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(yt,{searchHandler:()=>wt});var bt=b({"src/tools/search.ts"(){Ae(),dt(),rt()}}),vt={};async function jt(e){try{const t=await Se(),{items:r,totalCount:i}=await async function(e,t,r,i){const{ATOMS_SUPABASE_URL:n,ATOMS_SUPABASE_ANON_KEY:a}=await Promise.resolve().then(()=>(ue(),pe)),o=await fetch(`${n}/functions/v1/embed`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${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});return Xe(Me(r.map(e=>({id:e.id,title:Ze(e.title),type:e.type,status:e.data?.status,domains:e.data?.tags?.domains??[],level:e.data?.tags?.level})),$e(i,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(vt,{semanticSearchHandler:()=>jt});var xt=b({"src/tools/semantic-search.ts"(){Ae(),dt(),rt()}}),St={};async function kt(e){try{const t=await Se(),{covered:r,uncovered:i,total:n}=await async function(e,t,r){const{data:i,error:n}=await e.from("items").select("*").eq("project_id",t).eq("type","requirement").is("deleted_at",null);if(n)throw new Error(n.message);const a=(i??[]).filter(e=>!(r.domain&&!(e.data?.tags?.domains??[]).includes(r.domain)||r.level&&e.data?.tags?.level!==r.level)),{data:o,error:s}=await e.from("item_relationships").select("from_id").eq("project_id",t).eq("type","verified_by");if(s)throw new Error(s.message);const d=new Set((o??[]).map(e=>e.from_id));return{covered:a.filter(e=>d.has(e.id)),uncovered:a.filter(e=>!d.has(e.id)),total:a.length}}(t,e.project_id,{domain:e.domain,level:e.level}),a=i.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 Xe(Me({covered:r.length,uncovered:i.length,total:n,coverage_percent:n>0?Math.round(r.length/n*1e3)/10:100,uncovered_items:a}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(St,{getCoverageHandler:()=>kt});var It=b({"src/tools/get-coverage.ts"(){Ae(),dt(),rt()}}),Et={};async function Ot(e){try{const t=await Se(),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 Xe(Me(i,$e(i.length,e.limit,0)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(Et,{getHistoryHandler:()=>Ot});var Tt=b({"src/tools/get-history.ts"(){Ae(),dt(),rt()}}),Ct={};async function Ut(e){try{const t=await Se(),{data:r,error:i}=await t.from("items").select("id, title, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(i)throw new Error(i.message);if(!r||0===r.length)return Xe(Me({mermaid:"graph TD\n empty[No items in project]"}));const{data:n,error:a}=await t.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(a)throw new Error(a.message);const o=new Map;for(const e of r)o.set(e.id,e);const s=["graph TD"],d=new Set,c=new Set,l=(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 m;m=e.root_item_id?[e.root_item_id]:r.filter(e=>!l.has(e.id)).filter(t=>!(!e.include_tests&&"test-case"===t.type)).map(e=>e.id);const p=m.map(e=>({id:e,depth:0}));for(;p.length>0;){const{id:t,depth:r}=p.shift();if(d.has(t)||r>e.depth)continue;d.add(t);const i=o.get(t);if(!i)continue;if(!e.include_tests&&"test-case"===i.type)continue;const a=qt(i.title),l="requirement"===i.type?`["${a}"]`:"test-case"===i.type?`(["${a}"])`:`("${a}")`;s.push(` ${t}${l}`);for(const i of n??[]){if(i.from_id!==t)continue;const n=o.get(i.to_id);if(!n)continue;if(!e.include_tests&&"test-case"===n.type)continue;const a=`${i.from_id}-${i.type}-${i.to_id}`;c.has(a)||(c.add(a),"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}`),!d.has(i.to_id)&&r+1<=e.depth&&p.push({id:i.to_id,depth:r+1}))}}return Xe(Me({mermaid:s.join("\n"),node_count:d.size,edge_count:c.size}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}function qt(e){return e.replace(/"/g,"'").replace(/[[\]{}()]/g,"").substring(0,50)}v(Ct,{exportMermaidHandler:()=>Ut});var Dt=b({"src/tools/export-mermaid.ts"(){Ae(),rt()}}),Ht={};async function Rt(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}if(e.domains&&e.domains.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 t=e.domains.filter(e=>!i.includes(e));if(t.length>0)return et(Fe(`Unknown domain${t.length>1?"s":""}: ${t.join(", ")}. Registered: ${i.join(", ")}. Add new domains via Taxonomy Management in the ATOMS web app first.`))}}const i=await st(t,e.project_id,e.type),n=(new Date).toISOString(),a={id:i,type:e.type,title:e.title,summary:e.summary,body:e.body,tags:{domains:e.domains??[],level:e.level},ownership:{primary:null,additional:[]},relationships:{parents:e.parent_ids??[],children:[],related:[]},links:[],metadata:{created_at:n,created_by:r,updated_at:n,updated_by:r}},{error:o}=await t.from("items").insert({id:i,project_id:e.project_id,type:e.type,title:e.title,data:a,created_by:r,updated_by:r}).select().single();if(o)throw new Error(o.message);if(e.parent_ids&&e.parent_ids.length>0){const r=e.parent_ids.map(t=>({from_id:i,to_id:t,type:"parent",project_id:e.project_id}));await t.from("item_relationships").insert(r).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: relationship sync failed: ${e.message}\n`)})}return await t.from("change_history").insert({item_id:i,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:a,actor:"mcp_claude",session_id:ei()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),!1===e.echo?Xe(Me({id:i,type:e.type})):Xe(Me({id:i,type:e.type,title:e.title,summary:e.summary??"",body:e.body??"",domains:e.domains??[],level:e.level??"",project_id:e.project_id}))}catch(e){return et(Ye(e))}}v(Ht,{createItemHandler:()=>Rt});var At=b({"src/tools/create-item.ts"(){Ae(),dt(),ii(),rt()}}),zt={};async function Pt(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const i=async(i,n,a,o)=>{const s=await at(t,e.project_id,i);if(!s)return{updated_fields:[],error:`Item "${i}" not found`};if(void 0!==n.domains&&n.domains.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=n.domains.filter(e=>!i.includes(e));if(e.length>0)return{updated_fields:[],error:`Unknown domains: ${e.join(", ")}`}}}const d={...s.data},c={...s.data},l=[],m=(e,t)=>JSON.stringify(e)!==JSON.stringify(t);if(void 0!==n.title&&m(s.data.title,n.title)&&(c.title=n.title,l.push("title")),void 0!==n.body&&m(s.data.body,n.body)&&(c.body=n.body,l.push("body")),void 0!==n.summary&&m(s.data.summary,n.summary)&&(c.summary=n.summary,l.push("summary")),void 0!==n.domains&&m(s.data.tags?.domains??[],n.domains)&&(c.tags={...c.tags,domains:n.domains},l.push("domains")),void 0!==n.level&&m(s.data.tags?.level,n.level)&&(c.tags={...c.tags,level:n.level},l.push("level")),void 0!==n.status&&m(s.data.status,n.status)&&(c.status=n.status,l.push("status")),0===l.length)return{updated_fields:[],updatedData:s.data,itemType:s.type};c.metadata={...c.metadata,updated_at:(new Date).toISOString(),updated_by:r};const{data:p,error:u}=await t.from("items").update({title:n.title??s.title,data:c,updated_by:r}).eq("id",i).eq("project_id",e.project_id).select("id");if(u)return{updated_fields:[],error:u.message};if(!p||0===p.length)return{updated_fields:[],error:`Update silently failed for "${i}" — 0 rows affected. Likely RLS policy denied the write; verify your role and project access.`};const _={item_id:i,project_id:e.project_id,changed_by:r,event_type:"updated",old_data:d,new_data:c,actor:"mcp_claude",session_id:ei()};return a&&(_.batch_id=a),o&&(_.reason=o),await t.from("change_history").insert(_).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),{updated_fields:l,updatedData:c,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 et(Fe(`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 Xe(Me({batch_id:t,updated:n.length,results:!1===e.echo?void 0:n,errors:a}))}if(!e.item_id)return et(Fe("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},void 0,e.reason);if(s)return s.includes("not found")?et(We("Item",e.item_id)):s.includes("Unknown domain")?et(Fe(s)):et(Ke(s));if(!1===e.echo)return Xe(Me({id:e.item_id,updated_fields:n}));const d=a??{};return Xe(Me({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 et(Ye(e))}}v(zt,{updateItemHandler:()=>Pt});var Mt,$t,Nt=b({"src/tools/update-item.ts"(){Ae(),dt(),ii(),rt()}});function Wt(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!!G().require_confirmation_for.includes(e)}function Lt(e,t=Mt){const r=Date.now()+t,i={...e,exp:r},n=Buffer.from(JSON.stringify(i)).toString("base64url");return{token:`${n}.${g.default.createHmac("sha256",$t).update(n).digest("base64url")}`,expires_at:new Date(r).toISOString(),expires_in_seconds:Math.round(t/1e3)}}function Ft(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=g.default.createHmac("sha256",$t).update(r).digest("base64url"),a=Buffer.from(i,"utf8"),o=Buffer.from(n,"utf8");if(a.length!==o.length||!g.default.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 Vt(e){if(Array.isArray(e))return e.map(Vt);if(null!==e&&"object"==typeof e){const t={};for(const r of Object.keys(e).sort())t[r]=Vt(e[r]);return t}return e}function Jt(e){const t=JSON.stringify(Vt(e));return g.default.createHash("sha256").update(t).digest("base64url").slice(0,16)}var Bt,Qt=b({"src/middleware/confirmation.ts"(){Y(),Mt=6e4,$t=(()=>{const e=process.env.ATOMS_MCP_CONFIRMATION_SECRET;return e&&e.length>=16?e:g.default.randomBytes(32).toString("base64url")})()}}),Gt={};async function Yt(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const i=await at(t,e.project_id,e.item_id);if(!i)return et(We("Item",e.item_id));if(Wt(Bt)){const t={tool:Bt,project_id:e.project_id,target:e.item_id};if(!e.confirmation_token){const r=Lt(t);return Xe(Me({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=Ft(e.confirmation_token,t);if(!r.valid)return et(Fe(`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:"mcp_claude",session_id:ei()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Xe(Me({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 et(Ye(e))}}v(Gt,{deleteItemHandler:()=>Yt});var Kt=b({"src/tools/delete-item.ts"(){Ae(),dt(),ii(),Qt(),rt(),Bt="atoms_delete_item"}}),Zt={};async function Xt(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("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 et(We("Item",e.item_id));if(!i.deleted_at)return et(Ne(`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.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:"mcp_claude",session_id:ei()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Xe(Me({id:e.item_id,title:i.title,type:i.type,restored:!0,message:`Item ${e.item_id} restored successfully.`}))}catch(e){return et(Ye(e))}}v(Zt,{restoreItemHandler:()=>Xt});var er,tr,rr=b({"src/tools/restore-item.ts"(){Ae(),ii(),rt()}}),ir={};async function nr(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const i=async(i,n,a)=>{const o=await at(t,e.project_id,i.from_id);if(!o)throw new Error(`Item "${i.from_id}" not found`);const s=await at(t,e.project_id,i.to_id);if(!s)throw new Error(`Item "${i.to_id}" not found`);const d=er(i.type);if("add"===i.action){const{data:n}=await t.from("item_relationships").select("from_id").eq("from_id",i.from_id).eq("to_id",i.to_id).eq("type",i.type).eq("project_id",e.project_id).maybeSingle();if(!n){const{error:r}=await t.from("item_relationships").insert({from_id:i.from_id,to_id:i.to_id,type:i.type,project_id:e.project_id});if(r)throw new Error(r.message)}const a=JSON.parse(JSON.stringify(o.data));a.relationships=a.relationships??{};const c=a.relationships[tr(i.type)]??[];c.includes(i.to_id)||(a.relationships[tr(i.type)]=[...c,i.to_id]),await t.from("items").update({data:a,updated_by:r}).eq("id",i.from_id).eq("project_id",e.project_id);const l=JSON.parse(JSON.stringify(s.data));l.relationships=l.relationships??{};const m=l.relationships[tr(d)]??[];m.includes(i.from_id)||(l.relationships[tr(d)]=[...m,i.from_id]),await t.from("items").update({data:l,updated_by:r}).eq("id",i.to_id).eq("project_id",e.project_id)}else{await Promise.all([t.from("item_relationships").delete().eq("from_id",i.from_id).eq("to_id",i.to_id).eq("type",i.type).eq("project_id",e.project_id),t.from("item_relationships").delete().eq("from_id",i.to_id).eq("to_id",i.from_id).eq("type",d).eq("project_id",e.project_id)]);const n=JSON.parse(JSON.stringify(o.data));n.relationships?.[tr(i.type)]&&(n.relationships[tr(i.type)]=n.relationships[tr(i.type)].filter(e=>e!==i.to_id)),await t.from("items").update({data:n,updated_by:r}).eq("id",i.from_id).eq("project_id",e.project_id);const a=JSON.parse(JSON.stringify(s.data));a.relationships?.[tr(d)]&&(a.relationships[tr(d)]=a.relationships[tr(d)].filter(e=>e!==i.from_id)),await t.from("items").update({data:a,updated_by:r}).eq("id",i.to_id).eq("project_id",e.project_id)}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:"mcp_claude",session_id:ei()};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 et(Fe(`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 et(We("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=e=>{const t=new Set,r=[e];for(;r.length;){const e=r.pop();if(t.has(e))return e;t.add(e);for(const t of d.get(e)??[])r.push(t)}return null};for(const e of d.keys()){const t=c(e);if(t)return et(Fe(`Batch would create a cycle at item "${t}". No changes applied.`))}const l=crypto.randomUUID(),m=e.operations.filter(e=>"remove"===e.action),p=e.operations.filter(e=>"add"===e.action);for(const t of[...m,...p])await i(t,l,e.reason);return Xe(Me({batch_id:l,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?et(Fe("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},void 0,e.reason),!1===e.echo?Xe(Me({action:e.action,from_id:e.from_id,to_id:e.to_id})):Xe(Me({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}`}))):et(Fe("Provide (from_id, to_id, type, action) for single-op or operations[] for batch."))}catch(e){return et(Ye(e))}}v(ir,{linkItemsHandler:()=>nr});var ar=b({"src/tools/link-items.ts"(){Ae(),dt(),ii(),rt(),er=e=>"parent"===e?"child":"child"===e?"parent":"verifies"===e?"verified_by":"verified_by"===e?"verifies":"related",tr=e=>"parent"===e?"parents":"child"===e?"children":e}}),or={};async function sr(e){try{if(!e.items||0===e.items.length)return et(Fe("items array must contain at least 1 item"));if(e.items.length>100)return et(Fe(`Maximum 100 items per call (received ${e.items.length}). Split into multiple calls.`));const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}if(Wt("atoms_bulk_import")){const t={tool:"atoms_bulk_import",project_id:e.project_id,target:Jt(e.items)};if(!e.confirmation_token){const r=Lt(t),i={};for(const t of e.items)i[t.type]=(i[t.type]??0)+1;return Xe(Me({status:"confirmation_required",preview:{total_items:e.items.length,by_type:i,sample_titles:e.items.slice(0,5).map(e=>e.title)},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 will invalidate the token.`}))}const r=Ft(e.confirmation_token,t);if(!r.valid)return et(Fe(`confirmation_token rejected: ${r.reason}`))}const i=(new Date).toISOString(),n=ei(),a=[],o=[],s=[];for(let r=0;r<e.items.length;r++){const i=e.items[r];try{const n=await st(t,e.project_id,i.type);s.push({itemId:n,input:i,index:r})}catch(e){o.push({index:r,title:i.title,error:`ID generation failed: ${e instanceof Error?e.message:String(e)}`})}}if(0===s.length)return Xe(Me({created:0,items:[],errors:o}));const d=[],c=new Map;for(const{itemId:t,input:n}of s){const a={id:t,type:n.type,title:n.title,summary:n.summary,body:n.body,tags:{domains:n.domains??[],level:n.level},ownership:{primary:null,additional:[]},relationships:{parents:n.parent_id?[n.parent_id]:[],children:[],related:[]},links:[],metadata:{created_at:i,created_by:r,updated_at:i,updated_by:r}};c.set(t,a),d.push({id:t,project_id:e.project_id,type:n.type,title:n.title,data:a,created_by:r,updated_by:r})}const{error:l}=await t.from("items").insert(d);if(l)for(const{itemId:e,input:r,index:i}of s){const n=d.find(t=>t.id===e);if(!n)continue;const{error:s}=await t.from("items").insert(n);s?o.push({index:i,title:r.title,error:s.message}):a.push({id:e,title:r.title,type:r.type})}else for(const{itemId:e,input:t}of s)a.push({id:e,title:t.title,type:t.type});const m=s.filter(({input:e})=>e.parent_id).filter(({itemId:e})=>a.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}));m.length>0&&await t.from("item_relationships").insert(m).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: bulk relationship sync failed: ${e.message}\n`)});const p=a.map(t=>({item_id:t.id,project_id:e.project_id,changed_by:r,event_type:"created",old_data:null,new_data:c.get(t.id)??null,actor:"mcp_claude",session_id:n}));return p.length>0&&await t.from("change_history").insert(p).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: bulk change_history log failed: ${e.message}\n`)}),Xe(Me({created:a.length,items:a,errors:o.length>0?o:[],project_id:e.project_id}))}catch(e){return et(Ye(e))}}v(or,{bulkImportHandler:()=>sr});var dr=b({"src/tools/bulk-import.ts"(){Ae(),dt(),ii(),Qt(),rt()}}),cr={};async function lr(e){try{const t=await Se(),r=Oe(),i=Te();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const n=await at(t,e.project_id,e.item_id);if(!n)return et(We("Item",e.item_id));if("test-case"!==n.type)return et(Fe(`Item '${e.item_id}' is a ${n.type}, not a test-case`));const a=(new Date).toISOString(),{error:o}=await t.from("test_results").insert({item_id:e.item_id,project_id:e.project_id,result:e.result,run_by:i||r,run_at:a,note:e.note??null});if(o)throw new Error(o.message);return 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:a,note:e.note},actor:"mcp_claude",session_id:ei()}).then(({error:e})=>{e&&process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${e.message}\n`)}),Xe(Me({item_id:e.item_id,result:e.result,run_at:a,run_by:i||r,note:e.note??null,message:`Test result '${e.result}' recorded for ${e.item_id}`}))}catch(e){return et(Ye(e))}}v(cr,{recordTestResultHandler:()=>lr});var mr,pr,ur,_r,fr=b({"src/tools/record-test-result.ts"(){Ae(),dt(),ii(),rt()}}),hr={};async function gr(e){try{const t=Math.min(Math.max(e.depth??5,1),10),r=await Se(),i=await at(r,e.project_id,e.item_id);if(!i)return et(We("Item",e.item_id));const{data:n,error:a}=await r.from("item_relationships").select("from_id, to_id, type").eq("project_id",e.project_id);if(a)throw new Error(a.message);const{data:o,error:s}=await r.from("items").select("id, title, type").eq("project_id",e.project_id).is("deleted_at",null);if(s)throw new Error(s.message);const d=new Map;for(const e of o??[])d.set(e.id,{title:e.title,type:e.type});const c=new Map,l=(e,t,r)=>{c.has(e)||c.set(e,[]),c.get(e).push({neighbor:t,relType:r})},m=e.relationship_types?new Set(e.relationship_types):null;for(const t of n??[]){const r=t.type;if(("upstream"===e.direction||"both"===e.direction)&&(pr.includes(r)&&(m&&!m.has(r)||l(t.from_id,t.to_id,r)),ur.includes(r))){const e=_r[r]??r;m&&!m.has(e)||l(t.to_id,t.from_id,e)}if(("downstream"===e.direction||"both"===e.direction)&&(ur.includes(r)&&(m&&!m.has(r)||l(t.from_id,t.to_id,r)),pr.includes(r))){const e=_r[r]??r;m&&!m.has(e)||l(t.to_id,t.from_id,e)}"related"===r&&(m&&!m.has("related")||(l(t.from_id,t.to_id,"related"),l(t.to_id,t.from_id,"related")))}const p=new Set;p.add(e.item_id);const u=[],_=[],f=[],h=c.get(e.item_id)??[];for(const t of h)f.push({id:t.neighbor,depth:1,relType:t.relType,from:e.item_id});for(;f.length>0&&u.length<mr;){const{id:e,depth:r,relType:i,from:n}=f.shift();if(p.has(e)||r>t)continue;p.add(e);const a=d.get(e);if(a&&(u.push({id:e,title:a.title,type:a.type,relationship:i,depth:r,from:n}),_.push({from:n,to:e,type:i}),r<t)){const t=c.get(e)??[];for(const i of t)p.has(i.neighbor)||f.push({id:i.neighbor,depth:r+1,relType:i.relType,from:e})}}const g=d.get(e.item_id),y=g?{id:e.item_id,title:g.title,type:g.type,relationship:"root",depth:0,from:e.item_id}:{id:e.item_id,title:i.title??e.item_id,type:i.type??"unknown",relationship:"root",depth:0,from:e.item_id};return Xe(Me({root:e.item_id,direction:e.direction,depth_limit:t,items:[y,...u],edges:_,total_count:u.length+1,...u.length>=mr?{truncated:!0,truncation_message:`Results capped at ${mr} items. Use a smaller depth or filter by relationship_types.`}:{}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(hr,{traceHandler:()=>gr});var yr=b({"src/tools/trace.ts"(){Ae(),dt(),rt(),mr=200,pr=["parent","verifies"],ur=["child","verified_by"],_r={parent:"child",child:"parent",verifies:"verified_by",verified_by:"verifies",related:"related"}}}),wr={};async function br(e){try{const t=await Se(),{data:r,error:i}=await t.from("project_variables").select("*").eq("project_id",e.project_id).order("name");if(i)throw new Error(i.message);return Xe(Me({variables:r||[],total:(r||[]).length}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function vr(e){try{const t=await Se(),{data:r,error:i}=await t.from("project_variables").select("*").eq("project_id",e.project_id).eq("name",e.variable_name).maybeSingle();if(i)throw new Error(i.message);if(!r)return et(We("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 Xe(Me({variable:r,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")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function jr(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}if(!(await t.from("project_variables").select("*").eq("project_id",e.project_id).eq("name",e.variable_name).maybeSingle()).data)return et(We("Variable",e.variable_name));const i={updated_by:r,updated_at:(new Date).toISOString()};void 0!==e.value&&(i.value=e.value),void 0!==e.unit&&(i.unit=e.unit||null),void 0!==e.description&&(i.description=e.description||null);const{data:n,error:a}=await t.from("project_variables").update(i).eq("project_id",e.project_id).eq("name",e.variable_name).select().single();if(a)throw new Error(a.message);return Xe(Me({variable:n}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function xr(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("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 et(Fe(`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 et(Fe(`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 et(Fe(`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})),{data:l,error:m}=await t.from("project_variables").insert(c).select();if(m)throw new Error(m.message);return Xe(Me({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 et(Fe("Provide either (name, value) for single variable or variables[] for batch."));if(!i.test(e.name))return et(Fe(`Variable name "${e.name}" is invalid. Use letters, digits, and underscores only; must start with a letter or underscore.`));const{data:n,error:a}=await t.from("project_variables").insert({project_id:e.project_id,name:e.name,value:e.value,unit:e.unit??null,description:e.description??null,created_by:r,updated_by:r}).select().single();if(a){if(a.message.includes("unique")||a.message.includes("duplicate"))return et(Fe(`Variable "${e.name}" already exists in this project.`));throw new Error(a.message)}return!1===e.echo?Xe(Me({name:e.name})):Xe(Me({variable:n,message:`Variable "${e.name}" created. Reference it in requirement bodies as {${e.name}}.`}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function Sr(e){try{const t=await Se();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const{data:r}=await t.from("project_variables").select("id").eq("project_id",e.project_id).eq("name",e.name).maybeSingle();if(!r)return et(We("Variable",e.name));const{data:i}=await t.from("items").select("id").eq("project_id",e.project_id).is("deleted_at",null).filter("data->>body","ilike",`%{${e.name}}%`),n=(i??[]).length;if(n>0&&!e.force)return et(Fe(`Variable "${e.name}" is referenced in ${n} item${n>1?"s":""}. Use force=true to delete anyway, or remove all {${e.name}} references first.`));if(Wt("atoms_delete_variable")){const t={tool:"atoms_delete_variable",project_id:e.project_id,target:e.name};if(!e.confirmation_token){const r=Lt(t);return Xe(Me({status:"confirmation_required",preview:{variable_name:e.name,referenced_by_items:n,would_orphan_references:n>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=Ft(e.confirmation_token,t);if(!r.valid)return et(Fe(`confirmation_token rejected: ${r.reason}`))}const{error:a}=await t.from("project_variables").delete().eq("project_id",e.project_id).eq("name",e.name);if(a)throw new Error(a.message);return Xe(Me({deleted:!0,name:e.name,referenced_by_items:n,warning:n>0?`${n} item${n>1?"s":""} still contain {${e.name}} — those references are now unresolved.`:void 0}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(wr,{createVariableHandler:()=>xr,deleteVariableHandler:()=>Sr,getVariableHandler:()=>vr,listVariablesHandler:()=>br,updateVariableHandler:()=>jr});var kr=b({"src/tools/variables.ts"(){Ae(),Qt(),rt()}}),Ir={};async function Er(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 Or(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 Tr(e){try{const t=await Se(),{domains:r}=await Er(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 Xe(Me({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")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function Cr(e){try{const t=await Se();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const{domains:r,levels:i}=await Er(t,e.project_id),n=e.name.trim().toLowerCase();return n?r.includes(n)?et(Fe(`Domain "${n}" already exists in this project.`)):(await Or(t,e.project_id,[...r,n],i),Xe(Me({domain:{name:n},message:`Domain "${n}" registered. You can now tag items with domains=["${n}"].`}))):et(Fe("Domain name cannot be empty."))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function Ur(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const{domains:i,levels:n}=await Er(t,e.project_id),a=e.name.trim().toLowerCase(),o=e.new_name.trim().toLowerCase();if(!o)return et(Fe("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 et(We("Domain",a))}if(o!==a&&i.includes(o))return et(Fe(`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 Xe(Me({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;return m=s?i.map(e=>e===a?o:e):i.includes(o)?i:[...i,o],await Or(t,e.project_id,m,n),(new Date).toISOString(),await Promise.all((d??[]).map(i=>{const n=(i.data?.tags?.domains??[]).map(e=>e===a?o:e),s={...i.data,tags:{...i.data?.tags??{},domains:n}};return t.from("items").update({data:s,updated_by:r}).eq("id",i.id).eq("project_id",e.project_id)})),Xe(Me({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")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}async function qr(e){try{const t=await Se(),r=Oe();try{await Ce(t,e.project_id)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message)return et(Le("Viewer"));throw e}const{domains:i,levels:n}=await Er(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 et(We("Domain",a));if(c>0&&!e.force)return et(Fe(`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(Wt("atoms_delete_domain")){const t={tool:"atoms_delete_domain",project_id:e.project_id,target:a};if(!e.confirmation_token){const e=Lt(t);return Xe(Me({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=Ft(e.confirmation_token,t);if(!r.valid)return et(Fe(`confirmation_token rejected: ${r.reason}`))}const l=o?i.filter(e=>e!==a):i;return await Or(t,e.project_id,l,n),(new Date).toISOString(),c>0&&await Promise.all((s??[]).map(i=>{const n={...i.data,tags:{...i.data?.tags??{},domains:(i.data?.tags?.domains??[]).filter(e=>e!==a)}};return t.from("items").update({data:n,updated_by:r}).eq("id",i.id).eq("project_id",e.project_id)})),Xe(Me({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")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(Ir,{createDomainHandler:()=>Cr,deleteDomainHandler:()=>qr,listDomainsHandler:()=>Tr,updateDomainHandler:()=>Ur});var Dr=b({"src/tools/domains.ts"(){Ae(),Qt(),rt()}}),Hr={};async function Rr(e){try{const t=await Se(),{data:r,error:i}=await t.from("projects").select("id, name").eq("id",e.project_id).is("deleted_at",null).maybeSingle();if(i)throw new Error(i.message);if(!r)return et(We("Project",e.project_id));const{data:n,error:a}=await t.from("items").select("id, type, data").eq("project_id",e.project_id).is("deleted_at",null);if(a)throw new Error(a.message);const o=n??[],s={requirements:0,test_cases:0,notes:0},d=[],c=[];for(const e of o)if("requirement"===e.type){s.requirements++;const t=e.data?.tags?.domains??[];c.push({id:e.id,domains:t})}else"test-case"===e.type?(s.test_cases++,d.push(e.id)):"note"===e.type&&s.notes++;const l={passed:0,failed:0,blocked:0,not_run:0};if(d.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 d){const t=n.get(e);t&&"not-run"!==t?"passed"===t?l.passed++:"failed"===t?l.failed++:"blocked"===t?l.blocked++:l.not_run++:l.not_run++}}const{data:m,error:p}=await t.from("item_relationships").select("from_id").eq("project_id",e.project_id).eq("type","verified_by");if(p)throw new Error(p.message);const u=new Set((m??[]).map(e=>e.from_id)),_=c.filter(e=>u.has(e.id)).length,f=c.length,h={covered:_,uncovered:f-_,total:f,percent:f>0?Math.round(_/f*1e3)/10:100},g=new Map;for(const e of c){const t=e.domains.length>0?e.domains:["(untagged)"],r=u.has(e.id);for(const e of t){g.has(e)||g.set(e,{covered:0,total:0});const t=g.get(e);t.total++,r&&t.covered++}}const y=Array.from(g.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})),w=new Date;w.setDate(w.getDate()-7);const{count:b,error:v}=await t.from("change_history").select("id",{count:"exact",head:!0}).eq("project_id",e.project_id).gte("changed_at",w.toISOString());if(v)throw new Error(v.message);return Xe(Me({project_id:e.project_id,project_name:r.name,counts:s,test_status:l,coverage:h,coverage_by_domain:y,recent_changes:b??0,last_updated:(new Date).toISOString()}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(Hr,{projectSummaryHandler:()=>Rr});var Ar,zr=b({"src/tools/project-summary.ts"(){Ae(),rt()}}),Pr={};async function Mr(e){try{const t=await Se();let r,i;if(e.query&&e.query.trim().length>0){const n=await ot(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 nt(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 Xe(Me({items:r,filters:{domains:n.domains,levels:n.levels,types:Ar},project_id:e.project_id},$e(i,e.limit,e.offset)))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(Pr,{browseHandler:()=>Mr});var $r,Nr,Wr,Lr,Fr=b({"src/tools/browse.ts"(){Ae(),dt(),rt(),Ar=["requirement","test-case","note","table"]}}),Vr={};async function Jr(e){try{const t=e.direction??"downstream",r=null!=e.depth?Math.min(Math.max(e.depth,1),20):null,i=await Se(),n=await at(i,e.project_id,e.item_id);if(!n)return et(We("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||(Nr.includes(r)&&m(e.from_id,e.to_id,r),Wr.includes(r)&&m(e.to_id,e.from_id,Lr[r]??r)),"downstream"!==t&&"both"!==t||(Wr.includes(r)&&m(e.from_id,e.to_id,r),Nr.includes(r)&&m(e.to_id,e.from_id,Lr[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<$r;){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:Ze(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>=$r)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:Ze(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 Xe(Me({root:{item_id:e.item_id,title:Ze(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>=$r?{truncated:!0}:{}}}))}catch(e){return e instanceof Error&&e.message.includes("Not authenticated")?et(Ve()):et(Ke(e instanceof Error?e.message:String(e)))}}v(Vr,{impactAnalysisHandler:()=>Jr});var Br,Qr,Gr,Yr,Kr=b({"src/tools/impact-analysis.ts"(){Ae(),dt(),rt(),$r=200,Nr=["parent","verifies","traces_to","allocated_from","decomposed_from"],Wr=["child","verified_by","allocated_to","decomposes_to","traced_by"],Lr={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"}}});function Zr(e){M(e.toolName)&&function(e,t){const r=`ui://${t.toolName}/${t.htmlFile}`,i=function(e,t){const r=a.fileURLToPath("undefined"==typeof document?require("url").pathToFileURL(__filename).href:m&&"SCRIPT"===m.tagName.toUpperCase()&&m.src||new URL("index.cjs",document.baseURI).href),i=h.default.dirname(r);return h.default.resolve(i,"apps","src","apps",e,t)}(t.appName,t.htmlFile),o=[...tt.resourceDomains,...t.extraResourceDomains??[]];n.registerAppTool(e,t.toolName,{title:t.title,description:t.description,inputSchema:t.inputSchema,annotations:t.annotations,_meta:{ui:{resourceUri:r}}},t.handler),n.registerAppResource(e,`${t.title} UI`,r,{mimeType:n.RESOURCE_MIME_TYPE},async()=>{const e=await f.default.readFile(i,"utf-8");return{contents:[{uri:r,mimeType:n.RESOURCE_MIME_TYPE,text:e,_meta:{ui:{csp:{resourceDomains:o}}}}]}})}(Br,e)}function Xr(e,t){return async r=>{const i=function(e,t){if(I.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&&E.has(t))return{allowed:!1,reason:"read_only"};if(e.block_destructive&&O.has(t))return{allowed:!1,reason:"block_destructive"};if(e.block_bulk_import&&T.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"}}(G(),e);if(!i.allowed)return et(Ne(`Tool ${e} is blocked by org policy.`,[K(i.reason),"Contact your org admin to adjust the MCP access policy."]));try{!function(e){const t=F();if(!t)return;if("object"!=typeof e||null===e)return;const r=e.project_id;if("string"==typeof r&&r!==t)throw new W(t,r)}(r)}catch(e){if(e instanceof W)return et(Be(e.expectedProjectId));throw e}{const e=G(),t=r?.project_id;if(null!==e.project_scope&&"string"==typeof t&&!e.project_scope.includes(t))return et(Ne("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 Se(),n=Oe()}catch{return t(r)}const o=await async function(e,t){const r=parseInt(process.env.ATOMS_RATE_LIMIT_RPM??"",10)||S,i=new Date,n=new Date(i.getTime()-k);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())+k-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*k);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 et(Qe(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(),xe));await t(a,e)}catch(e){if(e instanceof Error&&"VIEWER_ROLE"===e.message){const{accessDeniedError:e}=await Promise.resolve().then(()=>(rt(),ze));return et(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:Gr,client_name:Yr};try{!function(e,t,r){const i=x(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 Se(),n,t)}catch{}}}}function ei(){return Gr}var ti,ri,ii=b({"src/server.ts"(){D(),H(),L(),B(),Y(),ee(),Ae(),rt(),it(),Br=new s.McpServer({name:"atoms-mcp-server",version:"0.5.0"}),Qr=(e,t,r)=>{if(M(e))return Br.registerTool(e,t,r)},Gr=o.randomUUID(),Yr=process.env.ATOMS_CLIENT_NAME??"unknown",Qr("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(()=>(me(),te)),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(()=>(je(),_e)),{email:t}=await e(),{invalidateClient:r}=await Promise.resolve().then(()=>(Ae(),xe));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)}]}}}),Qr("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}},Xr("atoms_list_projects",async()=>{const{listProjectsHandler:e}=await Promise.resolve().then(()=>(mt(),ct));return e()})),Qr("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:d.z.string().uuid("Must be a valid project UUID").describe("UUID of the project"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:d.z.string().max(64).optional().describe("Filter by domain tag (e.g., 'Safety', 'Performance')"),level:d.z.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),limit:d.z.number().int().min(1).max(200).default(50).describe("Max results (default 50, max 200)"),offset:d.z.number().int().min(0).default(0).describe("Pagination offset (default 0)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_list_items",async e=>{const{listItemsHandler:t}=await Promise.resolve().then(()=>(_t(),pt));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID (e.g., REQ-001)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_get_item",async e=>{const{getItemHandler:t}=await Promise.resolve().then(()=>(gt(),ft));return t(e)})),Qr("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 - 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")',inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),query:d.z.string().min(1).max(1e3).describe("Search text"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),limit:d.z.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_search",async e=>{const{searchHandler:t}=await Promise.resolve().then(()=>(bt(),yt));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),query:d.z.string().min(1).max(1e3).describe("Natural-language concept query"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),limit:d.z.number().int().min(1).max(50).default(10).describe("Max results (default 10, max 50)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_semantic_search",async e=>{const{semanticSearchHandler:t}=await Promise.resolve().then(()=>(xt(),vt));return t(e)})),Zr({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\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")',inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),domain:d.z.string().max(64).optional().describe("Filter by domain tag"),level:d.z.string().max(32).optional().describe("Filter by level")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Xr("atoms_get_coverage",async e=>{const{getCoverageHandler:t}=await Promise.resolve().then(()=>(It(),St));return t(e)})}),Qr("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_claude) 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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID"),limit:d.z.number().int().min(1).max(100).default(20).describe("Max entries (default 20)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_get_history",async e=>{const{getHistoryHandler:t}=await Promise.resolve().then(()=>(Tt(),Et));return t(e)})),Zr({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\nReturns:\n Mermaid graph string.",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),root_item_id:d.z.string().max(20).optional().describe("Root item ID to start from"),depth:d.z.number().int().min(1).max(10).default(3).describe("Max depth"),include_tests:d.z.boolean().default(!0).describe("Include test case links")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},extraResourceDomains:["cdn.jsdelivr.net","mermaid.ink"],handler:Xr("atoms_export_mermaid",async e=>{const{exportMermaidHandler:t}=await Promise.resolve().then(()=>(Dt(),Ct));return t(e)})}),Qr("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_claude'\n - Creates relationships if parent_ids provided",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the target project"),type:d.z.enum(["requirement","test-case","note"]).describe("Item type"),title:d.z.string().min(1).max(500).describe("Item title"),body:d.z.string().max(5e4).optional().describe("Rich text body"),summary:d.z.string().max(2e3).optional().describe("Brief summary"),domains:d.z.array(d.z.string().max(64)).max(20).optional().describe("Domain tags"),level:d.z.string().max(32).optional().describe("System|Subsystem|Component"),parent_ids:d.z.array(d.z.string().max(20)).max(50).optional().describe("Parent item IDs to link"),echo:d.z.boolean().optional().describe("false → lean {id,type} response, saves ~580 tokens")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Xr("atoms_create_item",async e=>{const{createItemHandler:t}=await Promise.resolve().then(()=>(At(),Ht));return t(e)})),Qr("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\nReturns:\n Updated item.",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).optional().describe("Item ID (single form)"),title:d.z.string().min(1).max(500).optional().describe("New title"),body:d.z.string().max(5e4).optional().describe("New body"),summary:d.z.string().max(2e3).optional().describe("New summary"),domains:d.z.array(d.z.string().max(64)).max(20).optional().describe("New domain tags"),level:d.z.string().max(32).optional().describe("New level"),status:d.z.enum(["passed","failed","blocked","not-run"]).optional().describe("Test case status"),echo:d.z.boolean().optional().describe("false → lean {id,updated_fields} response, saves ~580 tokens"),reason:d.z.string().max(500).optional().describe("Change rationale, persisted in audit history"),updates:d.z.array(d.z.object({item_id:d.z.string().min(1).max(20),title:d.z.string().min(1).max(500).optional(),body:d.z.string().max(5e4).optional(),summary:d.z.string().max(2e3).optional(),domains:d.z.array(d.z.string().max(64)).max(20).optional(),level:d.z.string().max(32).optional(),status:d.z.enum(["passed","failed","blocked","not-run"]).optional()})).optional().describe("Array form: update many items in one call"),idempotency_key:d.z.string().max(128).optional().describe("Safe retry key (24h TTL)"),continue_on_error:d.z.boolean().optional().describe("Allow partial success (default false = atomic)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_update_item",async e=>{const{updateItemHandler:t}=await Promise.resolve().then(()=>(Nt(),zt));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID to soft-delete"),confirmation_token:d.z.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}},Xr("atoms_delete_item",async e=>{const{deleteItemHandler:t}=await Promise.resolve().then(()=>(Kt(),Gt));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().max(256).describe("ID of the soft-deleted item to restore")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_restore_item",async e=>{const{restoreItemHandler:t}=await Promise.resolve().then(()=>(rr(),Zt));return t(e)})),Qr("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\nReturns:\n Updated relationship state for both items.",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),from_id:d.z.string().min(1).max(20).optional().describe("Source item ID (single form)"),to_id:d.z.string().min(1).max(20).optional().describe("Target item ID (single form)"),type:d.z.enum(["parent","child","related","verifies","verified_by"]).optional().describe("Relationship type"),action:d.z.enum(["add","remove"]).optional().describe("Add or remove"),echo:d.z.boolean().optional().describe("false → lean response"),reason:d.z.string().max(500).optional().describe("Change rationale, persisted in audit history"),operations:d.z.array(d.z.object({action:d.z.enum(["add","remove"]),from_id:d.z.string().min(1).max(20),to_id:d.z.string().min(1).max(20),type:d.z.enum(["parent","child","related","verifies","verified_by"])})).optional().describe("Array form: batch relationship ops. Always atomic with DAG cycle detection."),idempotency_key:d.z.string().max(128).optional().describe("Safe retry key (24h TTL)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_link_items",async e=>{const{linkItemsHandler:t}=await Promise.resolve().then(()=>(ar(),ir));return t(e)})),Zr({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_claude' for each created item\n - Creates parent relationships for items with parent_id",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the target project"),items:d.z.array(d.z.object({type:d.z.enum(["requirement","test-case","note"]).describe("Item type"),title:d.z.string().min(1).max(500).describe("Item title"),body:d.z.string().max(5e4).optional().describe("Rich text body"),summary:d.z.string().max(2e3).optional().describe("Brief summary"),domains:d.z.array(d.z.string().max(64)).max(20).optional().describe("Domain tags"),level:d.z.string().max(32).optional().describe("System|Subsystem|Component"),parent_id:d.z.string().max(20).optional().describe("Parent item ID to link")})).min(1).max(100).describe("Items to create (1-100)"),confirmation_token:d.z.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:Xr("atoms_bulk_import",async e=>{const{bulkImportHandler:t}=await Promise.resolve().then(()=>(dr(),or));return t(e)})}),Qr("atoms_record_test_result",{title:"Record Test Result",description:"Record a pass/fail/blocked result for a test case.\n\nAppends to the test results history. 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\nReturns:\n Recorded result with timestamp.\n\nSide effects:\n - Appends to test_results table\n - Logs to change_history with actor='mcp_claude'",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Test case ID (e.g., TC-001)"),result:d.z.enum(["passed","failed","blocked","not-run"]).describe("Test result"),note:d.z.string().max(1e3).optional().describe("Reason or comment for this result")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Xr("atoms_record_test_result",async e=>{const{recordTestResultHandler:t}=await Promise.resolve().then(()=>(fr(),cr));return t(e)})),Zr({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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Starting item ID"),direction:d.z.enum(["upstream","downstream","both"]).describe("Traversal direction"),depth:d.z.number().int().min(1).max(10).default(5).describe("Max traversal depth (default 5)"),relationship_types:d.z.array(d.z.enum(["parent","child","related","verifies","verified_by"])).optional().describe("Filter to specific relationship types")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Xr("atoms_trace",async e=>{const{traceHandler:t}=await Promise.resolve().then(()=>(yr(),hr));return t(e)})}),Qr("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\nReturns:\n { variables: [{ id, name, value, unit, description }], total }",inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_list_variables",async e=>{const{listVariablesHandler:t}=await Promise.resolve().then(()=>(kr(),wr));return t(e)})),Qr("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\nReturns:\n { variable, referenced_by: [{ id, title, type }], reference_count }',inputSchema:{project_id:d.z.string().uuid().describe("UUID of the project"),variable_name:d.z.string().min(1).max(64).describe("Variable name")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_get_variable",async e=>{const{getVariableHandler:t}=await Promise.resolve().then(()=>(kr(),wr));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),variable_name:d.z.string().min(1).max(64).describe("Variable name"),value:d.z.string().max(500).optional().describe("New value"),unit:d.z.string().max(32).optional().describe("Unit abbreviation"),description:d.z.string().max(2e3).optional().describe("Description")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_update_variable",async e=>{const{updateVariableHandler:t}=await Promise.resolve().then(()=>(kr(),wr));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).optional().describe("Variable name (single form)"),value:d.z.string().min(1).max(500).optional().describe("Initial value (single form)"),unit:d.z.string().max(32).optional().describe("Unit abbreviation"),description:d.z.string().max(2e3).optional().describe("Description"),echo:d.z.boolean().optional().describe("false → lean response (default true)"),variables:d.z.array(d.z.object({name:d.z.string().min(1).max(64).describe("Variable name"),value:d.z.string().min(1).max(500).describe("Initial value"),unit:d.z.string().max(32).optional().describe("Unit abbreviation"),description:d.z.string().max(2e3).optional().describe("Description")})).optional().describe("Array form: batch variable creation. Atomic — all or none."),idempotency_key:d.z.string().max(128).optional().describe("Dedup key for retries")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Xr("atoms_create_variable",async e=>{const{createVariableHandler:t}=await Promise.resolve().then(()=>(kr(),wr));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Variable name to delete"),force:d.z.boolean().optional().describe("Delete even if items reference it (default false)"),confirmation_token:d.z.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}},Xr("atoms_delete_variable",async e=>{const{deleteVariableHandler:t}=await Promise.resolve().then(()=>(kr(),wr));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_list_domains",async e=>{const{listDomainsHandler:t}=await Promise.resolve().then(()=>(Dr(),Ir));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Domain name")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Xr("atoms_create_domain",async e=>{const{createDomainHandler:t}=await Promise.resolve().then(()=>(Dr(),Ir));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Current domain name"),new_name:d.z.string().min(1).max(64).describe("New domain name"),dry_run:d.z.boolean().optional().describe("Preview impact without applying (default false)")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1}},Xr("atoms_update_domain",async e=>{const{updateDomainHandler:t}=await Promise.resolve().then(()=>(Dr(),Ir));return t(e)})),Qr("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:d.z.string().uuid().describe("UUID of the project"),name:d.z.string().min(1).max(64).describe("Domain name to delete"),force:d.z.boolean().optional().describe("Strip from all items and delete (default false)"),confirmation_token:d.z.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}},Xr("atoms_delete_domain",async e=>{const{deleteDomainHandler:t}=await Promise.resolve().then(()=>(Dr(),Ir));return t(e)})),Zr({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:d.z.string().uuid().describe("UUID of the project")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Xr("atoms_project_summary",async e=>{const{projectSummaryHandler:t}=await Promise.resolve().then(()=>(zr(),Hr));return t(e)})}),Zr({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:d.z.string().uuid().describe("UUID of the project"),type:d.z.enum(["requirement","test-case","note","table"]).optional().describe("Filter by item type"),domain:d.z.string().max(64).optional().describe("Filter by domain tag"),level:d.z.string().max(32).optional().describe("Filter by level (System, Subsystem, Component)"),query:d.z.string().max(1e3).optional().describe("Full-text search query"),limit:d.z.number().int().min(1).max(100).default(25).describe("Max results (default 25, max 100)"),offset:d.z.number().int().min(0).default(0).describe("Pagination offset (default 0)")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},handler:Xr("atoms_browse",async e=>{const{browseHandler:t}=await Promise.resolve().then(()=>(Fr(),Pr));return t(e)})}),Qr("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:d.z.string().uuid().describe("UUID of the project"),item_id:d.z.string().min(1).max(20).describe("Item ID to analyze (e.g., REQ-001)"),direction:d.z.enum(["downstream","upstream","both"]).default("downstream").describe("Traversal direction (default: downstream)"),depth:d.z.number().int().min(1).optional().describe("Max traversal depth. Omit for full depth."),include_variable_refs:d.z.boolean().default(!0).describe("Reserved for variable-aware impact (future). Default true.")},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},Xr("atoms_impact_analysis",async e=>{const{impactAnalysisHandler:t}=await Promise.resolve().then(()=>(Kr(),Vr));return t(e)}))}}),ni={};async function ai(){const e=o.randomBytes(32).toString("base64url"),t=o.createHash("sha256").update(e).digest("base64url"),r=`http://localhost:${ti}${ri}`,i=new a.URL(`${le}/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 o(){clearTimeout(n)}const s=c.createServer(async(i,n)=>{const d=new a.URL(i.url??"/",`http://localhost:${ti}`);if(d.pathname!==ri)return n.writeHead(404),void n.end("Not found");try{if("access_denied"===d.searchParams.get("error"))return n.writeHead(200,{"Content-Type":"text/html"}),n.end(si("Authorization was cancelled.")),o(),s.close(),void r(new Error("Authorization cancelled by user."));const i=d.searchParams.get("code");if(i){const a=await fetch(`${de}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{"Content-Type":"application/json",apikey:ce},body:JSON.stringify({auth_code:i,code_verifier:e})}),d=await a.json();if(!a.ok||!d.access_token){const e=d.error_description??d.msg??"Unknown error";return n.writeHead(200,{"Content-Type":"text/html"}),n.end(si(`Code exchange failed: ${e}`)),o(),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(de,ce,{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(si('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.')),o(),s.close(),void r(new Error("No ATOMS account found. Sign up at atoms.tech first."))):(await ie({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(oi(c)),o(),s.close(),void t({email:c}))}const a=d.searchParams.get("access_token"),c=d.searchParams.get("refresh_token"),l=parseInt(d.searchParams.get("expires_in")??"3600",10);a&&c?(await ie({access_token:a,refresh_token:c,expires_at:Date.now()+1e3*l}),n.writeHead(200,{"Content-Type":"text/html"}),n.end(oi("authenticated")),o(),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"),o(),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 ie({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})),o(),s.close(),t({email:e.user_email??"authenticated"})}catch(e){i.writeHead(500),i.end("Failed to store credentials"),o(),s.close(),r(e)}})}}),s.listen(ti,"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 oi(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 si(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>`}v(ni,{login:()=>ai});var di=b({"src/auth/login.ts"(){me(),ue(),ti=19275,ri="/callback"}});ii(),L(),Y(),Ae(),Q();var ci=process.argv.slice(2)[0];(async function(){switch(ci){case"login":{const{login:e}=await Promise.resolve().then(()=>(di(),ni));try{const{email:t}=await e();process.stderr.write(`\n [32m✓[0m Authenticated as ${t}\n`),process.stderr.write(" [32m✓[0m Credentials saved to ~/.atoms/credentials.json\n\n"),process.stderr.write(" [1mNext steps:[0m\n"),process.stderr.write(" [36m$[0m 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(' [90m{\n "mcpServers": {\n "atoms": {\n "command": "npx",\n "args": ["-y", "@atoms-tech/atoms-mcp"]\n }\n }\n }[0m\n\n')}catch(e){process.stderr.write(`\n [31m✗[0m ${e instanceof Error?e.message:String(e)}\n\n`),process.exit(1)}break}case"whoami":{const{getValidToken:e}=await Promise.resolve().then(()=>(je(),_e));try{const{email:t,user_id:r}=await e();process.stderr.write(`\n [32m✓[0m Logged in as ${t}\n [90mUser ID: ${r}[0m\n\n`)}catch(e){process.stderr.write("\n [31m✗[0m 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(()=>(me(),te));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 v0.4.0\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(()=>(je(),_e)),{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(()=>(di(),ni)),{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`)}}process.stderr.write(`[atoms-mcp] Tool registry: ${function(){const e=P(),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`);try{const t=await Se(),r=Oe(),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:V};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:V}):function(e){if(!e||"object"!=typeof e)return{role:null,is_member:!1,effective:V};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,V.read_only),lockdown:o(n.lockdown,V.lockdown),block_destructive:o(n.block_destructive,V.block_destructive),block_bulk_import:o(n.block_bulk_import,V.block_bulk_import),cross_project_browse:o(n.cross_project_browse,V.cross_project_browse),allowed_toolsets:a(n.allowed_toolsets,V.allowed_toolsets),forbidden_tools:a(n.forbidden_tools,V.forbidden_tools),project_scope:d,require_confirmation_for:a(n.require_confirmation_for,V.require_confirmation_for)}}}(i)}(t,r,i);e={effective:n.effective,role:n.role,org_id:i},J=e.effective,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`)}const t=new l.StdioServerTransport;await Br.connect(t),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)});
|