af360 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/bin/af360.mjs +38 -5
  2. package/dist/.next/BUILD_ID +1 -1
  3. package/dist/.next/app-build-manifest.json +136 -138
  4. package/dist/.next/app-path-routes-manifest.json +20 -20
  5. package/dist/.next/build-manifest.json +5 -5
  6. package/dist/.next/prerender-manifest.json +46 -46
  7. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/dist/.next/server/app/_not-found.html +1 -1
  9. package/dist/.next/server/app/_not-found.rsc +3 -3
  10. package/dist/.next/server/app/ahead/page.js +1 -1
  11. package/dist/.next/server/app/ahead/page_client-reference-manifest.js +1 -1
  12. package/dist/.next/server/app/ahead.html +1 -1
  13. package/dist/.next/server/app/ahead.rsc +4 -4
  14. package/dist/.next/server/app/api/browse/route_client-reference-manifest.js +1 -1
  15. package/dist/.next/server/app/api/card/route.js +1 -1
  16. package/dist/.next/server/app/api/card/route.js.nft.json +1 -1
  17. package/dist/.next/server/app/api/card/route_client-reference-manifest.js +1 -1
  18. package/dist/.next/server/app/api/check/route_client-reference-manifest.js +1 -1
  19. package/dist/.next/server/app/api/config/route_client-reference-manifest.js +1 -1
  20. package/dist/.next/server/app/api/connections/route_client-reference-manifest.js +1 -1
  21. package/dist/.next/server/app/api/connectors/github/route_client-reference-manifest.js +1 -1
  22. package/dist/.next/server/app/api/consent/route_client-reference-manifest.js +1 -1
  23. package/dist/.next/server/app/api/context/route_client-reference-manifest.js +1 -1
  24. package/dist/.next/server/app/api/discover/route_client-reference-manifest.js +1 -1
  25. package/dist/.next/server/app/api/expiry/route_client-reference-manifest.js +1 -1
  26. package/dist/.next/server/app/api/first-light/route_client-reference-manifest.js +1 -1
  27. package/dist/.next/server/app/api/handoff/route_client-reference-manifest.js +1 -1
  28. package/dist/.next/server/app/api/health/route.js +1 -1
  29. package/dist/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  30. package/dist/.next/server/app/api/incidents/route_client-reference-manifest.js +1 -1
  31. package/dist/.next/server/app/api/insights/route.js +1 -1
  32. package/dist/.next/server/app/api/insights/route.js.nft.json +1 -1
  33. package/dist/.next/server/app/api/insights/route_client-reference-manifest.js +1 -1
  34. package/dist/.next/server/app/api/log/route.js +1 -1
  35. package/dist/.next/server/app/api/log/route_client-reference-manifest.js +1 -1
  36. package/dist/.next/server/app/api/pulse/route_client-reference-manifest.js +1 -1
  37. package/dist/.next/server/app/api/radar/route.js +1 -1
  38. package/dist/.next/server/app/api/radar/route_client-reference-manifest.js +1 -1
  39. package/dist/.next/server/app/api/report/route.js +1 -1
  40. package/dist/.next/server/app/api/report/route.js.nft.json +1 -1
  41. package/dist/.next/server/app/api/report/route_client-reference-manifest.js +1 -1
  42. package/dist/.next/server/app/api/services/route_client-reference-manifest.js +1 -1
  43. package/dist/.next/server/app/api/status/route.js +1 -1
  44. package/dist/.next/server/app/api/status/route.js.nft.json +1 -1
  45. package/dist/.next/server/app/api/status/route_client-reference-manifest.js +1 -1
  46. package/dist/.next/server/app/connections/page.js +2 -2
  47. package/dist/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  48. package/dist/.next/server/app/connections.html +1 -1
  49. package/dist/.next/server/app/connections.rsc +4 -4
  50. package/dist/.next/server/app/dashboard/page_client-reference-manifest.js +1 -1
  51. package/dist/.next/server/app/dashboard.html +1 -1
  52. package/dist/.next/server/app/dashboard.rsc +3 -3
  53. package/dist/.next/server/app/discoveries/page.js +1 -1
  54. package/dist/.next/server/app/discoveries/page_client-reference-manifest.js +1 -1
  55. package/dist/.next/server/app/discoveries.html +1 -1
  56. package/dist/.next/server/app/discoveries.rsc +4 -4
  57. package/dist/.next/server/app/expiry/page_client-reference-manifest.js +1 -1
  58. package/dist/.next/server/app/expiry.html +1 -1
  59. package/dist/.next/server/app/expiry.rsc +3 -3
  60. package/dist/.next/server/app/fleet/page_client-reference-manifest.js +1 -1
  61. package/dist/.next/server/app/fleet.html +1 -1
  62. package/dist/.next/server/app/fleet.rsc +3 -3
  63. package/dist/.next/server/app/home/page_client-reference-manifest.js +1 -1
  64. package/dist/.next/server/app/home.html +1 -1
  65. package/dist/.next/server/app/home.rsc +3 -3
  66. package/dist/.next/server/app/legal/privacy/page_client-reference-manifest.js +1 -1
  67. package/dist/.next/server/app/legal/terms/page_client-reference-manifest.js +1 -1
  68. package/dist/.next/server/app/log/page.js +1 -1
  69. package/dist/.next/server/app/log/page_client-reference-manifest.js +1 -1
  70. package/dist/.next/server/app/log.html +1 -1
  71. package/dist/.next/server/app/log.rsc +4 -4
  72. package/dist/.next/server/app/page_client-reference-manifest.js +1 -1
  73. package/dist/.next/server/app/projects/page.js +1 -1
  74. package/dist/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  75. package/dist/.next/server/app/projects.html +1 -1
  76. package/dist/.next/server/app/projects.rsc +4 -4
  77. package/dist/.next/server/app/radar/page.js +1 -64
  78. package/dist/.next/server/app/radar/page.js.nft.json +1 -1
  79. package/dist/.next/server/app/radar/page_client-reference-manifest.js +1 -1
  80. package/dist/.next/server/app/radar.html +1 -1
  81. package/dist/.next/server/app/radar.rsc +4 -4
  82. package/dist/.next/server/app/report/page.js +2 -2
  83. package/dist/.next/server/app/report/page.js.nft.json +1 -1
  84. package/dist/.next/server/app/report/page_client-reference-manifest.js +1 -1
  85. package/dist/.next/server/app/report.html +1 -1
  86. package/dist/.next/server/app/report.rsc +4 -4
  87. package/dist/.next/server/app/settings/page.js +1 -1
  88. package/dist/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  89. package/dist/.next/server/app/settings.html +1 -1
  90. package/dist/.next/server/app/settings.rsc +4 -4
  91. package/dist/.next/server/app/sites/page.js +1 -1
  92. package/dist/.next/server/app/sites/page_client-reference-manifest.js +1 -1
  93. package/dist/.next/server/app/sites.html +1 -1
  94. package/dist/.next/server/app/sites.rsc +4 -4
  95. package/dist/.next/server/app/welcome/page_client-reference-manifest.js +1 -1
  96. package/dist/.next/server/app/welcome.html +1 -1
  97. package/dist/.next/server/app/welcome.rsc +4 -4
  98. package/dist/.next/server/app-paths-manifest.json +20 -20
  99. package/dist/.next/server/chunks/2346.js +1 -1
  100. package/dist/.next/server/chunks/2465.js +64 -1
  101. package/dist/.next/server/chunks/2817.js +1 -1
  102. package/dist/.next/server/chunks/4825.js +1 -0
  103. package/dist/.next/server/chunks/572.js +1 -1
  104. package/dist/.next/server/chunks/6992.js +1 -1
  105. package/dist/.next/server/chunks/7511.js +1 -1
  106. package/dist/.next/server/chunks/7719.js +1 -1
  107. package/dist/.next/server/chunks/8134.js +1 -1
  108. package/dist/.next/server/chunks/8150.js +1 -1
  109. package/dist/.next/server/chunks/876.js +1 -1
  110. package/dist/.next/server/chunks/9170.js +1 -1
  111. package/dist/.next/server/middleware-build-manifest.js +1 -1
  112. package/dist/.next/server/middleware-manifest.json +5 -5
  113. package/dist/.next/server/pages/404.html +1 -1
  114. package/dist/.next/server/pages/500.html +1 -1
  115. package/dist/.next/server/server-reference-manifest.json +1 -1
  116. package/dist/.next/static/chunks/{1255-612df69cb02f2021.js → 1255-fc6c00ab2f765339.js} +1 -1
  117. package/dist/.next/static/chunks/3192-40ade9877c89ec45.js +1 -0
  118. package/dist/.next/static/chunks/5623-53fb7cea5de0afef.js +1 -0
  119. package/dist/.next/static/chunks/7897-b9c15e8805df61ef.js +1 -0
  120. package/dist/.next/static/chunks/8050-a75457e83cd8be0f.js +1 -0
  121. package/dist/.next/static/chunks/app/ahead/page-75602559f92be6c4.js +1 -0
  122. package/dist/.next/static/chunks/app/connections/page-ff9d0b4e4fcf5ebe.js +1 -0
  123. package/dist/.next/static/chunks/app/discoveries/{page-52696e6ee0c8d3d5.js → page-6b06c0c2a94f3b94.js} +1 -1
  124. package/dist/.next/static/chunks/app/{error-37987420de14c10c.js → error-c67c297ef2dd70de.js} +1 -1
  125. package/dist/.next/static/chunks/app/log/page-842048c36f071dd6.js +1 -0
  126. package/dist/.next/static/chunks/app/projects/{page-75a41d4a90675d93.js → page-1d6b330842a2ff80.js} +1 -1
  127. package/dist/.next/static/chunks/app/radar/page-584b42327baac21b.js +1 -0
  128. package/dist/.next/static/chunks/app/report/page-9dc52d7f76569e42.js +1 -0
  129. package/dist/.next/static/chunks/app/settings/{page-a38c457b7c57567b.js → page-582279bf3fe509d4.js} +1 -1
  130. package/dist/.next/static/chunks/app/sites/page-3dcc5a0990fae677.js +1 -0
  131. package/dist/.next/static/chunks/{main-6828c74376f2b965.js → main-574ae5bf2f2165c4.js} +1 -1
  132. package/dist/.next/static/css/fbb4cf81dde34731.css +1 -0
  133. package/dist/build-id.json +145 -147
  134. package/dist/node_modules/@next/env/package.json +1 -1
  135. package/dist/node_modules/next/dist/build/swc/index.js +1 -1
  136. package/dist/node_modules/next/dist/build/webpack-config.js +2 -2
  137. package/dist/node_modules/next/dist/compiled/next-server/app-page-experimental.runtime.prod.js +2 -2
  138. package/dist/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +2 -2
  139. package/dist/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +2 -2
  140. package/dist/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js +2 -2
  141. package/dist/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js +1 -1
  142. package/dist/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +7 -7
  143. package/dist/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js +2 -2
  144. package/dist/node_modules/next/dist/lib/has-necessary-dependencies.js +2 -0
  145. package/dist/node_modules/next/dist/lib/verify-typescript-setup.js +19 -0
  146. package/dist/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +1 -1
  147. package/dist/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  148. package/dist/node_modules/next/dist/server/image-optimizer.js +22 -2
  149. package/dist/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  150. package/dist/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +17 -4
  151. package/dist/node_modules/next/dist/server/lib/start-server.js +1 -1
  152. package/dist/node_modules/next/dist/shared/lib/canary-only.js +1 -1
  153. package/dist/node_modules/next/dist/shared/lib/router/utils/escape-path-delimiters.js +1 -1
  154. package/dist/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  155. package/dist/node_modules/next/dist/telemetry/events/version.js +2 -2
  156. package/dist/node_modules/next/package.json +11 -11
  157. package/dist/package.json +2 -2
  158. package/dist/shell/bin/af360-bridge.exe +0 -0
  159. package/lib/mcpActivity.mjs +1 -0
  160. package/package.json +2 -2
  161. package/scripts/mcp-server.mjs +174 -2
  162. package/shell/bin/af360-bridge.exe +0 -0
  163. package/dist/.next/server/chunks/1155.js +0 -1
  164. package/dist/.next/server/chunks/6584.js +0 -1
  165. package/dist/.next/static/chunks/1681-b233404f9ec2fc2e.js +0 -1
  166. package/dist/.next/static/chunks/2036-e29441d605c493a4.js +0 -1
  167. package/dist/.next/static/chunks/3192-6b341d547a49fa85.js +0 -1
  168. package/dist/.next/static/chunks/5623-ec4f1a6f7695d8d1.js +0 -1
  169. package/dist/.next/static/chunks/7897-6e6c509aea495560.js +0 -1
  170. package/dist/.next/static/chunks/app/ahead/page-e72bed448dd40bd5.js +0 -1
  171. package/dist/.next/static/chunks/app/connections/page-5d6cae02e6f2b49a.js +0 -1
  172. package/dist/.next/static/chunks/app/log/page-45b4cfd8f1a29d09.js +0 -1
  173. package/dist/.next/static/chunks/app/radar/page-4b4aad549dffcd3a.js +0 -1
  174. package/dist/.next/static/chunks/app/report/page-145496e8f0d5bc86.js +0 -1
  175. package/dist/.next/static/chunks/app/sites/page-96a7dcf3c1e9aa19.js +0 -1
  176. package/dist/.next/static/css/94d302c686de7e23.css +0 -1
  177. /package/dist/.next/static/{da1c2e66999c334d135d9 → 4602553707d90d0fadeba}/_buildManifest.js +0 -0
  178. /package/dist/.next/static/{da1c2e66999c334d135d9 → 4602553707d90d0fadeba}/_ssgManifest.js +0 -0
@@ -1 +0,0 @@
1
- "use strict";exports.id=1155,exports.ids=[1155,6988],exports.modules={23854:(a,b,c)=>{c.d(b,{EH:()=>p,EZ:()=>r,Lr:()=>k,Vk:()=>q,XK:()=>u,aI:()=>t,iS:()=>n,wq:()=>o});var d=c(29021),e=c(35060),f=c(7719),g=c(33873),h=c.n(g);let i=e.EO;async function j(a,b,c){try{let d=i("git",(0,e.tQ)(b,a),{cwd:a,timeout:8e3,windowsHide:!0,maxBuffer:8e6,env:(0,e.tl)()});if(void 0!==c){let a=d.child;a.stdin?.end(c)}let{stdout:f}=await d;return f}catch(a){if("number"==typeof a.code&&1===a.code)return a.stdout??"";return null}}async function k(a){let b={name:a.name,path:a.path,read:!1,total:0,noUpstream:[]};if(!a.isGit)return{...b,read:!0};let c=await j(a.path,["for-each-ref","--format=%(refname:short)%09%(upstream)","refs/heads"]);if(null===c)return b;let d=c.split("\n").map(a=>a.replace(/\r$/,"")).filter(a=>a.length>0),e=[];for(let a of d){let[b,c]=a.split(" ");b&&((c??"").trim()||e.push(b))}return{name:a.name,path:a.path,read:!0,total:d.length,noUpstream:e}}let l=[{rel:".env",kind:"environment file"},{rel:".env.local",kind:"environment file"},{rel:".env.production",kind:"environment file"},{rel:".env.development",kind:"environment file"},{rel:"uploads",kind:"uploads folder"},{rel:"data",kind:"data folder"},{rel:"storage",kind:"storage folder"}],m=new Set([".sqlite",".sqlite3",".db"]);async function n(a){let b,c={name:a.name,path:a.path,read:!1,found:[],lookedFor:"environment files, local databases, and uploads, data and storage folders"};if(!a.isGit)return{...c,read:!0};try{b=await d.promises.readdir(a.path,{withFileTypes:!0})}catch{return c}let e=[];for(let b of l)try{await d.promises.stat(h().join(a.path,b.rel)),e.push(b)}catch{}for(let a of b)a.isFile()&&m.has(h().extname(a.name).toLowerCase())&&e.push({rel:a.name,kind:"local database"});if(0===e.length)return{...c,read:!0};let f=await j(a.path,["check-ignore","--stdin"],e.map(a=>a.rel).join("\n")+"\n");if(null===f)return c;let g=new Set(f.split("\n").map(a=>a.replace(/\r$/,"").trim()).filter(Boolean));return{...c,read:!0,found:e.filter(a=>g.has(a.rel))}}let o=4,p=2e3,q=4e3,r=10,s=new Set(["node_modules",".git",".next","dist","build","out",".venv","venv","__pycache__",".cache","vendor","target"]);async function t(a,b=Date.now()){let c=h().basename(a),e=b+q,g=0,i=!1,j=null;async function k(a,b){let c;if(!j){if(Date.now()>e){j="af360 ran out of time looking inside this folder";return}if(b>o){j="the folder is nested deeper than af360 looks";return}try{c=await d.promises.readdir(a,{withFileTypes:!0})}catch{j="af360 could not open the folder";return}for(let d of c){if(j)return;if(!d.isSymbolicLink()){if(d.isDirectory()){if(".git"===d.name){i=!0;return}if(s.has(d.name)||(0,f.f0)(h().join(a,d.name)))continue;if(await k(h().join(a,d.name),b+1),i)return}else if(d.isFile()&&++g>p){j="the folder holds more files than af360 counts";return}}}}}return(await k(a,0),i)?{name:c,path:a,complete:!0,hasRepoBeneath:!0,files:g}:j?{name:c,path:a,complete:!1,reason:j,hasRepoBeneath:!1,files:g}:{name:c,path:a,complete:!0,hasRepoBeneath:!1,files:g}}async function u(a,b=()=>{}){let c=[];for(let c of a.slice(20))b(c.toLowerCase());for(let b of a.slice(0,20)){let a;try{await d.promises.stat(h().join(b,".git"));continue}catch{}try{a=await d.promises.readdir(b,{withFileTypes:!0})}catch{continue}for(let e of a){if(!e.isDirectory()||e.isSymbolicLink()||e.name.startsWith(".")||s.has(e.name))continue;let a=h().join(b,e.name);if(!(0,f.f0)(a)){try{await d.promises.stat(h().join(a,".git"));continue}catch{}if(c.push(a),c.length>=50)return c}}}return c}},26988:(a,b,c)=>{c.d(b,{Ne:()=>g,cachedGithubScan:()=>h});var d=c(18150);let e=globalThis;function f(a){return{path:a.path,remoteUrl:a.remoteUrl,branch:a.branch,headSha:a.headShaKnown?a.headSha:null,ahead:a.ahead,behind:a.behind,statusKnown:a.statusKnown}}async function g(a,b){if(!await (0,d.fn)())return null;let g=e.__af360GhScan;if(g&&g.key===b)return g.run;let h=d.github.facts(a.map(f)).catch(()=>({at:Date.now(),byPath:{},outage:{kind:"down",reason:"af360 could not complete its check against GitHub"}}));return h.then(async()=>{let a=(0,d.ZL)();if(!a)return;let{logEvent:b}=await Promise.resolve().then(c.bind(c,22191));b(a.ok?"GitHub access is working again - af360 renewed it.":a.reason===d.PC?`${a.reason} - the credential af360 holds stays as it is.`:`GitHub would not renew af360's access - ${a.reason} - reconnect in Connections.`,"connection")}).catch(()=>{}),e.__af360GhScan={key:b,run:h},h}async function h(){let a=e.__af360GhScan;return a?a.run.catch(()=>null):null}},81155:(a,b,c)=>{c.d(b,{r_:()=>ax,od:()=>aH,ag:()=>aG,t7:()=>aI,Nb:()=>az});var d=c(29021),e=c(35060),f=c(21820),g=c.n(f),h=c(33873),i=c.n(h),j=c(35672),k=c.n(j),l=c(71011);let m=[{major:14,eol:"2023-04-30",ltsName:"Fermium"},{major:16,eol:"2023-09-11",ltsName:"Gallium"},{major:17,eol:"2022-06-01"},{major:18,eol:"2025-04-30",ltsName:"Hydrogen"},{major:19,eol:"2023-06-01"},{major:20,eol:"2026-04-30",ltsName:"Iron"},{major:21,eol:"2024-06-01"},{major:22,eol:"2027-04-30",ltsName:"Jod"},{major:23,eol:"2025-06-01"},{major:24,eol:"2028-04-30",ltsName:"Krypton"},{major:25,eol:"2026-06-01"}],n=[{id:"paid-sdk",cat:"money",name:"Paid services in dormant projects",blurb:"Projects you haven't opened in a month or more that still reference paid services"},{id:"cloud-config-stale",cat:"money",name:"Deploy configs with no recent work",blurb:"Deploy files (Vercel, Railway, Docker) in projects that stopped moving"},{id:"identity",cat:"identity",name:"Commit identity mismatches",blurb:"Projects committing as a different email than your global identity"},{id:"ssh-keys",cat:"identity",name:"SSH key inventory",blurb:"The keys living in your .ssh folder and how old they are"},{id:"unpushed",cat:"time-capsule",name:"Unpushed commits",blurb:"Commits that exist only on this machine"},{id:"no-remote",cat:"time-capsule",name:"Projects with no remote",blurb:"Projects whose only home is this disk"},{id:"no-upstream",cat:"time-capsule",name:"Branches that push nowhere",blurb:"Local branches with no upstream, so their commits stay on this machine"},{id:"no-git-folder",cat:"time-capsule",name:"Folders with no git at all",blurb:"Watched folders holding work that has no history and no copy elsewhere"},{id:"readme-stub",cat:"time-capsule",name:"README still a placeholder",blurb:"READMEs that still say TODO, with age"},{id:"todo-census",cat:"time-capsule",name:"TODO census",blurb:"TODOs across the estate, eldest quoted"},{id:"abandoned-branches",cat:"time-capsule",name:"Abandoned branches",blurb:"Branches untouched for six months or more"},{id:"anniversary",cat:"time-capsule",name:"Project anniversaries",blurb:"First-commit birthdays coming up"},{id:"inventory-burst",cat:"time-capsule",name:"Large deletions and change bursts",blurb:"Watched folders that lost a lot of files, or had a lot of them change, since the last look"},{id:"file-gone",cat:"time-capsule",name:"Files that disappeared",blurb:"Files that were there last time and are not now, with the command to bring back the ones git still holds"},{id:"agent-effects",cat:"time-capsule",name:"Changes while an agent was connected",blurb:"Files that changed or went missing in a window that overlapped an agent's questions - af360 cannot tell whose edits they were"},{id:"sync-folder",cat:"safety",name:"Repos inside sync folders",blurb:"Git repos living inside OneDrive, Dropbox or Google Drive"},{id:"history-secrets",cat:"safety",name:"Key-shaped strings in git history",blurb:"Access-key-shaped strings in the history of publicly-remoted repos"},{id:"gitignore-gaps",cat:"safety",name:".gitignore gaps",blurb:"Repos tracking node_modules, .env or build output"},{id:"dev-server-exposed",cat:"safety",name:"Dev server open to the network",blurb:"Dev scripts binding 0.0.0.0 instead of localhost"},{id:"install-scripts",cat:"safety",name:"Dependencies with install scripts",blurb:"Dependencies that run their own code at install time, named"},{id:"dep-uncommitted",cat:"safety",name:"Installs git has not recorded",blurb:"Dependency lists that changed on disk and have not been committed"},{id:"unbacked-ignored",cat:"safety",name:"Ignored files in a project with no remote",blurb:"Environment files, databases and upload folders git ignores, in projects with nowhere to push"},{id:"node-modules",cat:"health",name:"node_modules weight",blurb:"What your node_modules folders weigh and what dormancy would give back"},{id:"build-artifacts",cat:"health",name:"Build-artifact graveyards",blurb:"Old .next, dist and build folders with sizes"},{id:"runtime-eol",cat:"health",name:"Runtime end of life",blurb:"Your Node.js version against its published support dates"},{id:"disk-weight",cat:"health",name:"Tool caches",blurb:"What the caches other tools leave behind are weighing, not counting node_modules or build folders"},{id:"runtime-census",cat:"health",name:"Runtime census",blurb:"Projects asking for a Node version this machine is not running"},{id:"repo-front-door",cat:"health",name:"README and licence",blurb:"Repositories with a remote that carry neither"},{id:"dep-versions",cat:"health",name:"Same dependency, many versions",blurb:"One dependency installed at several versions across the estate, with sizes"},{id:"dep-arrivals",cat:"health",name:"Dependency arrivals",blurb:"What joined your dependency lists, and the date the project recorded it"},{id:"estate-stats",cat:"health",name:"Estate line count",blurb:"Lines of code and language map - the pride stat"},{id:"duplicate-downloads",cat:"health",name:"Downloads sharing a project's name",blurb:"Downloaded folders whose name matches a project you already have, with sizes"},{id:"deploy-sites",cat:"site",name:"Addresses your projects deploy to",blurb:"Hostnames named by deploy configs in your own repos, offered for watching"},{id:"og-preview",cat:"site",name:"Social share preview",blurb:"The card WhatsApp and LinkedIn render when your link is shared"},{id:"security-headers",cat:"site",name:"Security headers",blurb:"Protective headers your site does or doesn't send"},{id:"mixed-content",cat:"site",name:"Mixed content",blurb:"Secure pages loading pieces over plain http"},{id:"redirect-chain",cat:"site",name:"Redirect chain and www",blurb:"How many hops a visit takes, and whether www agrees with the bare domain"},{id:"robots-blocking",cat:"site",name:"robots.txt blocking all",blurb:"A robots.txt turning every search engine away"},{id:"sitemap-missing",cat:"site",name:"Sitemap missing",blurb:"No sitemap for search engines to read"},{id:"dns-watch",cat:"site",name:"DNS snapshot and changes",blurb:"Your domain's DNS records, and any change since last look"}];var o=c(83720);let p=e.EO,q="package-lock.json",r=[{file:"yarn.lock",tool:"Yarn"},{file:"pnpm-lock.yaml",tool:"pnpm"},{file:"bun.lockb",tool:"Bun"},{file:"poetry.lock",tool:"Poetry"},{file:"Pipfile.lock",tool:"Pipenv"},{file:"Cargo.lock",tool:"Cargo"},{file:"go.sum",tool:"Go"},{file:"composer.lock",tool:"Composer"},{file:"Gemfile.lock",tool:"Bundler"}],s=[q,...r.map(a=>a.file)];async function t(a,b){try{let{stdout:c}=await p("git",(0,e.tQ)(b,a),{cwd:a,timeout:8e3,windowsHide:!0,maxBuffer:3e7,env:(0,e.tl)()});return c}catch{return null}}function u(a){let b;try{b=JSON.parse(a)}catch{return null}if(!b||"object"!=typeof b||!b.packages)return null;let c={},d=new Set;for(let[a,e]of Object.entries(b.packages)){if(!e?.version)continue;let b=a.match(/^node_modules\/((?:@[^/]+\/)?[^/]+)$/);b&&(c[b[1]]=e.version,e.hasInstallScript&&d.add(b[1]))}return{set:c,scripts:d}}let v=()=>{};async function w(a,b){let c=await t(a,["ls-files","-z","--",...s.map(a=>`*${a}`)]);if(null===c)return null;let d=c.split("\0").filter(Boolean).filter(a=>s.includes(a.split("/").pop()??""));for(let c of d.slice(8))b(`${a}/${c}`.toLowerCase());return d.slice(0,8)}async function x(a,b){let c=await t(a,["log","-40","--format=%H%x09%aI","--",b]);return c?c.split("\n").filter(Boolean).map(a=>{let[b,c]=a.split(" ");return{commit:b,date:c}}).filter(a=>a.commit&&a.date):[]}async function y(a,b,c){return t(a,["show",`${b}:${c}`])}async function z(a,b){if(0===b.length)return{known:!0};let c=await t(a,["status","--porcelain","--",...b]);if(null===c)return{known:!1};let d=c.split("\n").map(a=>a.replace(/\r$/,"")).find(a=>a.trim().length>0);if(!d)return{known:!0};let e=d.slice(3).trim(),f=(e.includes(" -> ")?e.split(" -> ")[1]:e).replace(/^"|"$/g,""),g=d.slice(0,2);return{known:!0,file:f,kind:g.includes("D")?"deleted":g.includes("R")?"renamed":g.includes("A")?"added":g.includes("M")?"modified":void 0}}async function A(a){let b=await t(a,["status","--porcelain","--untracked-files=all","--",...s]);if(null===b)return{known:!1};let c=b.split("\n").map(a=>a.replace(/\r$/,"")).find(a=>a.startsWith("??")&&a.trim().length>0);return c?{known:!0,file:c.slice(3).trim().replace(/^"|"$/g,""),kind:"added"}:{known:!0}}async function B(a,b=v){let c={name:a.name,path:a.path,read:!1,unread:[],otherNpm:[],arrivals:[],removed:[],uncommitted:!1,uncommittedKnown:!1};if(!a.isGit)return{...c,reason:"this project is not in git, and the arrival record lives in git"};let d=await w(a.path,b);if(null===d)return{...c,reason:"git did not answer when af360 asked which files it is tracking"};let e=[];for(let a of d){let b=a.split("/").pop()??"",c=r.find(a=>a.file===b);c&&e.push({file:a,tool:c.tool})}let f=d.filter(a=>(a.split("/").pop()??"")===q),g=await z(a.path,d);if(g.known&&!g.file){let b=await A(a.path);b.file&&(g=b)}if(0===f.length)return{...c,read:0===e.length,reason:e.length>0?"af360 does not read this lockfile format yet":void 0,unread:e,uncommitted:!!g.file,uncommittedKnown:g.known,uncommittedFile:g.file,uncommittedKind:g.kind};let h=function(a){let b=a=>a.split("/").length-1;return[...a].sort((a,c)=>b(a)-b(c))[0]}(f),i=f.filter(a=>a!==h),j=await x(a.path,h);if(0===j.length)return{...c,unread:e,otherNpm:i,readFile:h,reason:"git has no record of this dependency list changing",uncommitted:!!g.file,uncommittedKnown:g.known,uncommittedFile:g.file,uncommittedKind:g.kind};let k=j[0];if(1===j.length)return{...c,read:!0,unread:e,otherNpm:i,readFile:h,firstRecord:!0,commit:k.commit,recordedAt:k.date,uncommitted:!!g.file,uncommittedKnown:g.known,uncommittedFile:g.file,uncommittedKind:g.kind};let l=j[1],[m,n]=await Promise.all([y(a.path,k.commit,h),y(a.path,l.commit,h)]);if(null===m||null===n)return{...c,unread:e,otherNpm:i,readFile:h,reason:"af360 could not read this dependency list out of git",uncommitted:!!g.file,uncommittedKnown:g.known,uncommittedFile:g.file,uncommittedKind:g.kind};let o=u(m),p=u(n);if(!o||!p)return{...c,unread:e,otherNpm:i,readFile:h,reason:"this dependency list is not in a shape af360 can read",uncommitted:!!g.file,uncommittedKnown:g.known,uncommittedFile:g.file,uncommittedKind:g.kind};let{arrivals:s,removed:t}=function(a,b,c){let d=[];for(let[e,f]of Object.entries(b)){let b=a[e];b!==f&&d.push({name:e,version:f,from:b,installScript:c.has(e)})}let e=Object.keys(a).filter(a=>!(a in b));return d.sort((a,b)=>a.name.localeCompare(b.name)),e.sort(),{arrivals:d,removed:e}}(p.set,o.set,o.scripts);return{name:a.name,path:a.path,read:!0,unread:e,otherNpm:i,readFile:h,arrivals:s,removed:t,commit:k.commit,recordedAt:k.date,uncommitted:!!g.file,uncommittedKnown:g.known,uncommittedFile:g.file}}async function C(a,b=v,c=40){let d=[];for(let d of a.slice(c))b(d.path.toLowerCase());for(let e of a.slice(0,c))d.push(await B(e,b));return d}var D=c(23854),E=c(1232);let F=Object.freeze(["unidentified","af360","node"]),G=Object.freeze({"disc.agentWindowBurstOne":"1 file changed in {0}, in a window that overlapped an agent session.","disc.agentWindowBurst":"{0} files changed in {1}, in a window that overlapped an agent session.","disc.agentWindowGoneOne":"1 file went missing from {0} while an agent was connected.","disc.agentWindowGone":"{0} files went missing from {1} while an agent was connected.","disc.agentUnnamed":"af360 could not identify which agent.","disc.agentNamed":"The agent connected was {0}.","disc.agentGrace":"af360 counts an agent session as open until {0} minutes after its last question.","disc.agentAlsoYou":"af360 cannot tell your own edits from an agent's. If you were working in this folder too, some of these changes are yours.","disc.agentOnlyWhenOpen":"af360 only sees an agent when it is running. If af360 was closed, an agent could have been connected and af360 would not know.","disc.agentUnread":"af360 could not finish reading {0}, so this window is short by whatever is in there.","disc.agentWindowIs":"af360 measures change between its own looks. This window opened when af360 last looked at {0}, not when the agent started.","disc.agentFirstLook":"This is af360's first look at {0}, so there is nothing to compare against yet."});function H(a,...b){return b.reduce((a,b,c)=>a.split(`{${c}}`).join(String(b)),String(a))}var I=c(79748),J=c(5731);let K=h.join(J.lF,"agent-shadow.jsonl"),L=h.join(J.lF,"agent-shadow.1.jsonl"),M=a=>{try{return JSON.parse(a)}catch{return null}};async function N(a=Date.now()){let b="";for(let a of[L,K])try{b+=await (0,I.readFile)(a,"utf8")}catch{}let c=b.split("\n").filter(a=>""!==a.trim()),d=[],e=0;for(let b of c){let c=M(b);if(!c||!Number.isFinite(c.ts)){e++;continue}a-c.ts>2592e6||d.push(c)}d.sort((a,b)=>a.ts-b.ts);let f=d.length?d[0].ts:null;return{entries:d,unreadable:e,oldestTs:f,spansWindow:null!==f&&a-f>=25056e5}}async function O(a,b=Date.now()){if(!Array.isArray(a)||0===a.length)return{written:0,skipped:0};let c=new Set;try{let{entries:a}=await N(b);c=new Set(a.map(a=>a.key))}catch{}let d=[],e=0;for(let b of a){if(!b||!b.key||c.has(b.key)){e++;continue}c.add(b.key),d.push(b)}if(0===d.length)return{written:0,skipped:e};let f=d.map(a=>JSON.stringify({ts:b,key:a.key,kind:a.kind,project:(0,E.dK)(String(a.project??"")),n:a.n,window:a.window,session:a.session,named:a.named,wouldFire:a.wouldFire,counts:a.counts,headline:(0,E.dK)(String(a.headline??"")),who:(0,E.dK)(String(a.who??"")),honesty:(a.honesty??[]).map(a=>(0,E.dK)(String(a))),grace:(0,E.dK)(String(a.grace??"")),shadow:!0})).join("\n")+"\n";try{let a=await (0,I.stat)(K).catch(()=>null);a&&a.size+f.length>262144&&await (0,I.rename)(K,L)}catch{}try{await (0,I.appendFile)(K,f,"utf8")}catch{return{written:0,skipped:e}}return{written:d.length,skipped:e}}var P=c(84848),Q=c(22191),R=c(88804),S=c(87078);let T=new Set(["co","com","org","net","ac","gov","edu","ltd","plc"]);function U(a){let b=a.toLowerCase().split(".").filter(Boolean);return b.length<=2?b.join("."):T.has(b[b.length-2])&&b.length>=3?b.slice(-3).join("."):b.slice(-2).join(".")}let V=[{file:"CNAME",extract:a=>$(a)},{file:"public/CNAME",extract:a=>$(a)},{file:"docs/CNAME",extract:a=>$(a)},{file:"railway.toml",extract:a=>_(a,/^\s*(?:domain|domains|url|custom_domain)\s*=/i)},{file:"railway.json",extract:a=>_(a,/"(?:domain|domains|url|customDomain)"\s*:/i)},{file:"wrangler.toml",extract:a=>_(a,/^\s*(?:route|routes|pattern|custom_domain|zone_name)\s*=/i)},{file:"vercel.json",extract:a=>_(a,/"(?:alias|domains?|host)"\s*:/i)},{file:"netlify.toml",extract:a=>_(a,/^\s*(?:from|to|url|base_url)\s*=/i)},{file:"next.config.js",extract:a=>_(a,/(?:domains|assetPrefix|hostname|destination)\s*[:=]/i)},{file:"next.config.mjs",extract:a=>_(a,/(?:domains|assetPrefix|hostname|destination)\s*[:=]/i)},{file:"next.config.ts",extract:a=>_(a,/(?:domains|assetPrefix|hostname|destination)\s*[:=]/i)},{file:"package.json",extract:a=>_(a,/"homepage"\s*:/i)},{file:"docker-compose.yml",extract:a=>aa(a)},{file:"docker-compose.yaml",extract:a=>aa(a)},{file:"compose.yaml",extract:a=>aa(a)},{file:"Dockerfile",extract:a=>aa(a)},{file:"README.md",prose:!0,extract:a=>ab(a)},{file:"docs/README.md",prose:!0,extract:a=>ab(a)}],W=/\b((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z-]{1,23})\b/gi,X=new Set(["example.com","example.org","example.net","localhost.com","github.com","www.github.com","raw.githubusercontent.com","gitlab.com","bitbucket.org","npmjs.com","www.npmjs.com","registry.npmjs.org","yarnpkg.com","unpkg.com","jsdelivr.net","cdn.jsdelivr.net","cdnjs.cloudflare.com","shields.io","img.shields.io","badgen.net","schema.org","json-schema.org","www.w3.org","w3.org","spdx.org","opensource.org","docker.io","hub.docker.com","docker.com","ghcr.io","quay.io","gcr.io","nodejs.org","reactjs.org","react.dev","nextjs.org","vitejs.dev","python.org","fonts.googleapis.com","fonts.gstatic.com","googleapis.com","gstatic.com","google.com","www.google.com","letsencrypt.org","mozilla.org","developer.mozilla.org","vercel.com","www.vercel.com","vercel.app","railway.app","up.railway.app","netlify.com","www.netlify.com","netlify.app","cloudflare.com","www.cloudflare.com","pages.dev","workers.dev","github.io","fly.io","render.com","heroku.com","herokuapp.com"]),Y=new Set(["local","localhost","internal","invalid","test","example","arpa","onion","js","mjs","cjs","ts","tsx","jsx","json","md","css","scss","sass","less","html","htm","xml","yml","yaml","toml","lock","txt","log","env","sh","ps","py","go","rs","rb","java","kt","swift","php","sql","png","jpg","jpeg","gif","svg","ico","webp","woff","woff2","ttf","map","min","d","node","gitignore","npmrc","nvmrc","config","spec","example","sample","bak","tmp"]),Z=["your-","your.","yourdomain","example","sample","placeholder","localhost","<",">","mydomain","changeme"];function $(a){let b=[];return a.split(/\r?\n/).forEach((a,c)=>{let d=a.trim();!(!d||d.startsWith("#"))&&/^[a-z0-9.-]+$/i.test(d)&&d.includes(".")&&b.push({host:d.toLowerCase(),line:c+1})}),b}function _(a,b){let c=[],d=a.split(/\r?\n/);return d.forEach((a,e)=>{if(!b.test(a))return;let f=[a,d[e+1]??"",d[e+2]??""];for(let[d,g]of(b.test(a)&&/[[{]\s*$/.test(a.trim())?f.join("\n"):a).split("\n").entries())for(let a of g.matchAll(W))c.push({host:a[1].toLowerCase(),line:e+1+d})}),c}function aa(a){let b=[];return a.split(/\r?\n/).forEach((a,c)=>{if(/Host\(`[^`]+`\)/.test(a)||/\b(?:VIRTUAL_HOST|LETSENCRYPT_HOST|DOMAIN|DOMAIN_NAME|APP_URL|PUBLIC_URL|SITE_URL|HOSTNAME)\b\s*[:=]/i.test(a))for(let d of a.matchAll(W))b.push({host:d[1].toLowerCase(),line:c+1})}),b}function ab(a){let b=[];return a.split(/\r?\n/).forEach((a,c)=>{for(let d of a.matchAll(/https?:\/\/([a-z0-9.-]+)/gi)){let a=d[1].toLowerCase().replace(/\.$/,"");a.includes(".")&&b.push({host:a,line:c+1})}}),b}async function ac(a,b,c=3){let e=[],f=[],g=new Set;for(let h of V){let j,k,l=i().join(a.path,...h.file.split("/"));try{j=await d.promises.stat(l)}catch{continue}if(j.isFile()){if(j.size>262144){f.push({file:h.file,project:a.name});continue}try{k=await d.promises.readFile(l,"utf8")}catch{f.push({file:h.file,project:a.name});continue}for(let d of h.extract(k))if(function(a){let b=a.toLowerCase();if(Z.some(a=>b.includes(a))||X.has(b))return!1;let c=b.split(".");if(c.length<2)return!1;let d=c[c.length-1];return!(Y.has(d)||/^\d+$/.test(d)||b.endsWith(".localhost"))}(d.host)&&(!h.prose||function(a,b){if(b.apexes.has(U(a)))return"apex";let c=function(a){let b=new Set;for(let c of a.toLowerCase().split("."))for(let a of(b.add(c),c.split("-")))b.add(a);return b}(a);for(let a of b.projectNames)if(a.length>=4&&c.has(a))return"name";for(let a of b.gitIdentities)if(a.length>=4&&c.has(a))return"identity";return null}(d.host,b))&&!g.has(d.host)){if(e.length>=c)break;g.add(d.host),e.push({host:d.host,file:h.file,line:d.line,project:a.name,projectPath:a.path,fromProse:!!h.prose})}}}return{found:e,unread:f}}async function ad(a,b=[],c=()=>{}){let d=function(a,b){let c=new Set;for(let a of b){let b=(0,S.Pm)(a);b&&c.add(U(b))}let d=new Set,e=new Set;for(let b of a)if(b.name&&d.add(b.name.toLowerCase()),b.remoteUrl){let a=function(a){let b=/^(?:[a-z+]+:\/\/)?(?:[^@/]*@)?[^/:]+[/:]+([^/]+)\//i.exec(a.trim()),c=b?.[1]?.toLowerCase();return c&&/^[a-z0-9][a-z0-9._-]*$/i.test(c)?c:null}(b.remoteUrl);a&&e.add(a)}return{apexes:c,projectNames:d,gitIdentities:e}}(a,b),e=[],f=[];for(let b of a.slice(40))c(b.path.toLowerCase());for(let b of a.slice(0,40)){if(b.looksDownloaded)continue;let a=await ac(b,d);e.push(...a.found),f.push(...a.unread)}return{found:e,unread:f}}function ae(a){return`${a.file}:${a.line}`}var af=c(26988);let ag=e.EO,ah=new Set(["node_modules",".git",".next","dist","build","out",".venv","venv","__pycache__","coverage",".turbo",".cache","target","vendor",".pytest_cache"]);async function ai(a){let b,c={},e=Date.now(),f=!0,g=async(h,j)=>{let k;if(f){if(j>12){f=!1,b="the folder is nested deeper than af360 looks";return}if(Date.now()-e>5e3){f=!1,b="the folder took longer to read than af360 allows";return}try{k=await d.promises.readdir(h,{withFileTypes:!0})}catch{f=!1,b=0===j?"af360 could not open the folder":"af360 could not open a folder inside it";return}for(let e of k){let k;if(!f)return;if(e.isSymbolicLink())continue;if(e.isDirectory()){if(ah.has(e.name)||e.name.startsWith("."))continue;await g(i().join(h,e.name),j+1);continue}if(!e.isFile())continue;if(Object.keys(c).length>=2e4){f=!1,b="the folder holds more files than af360 counts";return}let l=i().join(h,e.name);try{k=await d.promises.stat(l)}catch{continue}c[i().relative(a,l).split(i().sep).join("/")]=`${Math.round(k.mtimeMs)}:${k.size}`}}};return await g(a,0),f?{ts:Date.now(),files:c,complete:!0}:{ts:Date.now(),files:{},complete:!1,reason:b}}let aj={measured:!1,since:0,before:0,after:0,gone:[],added:[],edited:[],standing:[]};function ak(a){if(!a.measured)return!1;let b=a.gone.length;return b>=200||!(b<6)&&!(b<.1*a.before)&&(b>=20||b>=.5*a.before)}function al(a){return!!a.measured&&a.added.length+a.edited.length>=30}function am(a=(0,l.yF)()){return i().join(a,"inventory.json")}async function an(a=(0,l.yF)()){try{let b=JSON.parse(await d.promises.readFile(am(a),"utf8"));if(!b||"object"!=typeof b||Array.isArray(b))return{};let c={};for(let[a,d]of Object.entries(b)){if(!d||"object"!=typeof d||"number"!=typeof d.ts||"boolean"!=typeof d.complete||!d.files||"object"!=typeof d.files)continue;let b={};for(let[a,c]of Object.entries(d.missing??{}))c&&"object"==typeof c&&"number"==typeof c.since&&(b[a]={since:c.since,held:!0===c.held});c[a]={ts:d.ts,complete:d.complete,files:d.files,reason:"string"==typeof d.reason?d.reason:void 0,missing:Object.keys(b).length>0?b:void 0}}return c}catch{return{}}}async function ao(a,b=(0,l.yF)()){try{await d.promises.mkdir(b,{recursive:!0}),await d.promises.writeFile(am(b),JSON.stringify(a),"utf8")}catch{}}async function ap(a){try{let{stdout:b}=await ag("git",(0,e.tQ)(["ls-files","--deleted"],a),{cwd:a,timeout:8e3,windowsHide:!0,env:(0,e.tl)()});return{answered:!0,files:new Set(b.split(/\r?\n/).map(a=>a.trim()).filter(Boolean))}}catch{return{answered:!1,files:new Set}}}let aq={answered:!1,files:new Set};function ar(a){return`git restore -- "${a}"`}let as=e.EO;function at(a){return{verdict:a.en,verdictP:a}}function au(a,b){return{list:{slot:a,items:[b]}}}let av=globalThis,aw=av.__af360NmSizes??(av.__af360NmSizes=new Map);async function ax(a,b,c={}){let d=new Set(b.rules.muted),e=a.map(a=>a.path).join("|")+"::"+b.urls.map(a=>a.url).join("|")+"::m:"+[...d].sort().join(",")+"::d:"+b.declinedSites.map(a=>`${a.host}@${a.citation}`).sort().join(","),f=av.__af360Engine;if(!c.fresh&&f&&f.key===e&&Date.now()-f.ts<36e5)return ay(f,d);let g={key:e,ts:Date.now(),order:[],pending:new Set,deepTotal:0,failed:new Set,unscanned:new Set};av.__af360Engine=g,aT.clear(),aX=null,aU.clear();let h=aD.filter(a=>!d.has(a.id)),i={projects:a,cfg:b,skip:a=>{av.__af360Engine===g&&g.unscanned.add(a)}};for(let a of(await Promise.all(h.filter(a=>"fast"===a.speed).map(a=>a.run(i).catch(()=>[])))))g.order.push(...a);let j=h.filter(a=>"slow"===a.speed),k=function(){let a=Number(process.env.AF360_DEEP_DELAY_MS);return Number.isFinite(a)&&a>0?Math.min(a,6e4):0}();for(let[a,b]of(g.deepTotal=j.length,j.entries()))g.pending.add(b.id),(k>0?new Promise(b=>setTimeout(b,k*(a+1))):Promise.resolve()).then(()=>b.run(i)).then(a=>{av.__af360Engine===g&&g.order.push(...a)}).catch(()=>{if(av.__af360Engine!==g)return;g.failed.add(b.id);let a=n.find(a=>a.id===b.id)?.name??b.id;(0,Q.logEvent)(`${a} didn't finish this time`)}).finally(()=>{g.pending.delete(b.id)});return ay(g,d)}function ay(a,b){return{insights:a.order,muted:n.filter(a=>b.has(a.id)).map(a=>({id:a.id,name:a.name})),pending:a.pending.size,deepTotal:a.deepTotal,failed:[...a.failed].map(a=>({id:a,name:n.find(b=>b.id===a)?.name??a})),unscanned:[...a.unscanned]}}function az(){let a=av.__af360Engine;return!a||Date.now()-a.ts>=36e5?null:a.order.map(a=>({id:a.id,cat:a.cat}))}let aA=globalThis;function aB(a){let b=av.__af360Engine,c=aA.__af360InvPass;if(c&&c.state===b)return c.p;let d=aC(a);return aA.__af360InvPass={state:b,p:d},d}async function aC({projects:a,skip:b}){let c=await an(),d={...c},e=[],f=[],g=new Map;for(let h of function(a,b=()=>{}){let c=a.filter(a=>!a.looksDownloaded);for(let a of c.slice(40))b(a.path.toLowerCase());return c.slice(0,40)}(a,b)){let a=h.path.toLowerCase(),b=await ai(h.path),i=function(a,b){if(!a)return{...aj,first:!0,reason:"af360 has not looked inside this folder before"};if(!a.complete)return{...aj,since:a.ts,reason:a.reason??"af360 could not read this folder last time"};if(!b.complete)return{...aj,since:a.ts,reason:b.reason??"af360 could not read this folder this time"};let c=[],d=[],e=[];for(let[d,f]of Object.entries(a.files)){let a=b.files[d];void 0===a?c.push(d):a!==f&&e.push(d)}for(let c of Object.keys(b.files))void 0===a.files[c]&&d.push(c);let f=[];for(let[c,d]of Object.entries(a.missing??{}))void 0===b.files[c]&&f.push({rel:c,since:d.since,held:d.held});for(let b of c)void 0===(a.missing??{})[b]&&f.push({rel:b,since:a.ts,held:!1});return{measured:!0,since:a.ts,before:Object.keys(a.files).length,after:Object.keys(b.files).length,gone:c,added:d,edited:e,standing:f}}(c[a],b);i.measured||i.first||f.push({name:h.name,reason:i.reason??"af360 could not read it"});let j=i.measured&&i.standing.length>0&&h.isGit?await ap(h.path):aq,k=i.measured?function(a,b){return a.filter(a=>!(a.held&&b.answered&&!b.files.has(a.rel))).map(a=>({...a,held:a.held||b.files.has(a.rel)}))}(i.standing,j):[];g.set(h.path,j),e.push({p:h,d:{...i,standing:k}}),b.complete&&(d[a]={...b,missing:k.length>0?function(a){let b={};for(let c of a)b[c.rel]={since:c.since,held:c.held};return b}(k):void 0})}return await ao(d),{diffs:e,unreadable:f,gitHeld:g}}let aD=[{id:"unpushed",speed:"fast",async run({projects:a}){let b=a.filter(a=>a.isGit&&(a.ahead??0)>0);if(0===b.length)return[];let c=b.reduce((a,b)=>a+(b.ahead??0),0);return[{id:"unpushed",det:"unpushed",cat:"time-capsule",...at((0,R.ph)("disc.unpushed",`{0} commit${1===c?"":"s"} exist${1===c?"s":""} only on this laptop, across {1} project${1===b.length?"":"s"}.`,c,b.length)),why:"A push costs a minute; losing them costs the work itself.",detail:b.map(a=>`${a.name} - ${a.ahead} unpushed`),steps:["Open each project and push once - the remote gets a full copy."],devDetail:b.map(a=>a.path)}]}},{id:"no-remote",speed:"fast",async run({projects:a,cfg:b}){let c=a.filter(a=>a.isGit&&a.remoteKnown&&!a.hasRemote&&!a.looksDownloaded);if(0===c.length)return[];let d=new Set(b.ignored.map(a=>a.id)),e=c.some(a=>d.has(a.path.toLowerCase()));return[{id:"no-remote",det:"no-remote",cat:"time-capsule",...at((0,R.ph)("disc.noRemote",`{0} project${1===c.length?" has":"s have"} no home but this machine.`,c.length)),why:e?"Counting the ones you've silenced too - a remote copy is the whole insurance policy.":"A remote copy is the whole insurance policy.",detail:c.map(a=>a.name),steps:["Create an empty repository on your code host, connect it, and push - a minute per project."],devDetail:c.map(a=>a.path)}]}},{id:"no-upstream",speed:"slow",async run({projects:a,skip:b}){let c=[];for(let d of aW(a,40,b,a=>a.path.toLowerCase()))c.push(await (0,D.Lr)(d));let d=[],e=c.filter(a=>a.read&&a.noUpstream.length>0).sort((a,b)=>b.noUpstream.length-a.noUpstream.length);if(e.length>0){let a=e[0];d.push({id:"no-upstream",det:"no-upstream",cat:"time-capsule",...at(1===e.length?(0,R.ph)("disc.noUpstream","{0} has {1} local branches that push nowhere.",a.name,a.noUpstream.length):(0,R.ph)("disc.noUpstreamMany","{0} projects have local branches that push nowhere - {1} branches in total.",e.length,e.reduce((a,b)=>a+b.noUpstream.length,0))),why:"A branch with no upstream is not sent anywhere by git push on its own. The commits on it are only on this machine.",detail:e.flatMap(a=>[`${a.name} - ${a.noUpstream.length} of ${a.total} local branches`,...a.noUpstream.map(b=>`${a.name}: ${b}`)]),steps:["If a branch is meant to be shared, one command sets it: git push -u origin <branch>.","If it is finished or abandoned, deleting it locally is the other honest answer."],dev:"git for-each-ref refs/heads, comparing %(refname:short) against %(upstream) - an empty upstream field is the finding",devDetail:e.map(a=>`${a.path}: ${a.noUpstream.join(", ")}`)})}let f=c.filter(a=>!a.read);return f.length>0&&d.push({id:"no-upstream-unread",det:"no-upstream",cat:"time-capsule",...at((0,R.ph)("disc.noUpstreamUnread","af360 could not list the branches in {0} of your projects.",f.length)),why:"git did not answer for these. af360 is not saying their branches are fine, it is saying it has not read them.",detail:f.map(a=>a.name),steps:["Nothing to do. This names what af360 could not read, not a problem in the project."],devDetail:f.map(a=>a.path)}),d}},{id:"no-git-folder",speed:"slow",async run({cfg:a,skip:b}){let c=await (0,D.XK)(a.roots??[],b);if(0===c.length)return[];let d=[];for(let a of c)d.push(await (0,D.aI)(a));let e=[],f=d.filter(a=>a.complete&&!a.hasRepoBeneath&&a.files>=D.EZ).sort((a,b)=>b.files-a.files);if(f.length>0){let a=f[0];e.push({id:"no-git-folder",det:"no-git-folder",cat:"time-capsule",...at(1===f.length?(0,R.ph)("disc.noGitFolder","{0} is not in git, and it holds {1} files.",a.name,a.files):(0,R.ph)("disc.noGitFolderMany","{0} folders inside the places you watch are not in git - the largest is {1}, holding {2} files.",f.length,a.name,a.files)),why:"Nothing in these has a history, so nothing here records what changed or lets you go back. af360 only looked inside the folder - it cannot see whether a copy exists somewhere else. If it is work you would miss, git init is the first step.",detail:[...f.map(a=>`${a.name} - ${a.files} files, and no git anywhere inside it`),`af360 names folders holding ${D.EZ} files or more; smaller ones are left alone.`],steps:["git init inside the folder, then commit - that gives it a history on this machine.","Adding a remote and pushing is what gives it a copy somewhere else."],dev:`walked to depth ${D.wq}, file cap ${D.EH}, time cap ${D.Vk}ms, symlinks never followed; a folder with a repository anywhere beneath it is a container and is excluded`,devDetail:f.map(a=>`${a.path}: ${a.files} files`)})}let g=d.filter(a=>!a.complete);return g.length>0&&e.push({id:"no-git-folder-unread",det:"no-git-folder",cat:"time-capsule",...at((0,R.ph)("disc.noGitFolderUnread","af360 could not look all the way inside {0} folders in the places you watch.",g.length)),why:"af360 is not saying these have no backup. It did not finish looking, and a folder it did not finish reading could hold a repository it never reached.",detail:g.map(a=>`${a.name} - ${a.reason??"af360 could not finish the walk"}`),steps:["Nothing to do. This names where af360 stopped, not a problem in the folder."],devDetail:g.map(a=>`${a.path}: ${a.reason}`)}),e}},{id:"readme-stub",speed:"fast",async run({projects:a,skip:b}){for(let c of aW(a,60,b,a=>a.path.toLowerCase())){let a=await aQ(c.path);if(a)return[{id:`readme:${c.path.toLowerCase()}`,det:"readme-stub",cat:"time-capsule",...at(c.lastActivity?(0,R.ph)("disc.readmeStubSince",'{0}\'s README has said "{1}" since about {2}.',c.name,a,(0,o.KN)(c.lastActivity).replace(" ago"," back")):(0,R.ph)("disc.readmeStubWhile",'{0}\'s README has said "{1}" for a while.',c.name,a)),why:"Future-you will wonder what this was. Two honest sentences fix it.",steps:["Open the README and write what it is and how to run it - nothing fancier."]}]}return[]}},{id:"anniversary",speed:"fast",async run({projects:a}){let b=new Date;for(let c of a){if(!c.firstCommit)continue;let a=new Date(c.firstCommit);if(isNaN(a.getTime()))continue;let d=new Date(a);d.setFullYear(b.getFullYear()),d.getTime()<b.getTime()-864e5&&d.setFullYear(b.getFullYear()+1);let e=d.getFullYear()-a.getFullYear(),f=Math.round((d.getTime()-b.getTime())/864e5);if(e<1||f>14)continue;let g=f<=0?(0,R.pl)("disc.whenToday","today"):1===f?(0,R.pl)("disc.whenTomorrow","tomorrow"):(0,R.ph)("disc.whenOn","on {0}",d.toLocaleDateString([],{month:"short",day:"numeric"})),h=g.en;return[{id:`anniversary:${c.path.toLowerCase()}`,det:"anniversary",cat:"time-capsule",...at(null!=c.commitCount?{...(0,R.ph)("disc.anniversaryCommits","{0} turns {1} {2} - {3} commits since the first.",c.name,e,h,c.commitCount),...au(2,g)}:{...(0,R.ph)("disc.anniversary","{0} turns {1} {2}.",c.name,e,h),...au(2,g)}),why:"Estates have birthdays too. The full list lives on Ahead.",link:{href:"/ahead",label:"See Ahead →"},dev:`first commit ${a.toLocaleDateString()}`}]}return[]}},{id:"paid-sdk",speed:"fast",async run({projects:a,skip:b}){let c=["stripe","twilio","@sendgrid/","openai","@anthropic-ai/","aws-sdk","@aws-sdk/","@azure/","@google-cloud/","plaid","algoliasearch","pusher","mailgun","firebase-admin"],d=[];for(let e of aW(a.filter(a=>("dormant"===a.staleness||"forgotten"===a.staleness)&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=(await aM(e.path)).filter(a=>c.some(b=>b.endsWith("/")?a.startsWith(b):a===b));a.length>0&&d.push({p:e,deps:a})}return 0===d.length?[]:[{id:"paid-sdk",det:"paid-sdk",cat:"money",...at((0,R.ph)("disc.paidSdk",`{0} project${1===d.length?" you haven't opened in a month or more still references":"s you haven't opened in a month or more still reference"} paid services.`,d.length)),why:"Dormant code doesn't bill; the accounts behind it might. Worth one look.",detail:d.map(a=>`${a.p.name} - ${a.deps.join(", ")}`),steps:["Check each service's dashboard for an active subscription or usage billing tied to these projects."],devDetail:d.map(a=>a.p.path)}]}},{id:"cloud-config-stale",speed:"fast",async run({projects:a,skip:b}){let c=["vercel.json","railway.toml","railway.json","netlify.toml","fly.toml","render.yaml","Procfile","Dockerfile"],d=[];for(let e of aW(a.filter(a=>("dormant"===a.staleness||"forgotten"===a.staleness)&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=[];for(let b of c)await aL(i().join(e.path,b))&&a.push(b);a.length>0&&d.push({p:e,files:a})}return 0===d.length?[]:[{id:"cloud-config-stale",det:"cloud-config-stale",cat:"money",...at((0,R.ph)("disc.cloudConfigStale",`{0} dormant project${1===d.length?" still carries":"s still carry"} deploy configs - anything still running for them may still be billing.`,d.length)),why:"A deploy that outlives its project is the quietest subscription there is.",detail:d.map(a=>`${a.p.name} - ${a.files.join(", ")}${a.p.lastActivity?` \xb7 untouched since ${(0,o.KN)(a.p.lastActivity).replace(" ago"," back")}`:""}`),steps:["Open your host's dashboard and check whether these projects still have live deployments."],devDetail:d.map(a=>a.p.path)}]}},{id:"ssh-keys",speed:"fast",async run(){let a,b=i().join(g().homedir(),".ssh");try{a=await d.promises.readdir(b,{withFileTypes:!0})}catch{return[]}let c=[];for(let e of a){if(!e.isFile())continue;let a=e.name;if((/^id_[a-z0-9_]+$/i.test(a)||a.endsWith(".pem"))&&!a.endsWith(".pub"))try{let e=await d.promises.stat(i().join(b,a));c.push({name:a,ageDays:Math.floor((Date.now()-e.mtimeMs)/864e5)})}catch{}}if(0===c.length)return[];c.sort((a,b)=>b.ageDays-a.ageDays);let e=c[0],f=Math.floor(e.ageDays/365),h=e.ageDays>=365?(0,R.ph)("disc.ageYears",`{0} year${1===f?"":"s"}`,f):(0,R.ph)("disc.ageDays","{0} days",e.ageDays),j=h.en;return[{id:"ssh-keys",det:"ssh-keys",cat:"identity",...at({...(0,R.ph)("disc.sshKeys",`{0} SSH key${1===c.length?" lives":"s live"} on this machine - the eldest, {1}, was last changed {2} ago.`,c.length,e.name,j),...au(2,h)}),why:"Keys don't expire on their own; the ones you forgot still open doors.",detail:c.map(a=>`${a.name} - last changed ${a.ageDays>=365?`${Math.floor(a.ageDays/365)}y`:`${a.ageDays}d`} ago`),steps:["Check which of these still match keys registered with your code host or your servers; retire the ones nothing uses."],dev:`read from ${b} - names and dates only, key contents never read`}]}},{id:"identity",speed:"slow",async run({projects:a}){let b=a.filter(a=>a.isGit),c=await aJ(process.cwd(),["config","--global","user.email"])??"";if(!c)return[];let d=(await Promise.all(b.map(async a=>({p:a,email:await aJ(a.path,["config","user.email"])??""})))).filter(a=>a.email&&a.email.toLowerCase()!==c.toLowerCase());if(0===d.length)return[];let e=new Map;for(let a of d)e.set(a.email,[...e.get(a.email)??[],a.p.name]);let[f,g]=[...e.entries()].sort((a,b)=>b[1].length-a[1].length)[0];return[{id:"identity",det:"identity",cat:"identity",...at((0,R.ph)("disc.identity",`{0} project${1===g.length?" commits":"s commit"} as {1} - your global identity is {2}.`,g.length,f,c)),why:"Mixed identities scatter your contribution history across strangers.",detail:[...e.entries()].map(([a,b])=>`${a}: ${b.join(", ")}`),steps:["In each project, set the identity you want its history to carry."],dev:`global user.email = ${c}`,devDetail:['git config user.email "you@example.com" (run inside the project)']}]}},{id:"sync-folder",speed:"fast",run:async({projects:a})=>a.filter(a=>a.isGit&&/OneDrive|Dropbox|Google Drive/i.test(a.path)).map(a=>({id:`sync:${a.path.toLowerCase()}`,det:"sync-folder",cat:"safety",...at((0,R.ph)("disc.syncFolder","{0} lives inside a sync folder - git histories and sync services corrupt each other.",a.name)),why:"Sync tools rewrite .git internals mid-operation. Moving it out takes one drag.",detail:[a.path],steps:["Move the folder somewhere the sync tool doesn't watch (like C:\\dev), then keep using it as before."]}))},{id:"dev-server-exposed",speed:"fast",async run({projects:a,skip:b}){let c=[];for(let e of aW(a,60,b,a=>a.path.toLowerCase()))try{let a=i().join(e.path,"package.json");if((await d.promises.stat(a)).size>2e6)continue;let b=JSON.parse(await d.promises.readFile(a,"utf8")),f=b?.scripts&&"object"==typeof b.scripts?b.scripts:{},g=Object.entries(f).find(([,a])=>"string"==typeof a&&a.includes("0.0.0.0"));g&&c.push({p:e,script:`${g[0]}: ${g[1]}`})}catch{}return 0===c.length?[]:[{id:"dev-server-exposed",det:"dev-server-exposed",cat:"safety",...at((0,R.ph)("disc.devServerExposed",`{0} project${1===c.length?" has a dev script that binds":"s have dev scripts that bind"} 0.0.0.0 - while that server is running it accepts connections from the whole network, not just this machine.`,c.length)),why:"Fine when you're testing from a phone on purpose; worth knowing when you're not.",detail:c.map(a=>`${a.p.name} - ${a.script}`),steps:["Remove the 0.0.0.0 binding unless you need other devices to reach the dev server; localhost is the default for a reason."],devDetail:c.map(a=>a.p.path)}]}},{id:"gitignore-gaps",speed:"slow",async run({projects:a,skip:b}){let c=[];for(let d of aW(a.filter(a=>a.isGit&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=await aJ(d.path,["ls-files","--","node_modules",".env",".env.*","dist",".next","build"]);if(!a)continue;let b=a.split("\n").filter(Boolean).filter(a=>!/^\.env\.(example|sample|template|dist)$/i.test(a));if(0===b.length)continue;let e=new Set;for(let a of b)a.startsWith("node_modules")?e.add("node_modules"):(a.startsWith(".env"),e.add(a.split("/")[0]));c.push({p:d,what:[...e]})}return 0===c.length?[]:[{id:"gitignore-gaps",det:"gitignore-gaps",cat:"safety",...at((0,R.ph)("disc.gitignoreGaps",`{0} repo${1===c.length?" is":"s are"} tracking things .gitignore usually keeps out.`,c.length)),why:"node_modules bloats every clone; a tracked .env puts its contents in history for good.",detail:c.map(a=>`${a.p.name} - tracking ${a.what.join(", ")}`),steps:["Add the folder or file to .gitignore, then untrack it (the file stays on disk, git just stops carrying it).","If a tracked .env held real secrets, rotate them - removing the file doesn't remove it from history."],devDetail:c.map(a=>`${a.p.path}: git rm -r --cached ${a.what.join(" ")}`)}]}},{id:"history-secrets",speed:"slow",async run({projects:a,skip:b}){let c=/github\.com|gitlab\.com|bitbucket\.org/i,d=[[/AKIA[0-9A-Z]{16,}(?![0-9A-Z])/,"AWS key"],[/sk_live_[0-9a-zA-Z]{16,}(?![0-9a-zA-Z])/,"Stripe live key"],[/ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/,"GitHub token"],[/github_pat_[A-Za-z0-9_]{22,}(?![A-Za-z0-9_])/,"GitHub token"],[/AIza[0-9A-Za-z_-]{35,}(?![0-9A-Za-z_-])/,"Google API key"],[/xox[baprs]-[0-9A-Za-z-]{10,}(?![0-9A-Za-z-])/,"Slack token"],[/-----BEGIN [A-Z ]*PRIVATE KEY-----/,"private key block"]],f=RegExp(d.map(([a])=>a.source).join("|"),"g"),g=a=>{for(let[b,c]of d)if(RegExp(`^(?:${b.source})`).test(a))return c;return"key"},h=a.filter(a=>a.isGit&&!a.looksDownloaded&&a.remoteUrl&&c.test(a.remoteUrl)),i=h.slice(0,8);for(let a of h.slice(8))b(a.path.toLowerCase());let j=[];for(let a of i)try{let{stdout:b}=await as("git",(0,e.tQ)(["log","--all","--format=","-p","-G","AKIA[0-9A-Z]{16}|sk_live_|ghp_|github_pat_|AIza[0-9A-Za-z_-]{20}|xox[baprs]-|BEGIN [A-Z ]*PRIVATE KEY"]),{cwd:a.path,timeout:3e4,windowsHide:!0,maxBuffer:16e6,env:(0,e.tl)()}),c=new Set,d=new Set;for(let a of b.matchAll(f)){c.add(a[0]);let b=g(a[0]);d.add("private key block"===b?"a private key block":`${b} ...${a[0].slice(-4)}`)}if(d.size>0){let b=[...d].sort();j.push({p:a,samples:b.slice(0,4),count:c.size,forms:d.size})}}catch{b(a.path.toLowerCase())}return 0===j.length?[]:[{id:"history-secrets",det:"history-secrets",cat:"safety",...at(1===j.length?(0,R.ph)("disc.historySecretsOne","{0}'s git history holds strings shaped like access keys - and the repo has a remote - history travels with every clone.",j[0].p.name):(0,R.ph)("disc.historySecretsMany","{0} repos' git history holds strings shaped like access keys - and the repos have a remote - history travels with every clone.",j.length)),why:"If any of these are real, deleting the file didn't unpublish them: every copy of the repo still carries the commit that held them.",detail:[...h.length>8?[(0,R.ph)("disc.historySecretsCapped","af360 read the history of {0} of {1} projects this scan - the rest were not looked at",8,h.length).en]:[],...j.map(a=>{let b=a=>1===a?"":"s",c=a.count===a.forms?`${a.count} key-shaped string${b(a.count)}`:`${a.count} key-shaped string${b(a.count)} in ${a.forms} distinct masked form${b(a.forms)}`,d=a.samples.length<a.forms?`, ${a.samples.length} shown`:"";return`${a.p.name} - ${c}${d}: ${a.samples.join(", ")}`})],steps:["Check whether any are real and still active - if so, rotate them first; that alone ends the risk.","Then, if you want history clean too, rewrite it (git filter-repo) and force-push - optional once keys are rotated."],dev:"matched shapes: AWS, Stripe live, GitHub tokens, Google API, Slack, PEM blocks - values masked at capture (kind + last four, no prefix and no length), raw strings never kept",devDetail:j.map(a=>a.p.path)}]}},{id:"stale-remote",speed:"slow",async run({projects:a}){let b=await (0,af.cachedGithubScan)();if(!b)return[];let c=[];for(let d of a){let a=b.byPath[d.path.toLowerCase()];if(!a||a.ok||"moved"!==a.kind)continue;let e=/GitHub now calls this repository (\S+)$/.exec(a.reason);c.push({p:d,to:e?e[1]:null})}return 0===c.length?[]:[{id:"stale-remote",det:"stale-remote",cat:"health",...at(1===c.length?(0,R.ph)("disc.staleRemoteOne","{0}'s remote points at a name GitHub has moved.",c[0].p.name):(0,R.ph)("disc.staleRemoteMany","{0} projects have a remote pointing at a name GitHub has moved.",c.length)),why:"Git still works - GitHub forwards the old name, so `git push` and `git pull` are fine. But af360 asks GitHub about your projects by the name in your remote, and it cannot follow that forward. Until the remote is updated, these projects stay outside every GitHub answer af360 gives you.",detail:c.map(({p:a,to:b})=>b?`${a.name} - your remote: ${a.remoteUrl??"not read"} - GitHub now calls it: ${b}`:`${a.name} - your remote: ${a.remoteUrl??"not read"} - GitHub says it has moved and did not say where`),steps:["`git remote set-url origin <new URL>` in the project updates it.","Nothing is lost either way - this is about what af360 can see, not about your work."],dev:"GitHub answered 301/308 for this repository; the Location header was validated to the API host and the {owner}/{name} shape before any part of it was rendered. Projects af360 did not ask about - past the per-scan ceiling, rate-limited, non-GitHub remotes, or unread .git/config - are NOT candidates and are not counted as current either.",devDetail:c.map(({p:a})=>a.path)}]}},{id:"install-scripts",speed:"slow",async run({projects:a,skip:b}){let c=new Map,d=0;for(let e of aW(a,40,b,a=>a.path.toLowerCase())){let a=await aS(e.path);if(!a)continue;let b=function(a){let b=new Set;for(let[c,d]of Object.entries(a.packages??{})){if(!d?.hasInstallScript)continue;let a=c.split("node_modules/").pop();a&&b.add(a)}return[...b]}(a);for(let a of(b.length>0&&d++,b))c.set(a,(c.get(a)??0)+1)}if(0===c.size)return[];let e=[...c.entries()].sort((a,b)=>b[1]-a[1]);return[{id:"install-scripts",det:"install-scripts",cat:"safety",...at((0,R.ph)("disc.installScripts",`{0} dependenc${1===c.size?"y runs":"ies run"} their own code at install time, across {1} project${1===d?"":"s"}.`,c.size,d)),why:"Normal for packages that build native code - worth knowing the names, since install scripts run with your full user account.",detail:e.map(([a,b])=>`${a} - in ${b} project${1===b?"":"s"}`),steps:["Nothing to fix if these are names you recognize from your own stack. An unfamiliar name is worth a look before the next install."],dev:"read from package-lock.json hasInstallScript entries - nothing was executed"}]}},{id:"unbacked-ignored",speed:"slow",async run({projects:a,skip:b}){let c=a.filter(a=>a.isGit&&a.remoteKnown&&!a.hasRemote&&!a.looksDownloaded);if(0===c.length)return[];let d=[];for(let a of aW(c,40,b,a=>a.path.toLowerCase()))d.push(await (0,D.iS)(a));let e=[],f=d.filter(a=>a.read&&a.found.length>0);if(f.length>0){let a=f[0];e.push({id:"unbacked-ignored",det:"unbacked-ignored",cat:"safety",...at(1===f.length?(0,R.ph)("disc.unbackedIgnored","{0} has no remote, and git is ignoring {1} things inside it.",a.name,a.found.length):(0,R.ph)("disc.unbackedIgnoredMany","{0} projects have no remote and hold things git is ignoring.",f.length)),why:"A file git ignores is not in the repository, and a project with no remote is not on a server either. af360 looked at those two places and neither holds this file - it has not looked anywhere else on this machine or off it.",detail:[...f.flatMap(a=>a.found.map(b=>`${a.name}: ${b.rel} - ${b.kind}`)),`af360 looked for ${a.lookedFor}.`],steps:["If these hold anything you would miss, copy them somewhere off this machine.","Keep them out of git if they hold secrets - the answer is a backup, not a commit."],dev:"candidate paths checked for existence, then confirmed ignored by git check-ignore --stdin - af360 does not reimplement .gitignore rules",devDetail:f.map(a=>`${a.path}: ${a.found.map(a=>a.rel).join(", ")}`)})}let g=d.filter(a=>!a.read);return g.length>0&&e.push({id:"unbacked-ignored-unread",det:"unbacked-ignored",cat:"safety",...at((0,R.ph)("disc.unbackedIgnoredUnread","af360 could not check what git ignores in {0} of your projects.",g.length)),why:"git did not answer for these, so af360 has not looked rather than found nothing.",detail:g.map(a=>a.name),steps:["Nothing to do. This names what af360 could not read."],devDetail:g.map(a=>a.path)}),e}},{id:"dep-uncommitted",speed:"slow",async run({projects:a,skip:b}){let c=(await aV(a,b)).filter(a=>a.uncommittedKnown&&a.uncommitted);if(0===c.length)return[];let d=c[0];return[{id:"dep-uncommitted",det:"dep-uncommitted",cat:"safety",...at(1===c.length?"deleted"===d.uncommittedKind?(0,R.ph)("disc.depDeleted","{0}'s dependency list was deleted and git has not recorded it yet.",d.name):"added"===d.uncommittedKind?(0,R.ph)("disc.depAdded","{0} has a new dependency list that git has not recorded yet.",d.name):(0,R.ph)("disc.depUncommitted","{0}'s dependency list changed and git has not recorded it yet.",d.name):(0,R.ph)("disc.depUncommittedMany","{0} projects have dependency lists that changed and git has not recorded yet.",c.length)),why:"The dependency list on disk and the one in the project's record no longer match. Committing the change puts it in the record, where the next person to clone it will get the same versions you have.",detail:c.map(a=>`${a.name} - ${a.uncommittedFile??"its dependency list"} is changed and not committed`),steps:["Look at what changed, and commit it if you meant it.","If you did not change it yourself, this is worth understanding before you commit it."],dev:"git status --porcelain over the tracked lockfile paths for a modified, added, deleted or renamed entry, and - only when that finds nothing - a second pass with --untracked-files=all over the lockfile NAMES, which is the only way to see a file git has never been told about (R-32, 2026-08-22). The status code is read rather than discarded, so a deletion is not announced as an install (R-31)",devDetail:c.map(a=>`${a.path}: ${a.uncommittedFile??"(path not reported)"}`)}]}},{id:"runtime-eol",speed:"fast",async run(){let a=process.version,b=function(a){let b=a.match(/^v?(\d+)/);return b?m.find(a=>a.major===parseInt(b[1],10))??null:null}(a);if(!b)return[];let c=new Date(b.eol),d=Math.floor((c.getTime()-Date.now())/864e5);if(d>90)return[];let e=c.toLocaleDateString([],{month:"long",year:"numeric"});return[{id:"runtime-eol",det:"runtime-eol",cat:"health",...at(d<0?(0,R.ph)("disc.runtimeEolPast","Your Node.js ({0}) reached end of life in {1} - it still runs, it just stopped getting security fixes.",a,e):(0,R.ph)("disc.runtimeEolSoon","Your Node.js ({0}) reaches end of life in {1} days.",a,d)),why:"New releases install alongside your projects; most things just keep working.",steps:["Install the current LTS from nodejs.org (or with your version manager), then re-run your projects once to check."],dev:`support table ships with af360 (static data, nothing fetched) - v${b.major}${b.ltsName?` "${b.ltsName}"`:""} EOL ${b.eol}`}]}},{id:"node-modules",speed:"slow",async run({projects:a,skip:b}){let c=[],d={truncated:!1};for(let e of a){let a=i().join(e.path,"node_modules"),f=await aP(a,b,d);f>0&&c.push({p:e,bytes:f})}if(0===c.length)return[];let e=c.reduce((a,b)=>a+b.bytes,0),f=c.filter(a=>"dormant"===a.p.staleness||"forgotten"===a.p.staleness).reduce((a,b)=>a+b.bytes,0);return[{id:"node-modules",det:"node-modules",cat:"health",...at(d.truncated?f>1e8?(0,R.ph)("disc.nodeModulesDormantFloor","Your node_modules folders weigh at least {0} - {1} of that belongs to projects you haven't opened in a month or more. af360 stopped measuring some of them before the end.",aK(e),aK(f)):(0,R.ph)("disc.nodeModulesFloor",`Your node_modules folders weigh at least {0} across {1} project${1===c.length?"":"s"} - af360 stopped measuring some of them before the end.`,aK(e),c.length):f>1e8?(0,R.ph)("disc.nodeModulesDormant","Your node_modules folders weigh {0} - {1} belongs to projects you haven't opened in a month or more.",aK(e),aK(f)):(0,R.ph)("disc.nodeModules",`Your node_modules folders weigh {0} across {1} project${1===c.length?"":"s"}.`,aK(e),c.length)),why:"npm install is what brings node_modules back, so it costs a reinstall rather than the work itself - and that needs the network and the registry on the day you do it.",detail:c.sort((a,b)=>b.bytes-a.bytes).map(a=>`${a.p.name} - ${aK(a.bytes)}${"dormant"===a.p.staleness||"forgotten"===a.p.staleness?" \xb7 dormant":""}`),steps:["Delete node_modules in the ones you are not working in; npm install brings it back when you next need it, as long as you are online."],dev:`${e.toLocaleString()} bytes total, ${f.toLocaleString()} reclaimable`,devDetail:c.sort((a,b)=>b.bytes-a.bytes).map(a=>`${i().join(a.p.path,"node_modules")} - ${a.bytes.toLocaleString()} bytes`)}]}},{id:"build-artifacts",speed:"slow",async run({projects:a,skip:b}){let c=[".next","dist","build","out",".turbo","coverage",".nuxt",".svelte-kit"],d=[],e={truncated:!1};for(let f of a){let a=0,g=[];for(let d of c){let c=await aP(i().join(f.path,d),b,e);c>0&&(a+=c,g.push(d))}a>0&&d.push({p:f,bytes:a,dirs:g})}let f=d.reduce((a,b)=>a+b.bytes,0);return f<2e8?[]:[{id:"build-artifacts",det:"build-artifacts",cat:"health",...at(e.truncated?(0,R.ph)("disc.buildArtifactsFloor","At least {0} sits in folders that build and test tools create - af360 stopped measuring some of them before the end.",aK(f)):(0,R.ph)("disc.buildArtifacts","{0} sits in folders that build and test tools create.",aK(f))),why:"af360 recognised these by their names. Most are remade the next time the tool runs, and a coverage folder comes from a test rather than a build.",detail:d.sort((a,b)=>b.bytes-a.bytes).map(a=>`${a.p.name} - ${aK(a.bytes)} (${a.dirs.join(", ")})`),steps:["Check what made each folder before deleting it - the names are listed above, and the tool that created one is what puts it back."],devDetail:d.sort((a,b)=>b.bytes-a.bytes).map(a=>`${a.p.path}: ${a.dirs.join(", ")} - ${a.bytes.toLocaleString()} bytes`)}]}},{id:"disk-weight",speed:"slow",async run({projects:a,skip:b}){let c={truncated:!1},e=[".cache",".parcel-cache",".vite","target","__pycache__",".pytest_cache",".venv",".gradle",".m2","Pods",".terraform",".mypy_cache",".tox"],f=[],g=[];for(let h of a){let a=0,j=[];for(let f of e){let e=i().join(h.path,f),k=!1;try{k=(await d.promises.stat(e)).isDirectory()}catch{continue}if(!k)continue;let l={truncated:!1,unreadable:!1},m=await aP(e,b,l);l.truncated&&(c.truncated=!0),m>0?(a+=m,j.push(f)):l.unreadable&&g.push(`${h.name}/${f}`)}a>0&&f.push({p:h,bytes:a,dirs:j})}let h=f.reduce((a,b)=>a+b.bytes,0);return h<1e8&&0===g.length?[]:[{id:"disk-weight",det:"disk-weight",cat:"health",...at(c.truncated?(0,R.ph)("disc.diskWeightFloor",`At least {0} sits in tool caches across {1} project${1===f.length?"":"s"} - af360 stopped measuring some of them before the end${g.length>0?`, and could not size ${g.length} more folder${1===g.length?"":"s"}`:""}.`,aK(h),f.length):g.length>0?(0,R.ph)("disc.diskWeightPartial",`{0} sits in tool caches across {1} project${1===f.length?"":"s"} - and af360 could not size {2} more folder${1===g.length?"":"s"}.`,aK(h),f.length,g.length):(0,R.ph)("disc.diskWeight",`{0} sits in tool caches across {1} project${1===f.length?"":"s"}.`,aK(h),f.length)),why:"af360 recognised these by their names - the caches build tools keep beside a project. Whether a given one comes back on its own depends on the tool that made it.",detail:[...f.sort((a,b)=>b.bytes-a.bytes).map(a=>`${a.p.name} - ${aK(a.bytes)} (${a.dirs.join(", ")})`),...g.map(a=>`${a} - af360 could not read this folder`)],steps:["Check what made each cache before deleting it - the names are listed above, and some (a virtual environment, a dependency folder) need their own command to come back rather than returning on the next run."],dev:`counted: ${e.join(", ")}. NOT counted here, they have their own detectors: node_modules, .next, dist, build, out, .turbo, coverage, .nuxt, .svelte-kit`,devDetail:f.sort((a,b)=>b.bytes-a.bytes).map(a=>`${a.p.path}: ${a.dirs.join(", ")} - ${a.bytes.toLocaleString()} bytes`)}]}},{id:"runtime-census",speed:"slow",async run({projects:a,skip:b}){let c=process.version.replace(/^v/,""),e=parseInt(c.split(".")[0],10),f=[],g=0,h=0,j=[];for(let k of aW(a,60,b,a=>a.path.toLowerCase())){let a,b=null,l=null;try{b=await d.promises.readFile(i().join(k.path,"package.json"),"utf8")}catch{b=null}try{l=(await d.promises.readFile(i().join(k.path,".nvmrc"),"utf8")).trim()}catch{l=null}if(null===b&&null===l)continue;let m=null;if(null!==b)try{m=JSON.parse(b).engines?.node??null}catch{j.push(`${k.name} - package.json could not be read`);continue}let n=m??(l?l.replace(/^v/,""):null);if(!n){h++;continue}g++;let o=n.trim(),p=/^>=?\s*v?(\d+)/.exec(o),q=/^[\^~]\s*v?(\d+)/.exec(o),r=/^v?(\d+)(?:\.\d+)*\s*$/.exec(o);if("unread"===(a=/\|\||\s-\s|<|\*|x/i.test(o)?"unread":p?e>=parseInt(p[1],10)?"ok":"mismatch":q?e===parseInt(q[1],10)?"ok":"mismatch":r?e===parseInt(r[1],10)?"ok":"mismatch":"unread")){j.push(`${k.name} - "${n}" is a constraint af360 does not read`);continue}"mismatch"===a&&f.push(`${k.name} - asks for ${n}, this machine runs ${c}`)}return 0===f.length&&0===j.length?[]:[{id:"runtime-census",det:"runtime-census",cat:"health",...at(f.length>0?(0,R.ph)("disc.runtimeCensus",`{0} project${1===f.length?" asks":"s ask"} for a Node version this machine is not running.`,f.length):(0,R.ph)("disc.runtimeCensusUnread",`af360 could not read the Node version {0} project${1===j.length?" asks":"s ask"} for.`,j.length)),why:"The mismatch usually surfaces as an install that fails or a build that behaves differently here than it does everywhere else.",detail:[...f,...j],steps:["Run the project once on the version it asks for - a version manager (nvm, fnm, volta) switches per folder without changing your default."],dev:`installed ${process.version}; ${g} declared, ${h} declared nothing, ${j.length} unread`}]}},{id:"repo-front-door",speed:"slow",async run({projects:a}){let b=[],c=[];for(let e of a){if(!e.isGit||e.looksDownloaded)continue;if(!e.remoteKnown){c.push(`${e.name} - af360 could not read this project's remote`);continue}if(!e.hasRemote)continue;let a=async a=>{for(let b of a)try{return await d.promises.stat(i().join(e.path,b)),!0}catch{}return!1},f=[];await a(["README.md","README","readme.md","README.rst","README.txt"])||f.push("README"),await a(["LICENSE","LICENCE","LICENSE.md","LICENCE.md","LICENSE.txt","COPYING"])||f.push("licence"),f.length>0&&b.push({name:e.name,lacks:f})}return 0===b.length&&0===c.length?[]:[{id:"repo-front-door",det:"repo-front-door",cat:"health",...at(b.length>0?b.every(a=>1===a.lacks.length&&"licence"===a.lacks[0])?(0,R.ph)("disc.frontDoorLicence",`{0} project${1===b.length?" with a remote has":"s with a remote have"} no licence.`,b.length):b.every(a=>1===a.lacks.length&&"README"===a.lacks[0])?(0,R.ph)("disc.frontDoorReadme",`{0} project${1===b.length?" with a remote has":"s with a remote have"} no README.`,b.length):(0,R.ph)("disc.frontDoor",`{0} project${1===b.length?" with a remote is":"s with a remote are"} missing a README or a licence.`,b.length):(0,R.ph)("disc.frontDoorUnread",`af360 could not read the remote on {0} project${1===c.length?"":"s"}, so it has not judged them.`,c.length)),why:"af360 read a remote in each of these, so the code exists somewhere besides this machine - it has not checked whether any of them are public. A licence decides what anyone who can see the code may legally do with it, and its absence is the most restrictive answer rather than the most open one.",detail:[...b.map(a=>`${a.name} - no ${a.lacks.join(", no ")}`),...c],steps:["Add a README saying what the project is, and pick a licence at choosealicense.com - one file each."],dev:`${b.length} with a remote and a gap, ${c.length} unread`}]}},{id:"dep-versions",speed:"slow",async run({projects:a,skip:b}){let c=new Map,d=aW(a,40,b,a=>a.path.toLowerCase());for(let a of d){let b=await aS(a.path);if(b)for(let[d,e]of function(a){let b=[];for(let[c,d]of Object.entries(a.packages??{})){if(!d?.version)continue;let a=c.match(/^node_modules\/((?:@[^/]+\/)?[^/]+)$/);a&&b.push([a[1],d.version])}return b}(b)){let b=c.get(d)??new Map,f=b.get(e)??new Set;f.add(a.name),b.set(e,f),c.set(d,b)}}let e=[...c.entries()].filter(([,a])=>a.size>=3).sort((a,b)=>b[1].size-a[1].size);if(0===e.length)return[];let[f]=e[0],g=0;for(let a of d)g+=await aP(i().join(a.path,"node_modules",f),b);let[,h]=e[0];return[{id:"dep-versions",det:"dep-versions",cat:"health",...at(g>0?(0,R.ph)("disc.depVersionsSize","{0} exists in {1} different versions across the {3} projects af360 read - {2} of the same library.",f,h.size,aK(g),d.length):(0,R.ph)("disc.depVersions","{0} exists in {1} different versions across the {2} projects af360 read.",f,h.size,d.length)),why:"Nothing is broken; it's just the same code paid for several times over in disk and install time.",detail:e.map(([a,b])=>`${a} - ${b.size} versions: ${[...b.keys()].join(", ")}`),steps:["Aligning versions is optional housekeeping: bump the stragglers next time you're in each project anyway."],devDetail:e.flatMap(([a,b])=>[...b.entries()].map(([b,c])=>`${a}@${b}: ${[...c].join(", ")}`))}]}},{id:"dep-arrivals",speed:"slow",async run({projects:a,skip:b}){let c=await aV(a,b),d=[],e=function(a){let b=null,c="";for(let d of a){if(!d.read||!d.recordedAt||0===d.arrivals.length||d.recordedAt<=c)continue;let a=d.arrivals.find(a=>!a.from)??d.arrivals[0];b={project:d,arrival:a},c=d.recordedAt}return b}(c);if(e){let{project:a,arrival:b}=e,c=(0,o.OL)(a.recordedAt),f=a.arrivals.length,g=a.arrivals.filter(a=>a.installScript);d.push({id:"dep-arrivals",det:"dep-arrivals",cat:"health",...at(1===f?(0,R.ph)("disc.depArrival","{0} {1} joined {2}'s dependency list, recorded on {3}.",b.name,b.version,a.name,c):(0,R.ph)("disc.depArrivalMany","{0} packages joined or moved in {1}'s dependency list, recorded on {2}.",f,a.name,c)),why:"That date is when the project committed the change. An install often happens earlier than the commit that writes it down, so this is the day the record moved rather than the day the package landed on the disk.",detail:[...a.arrivals.map(a=>`${a.name} ${a.version}${a.from?` - was ${a.from}`:" - new"}${a.installScript?", and it runs its own code at install time":""}`),...a.removed.map(a=>`${a} - left the list`)],steps:g.length>0?["The full list of dependencies that run their own code at install time is on the 'Dependencies with install scripts' discovery - this card marks them, that one counts them."]:["Nothing to do. This is the estate's own record, read back."],dev:`parsed from ${q} at two commits via git show - no diff text was read, because a version-only bump does not carry the package name into the hunk`,devDetail:[`${a.path}: ${a.commit} (${a.recordedAt})`,...a.arrivals.map(a=>`${a.name}: ${a.from??"(absent)"} -> ${a.version}${a.installScript?" [install script]":""}`)]})}let f=c.filter(a=>!a.read),g=c.filter(a=>a.read&&(a.otherNpm.length>0||a.unread.length>0));if(f.length>0&&d.push({id:"dep-arrivals-unread",det:"dep-arrivals",cat:"health",...at(1===f.length?(0,R.ph)("disc.depUnread","af360 has no dependency answer for {0}.",f[0].name):(0,R.ph)("disc.depUnreadMany","af360 has no dependency answer for {0} projects.",f.length)),why:"Each one is listed above with its own reason - a lockfile format af360 does not read yet, a project with no dependency list, or a history git would not give up. This is af360 saying it has not looked, which is a different thing from saying nothing arrived.",detail:f.map(a=>{let b=a.unread.map(a=>`${a.file} (${a.tool})`).join(", ");return b?`${a.name} - ${b}`:`${a.name} - ${a.reason??"af360 could not read it"}`}),steps:["Nothing to do. This names a gap in what af360 can read, not a problem in your project."],dev:`v1 parses ${q} only; other lockfiles are detected by name through git ls-files and reported unread rather than counted as zero`,devDetail:f.map(a=>`${a.path}: ${a.reason??"unreadable lockfile format"}${a.unread.length?` [${a.unread.map(a=>a.file).join(", ")}]`:""}`)}),g.length>0){let a=g[0];d.push({id:"dep-arrivals-partial",det:"dep-arrivals",cat:"health",...at(1===g.length?(0,R.ph)("disc.depPartial","af360 read 1 of the {1} dependency lists it found in {0}.",a.name,1+a.otherNpm.length+a.unread.length):(0,R.ph)("disc.depPartialMany","af360 read one dependency list each in {0} projects that have more than one.",g.length)),why:"af360 answers for one dependency list per project in this version, and it says which. The others are not read yet - that is different from there being nothing in them.",detail:g.flatMap(a=>[`${a.name} - read: ${a.readFile??"(not recorded)"}`,...a.otherNpm.map(b=>`${a.name} - not read: ${b}`),...a.unread.map(b=>`${a.name} - not read: ${b.file} (${b.tool})`)]),steps:["Nothing to do. The list af360 answered for is named above, so you can see which part of the project this card is about."],dev:"the SHALLOWEST lockfile is chosen - depth decides, and among equal depths git ls-files' alphabetical order breaks the tie, because the sort is stable and reads depth only",devDetail:g.map(a=>`${a.path}: read ${a.readFile}${a.otherNpm.length?`, unanswered ${a.otherNpm.join(", ")}`:""}${a.unread.length?`, unreadable ${a.unread.map(a=>a.file).join(", ")}`:""}`)})}return d}},{id:"estate-stats",speed:"slow",async run({projects:a,skip:b}){let c=await aG(a,b);if(!c||0===c.lines)return[];let d=Object.entries(c.languages).sort((a,b)=>b[1]-a[1]),e=d[0];return[{id:"estate-stats",det:"estate-stats",cat:"health",...at(c.bounded?(0,R.ph)("disc.estateStatsFloor",`Your estate is at least {0} lines across {1} project${1===c.projectCount?"":"s"} - mostly {2}. af360 stopped counting some projects before the end.`,c.lines.toLocaleString(),c.projectCount,e[0]):(0,R.ph)("disc.estateStats",`Your estate is {0} lines across {1} project${1===c.projectCount?"":"s"} - mostly {2}.`,c.lines.toLocaleString(),c.projectCount,e[0])),why:"You wrote all of that. The full picture makes a good screenshot.",detail:d.map(([a,b])=>`${a} - ${b.toLocaleString()} lines`),link:{href:"/report",label:"See your Estate Report →"},dev:`${c.files.toLocaleString()} files counted (bounded walk; node_modules and build output skipped)`}]}},{id:"todo-census",speed:"slow",async run({projects:a,skip:b}){let c=await aH(a,b);return 0===c.count?[]:[{id:"todo-census",det:"todo-census",cat:"time-capsule",...at(c.bounded?(0,R.ph)("disc.todoCensusFloor","af360 found at least {0} TODOs in the files it read - it stopped reading some projects before the end.",c.count):(0,R.ph)("disc.todoCensus","af360 found {0} TODOs in the files it read.",c.count)),why:c.quote?`The elder, from ${c.from}:`:void 0,quoted:c.quote?{text:c.quote,what:"a note written in this project's code"}:void 0,dev:`${c.files} files carried at least one`}]}},{id:"abandoned-branches",speed:"slow",async run({projects:a,skip:b}){let c=[];for(let d of aW(a.filter(a=>a.isGit&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=await aJ(d.path,["for-each-ref","refs/heads","--format=%(refname:short)|%(committerdate:iso8601)"]);if(!a)continue;let b=a.split("\n").filter(Boolean).map(a=>{let[b,c]=a.split("|");return{name:b,age:Date.now()-new Date(c).getTime()}}).filter(a=>a.name!==d.branch&&"main"!==a.name&&"master"!==a.name&&a.age>158112e5);b.length>0&&c.push({p:d,branches:b.map(a=>({name:a.name,ageDays:Math.floor(a.age/864e5)}))})}if(0===c.length)return[];let d=c.reduce((a,b)=>a+b.branches.length,0),e=c.flatMap(a=>a.branches).sort((a,b)=>b.ageDays-a.ageDays)[0];return[{id:"abandoned-branches",det:"abandoned-branches",cat:"time-capsule",...at((0,R.ph)("disc.abandonedBranches",`{0} branch${1===d?" sits":"es sit"} abandoned across {1} project${1===c.length?"":"s"} - the eldest untouched {2} months.`,d,c.length,Math.floor(e.ageDays/30))),why:"Old branches are usually finished thoughts or abandoned ones. Either way, the name stops meaning anything.",detail:c.flatMap(a=>a.branches.map(b=>`${a.p.name} - ${b.name} \xb7 ${Math.floor(b.ageDays/30)}mo`)),steps:["Merge or delete the ones you recognize; git branch -d refuses to lose unmerged work, so it's a safe sweep."],devDetail:c.map(a=>a.p.path)}]}},{id:"duplicate-downloads",speed:"slow",async run({projects:a,skip:b}){let c=a=>a.toLowerCase().replace(/-(main|master)$/i,"").replace(/\s*\(\d+\)$/,"").replace(/[-_]v?\d+(\.\d+)+$/i,""),d=new Set(a.filter(a=>!a.looksDownloaded).map(a=>c(a.name))),e=new Map;for(let b of a.filter(a=>a.looksDownloaded)){let a=c(b.name);e.set(a,[...e.get(a)??[],b])}let f=[];for(let[a,c]of e)if(d.has(a)||c.length>1)for(let a of c)f.push({p:a,bytes:await aP(a.path,b)});if(0===f.length)return[];let g=f.reduce((a,b)=>a+b.bytes,0);return[{id:"duplicate-downloads",det:"duplicate-downloads",cat:"health",...at((0,R.ph)("disc.duplicateDownloads",`{0} downloaded folder${1===f.length?"":"s"} share a name with a project you already have - {1} sitting idle.`,f.length,aK(g))),why:"af360 matched these by folder name only - it has not compared their contents or their history with the projects they resemble.",detail:f.sort((a,b)=>b.bytes-a.bytes).map(a=>`${a.p.name} - ${aK(a.bytes)}`),steps:["Open each one beside the project of the same name and check they really are the same work before you delete anything - af360 matched them by name, not by contents."],devDetail:f.map(a=>a.p.path)}]}},{id:"og-preview",speed:"slow",async run({cfg:a,skip:b}){let c=[];for(let d of(await aZ(a.urls,b))){let a=await a$(d.url);if(!a){b(d.url);continue}if(!a.html)continue;let e=function(a,b){let c=(()=>{try{return new URL(a).hostname}catch{return a}})(),d=a=>{let c=RegExp(`<meta[^>]+(?:property|name)=["']${a}["'][^>]*content=["']([^"']*)["']|<meta[^>]+content=["']([^"']*)["'][^>]*(?:property|name)=["']${a}["']`,"i"),d=b.match(c);return(d?.[1]??d?.[2])?.trim()||void 0},e=d("og:title")??b.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.trim(),f=d("og:description")??d("description"),g=d("og:image");if(g&&!/^https?:/i.test(g)){try{g=new URL(g,a).href}catch{g=void 0}g&&!/^https?:/i.test(g)&&(g=void 0)}let h=[];return e||h.push("title"),f||h.push("description"),g||h.push("image"),{url:a,host:c,title:e,description:f,image:g,missing:h}}(d.url,a.html),f=d.label||e.host;c.push({id:`og:${d.id}`,det:"og-preview",cat:"site",...at(0===e.missing.length?(0,R.ph)("disc.ogSharp","{0} looks sharp when shared - this is the card people see.",f):e.missing.includes("image")?(0,R.ph)("disc.ogSharedBare","{0} shows up bare when shared - no {1}.",f,e.missing.join(", no ")):(0,R.ph)("disc.ogSharedThin","{0} shows up thin when shared - no {1}.",f,e.missing.join(", no "))),why:0===e.missing.length?void 0:"Chat apps and social feeds all render this card. It's the first impression.",steps:0===e.missing.length?void 0:["Add the missing og: meta tags to the page head - title, description and a 1200\xd7630 image cover it."],og:e})}return c}},{id:"security-headers",speed:"slow",async run({cfg:a,skip:b}){let c=["strict-transport-security","content-security-policy","x-content-type-options","referrer-policy"],d=[];for(let e of(await aZ(a.urls,b))){let a=await a$(e.url);if(!a){b(e.url);continue}if(!a.ok)continue;let f=c.filter(b=>a.headers[b]),g=c.filter(b=>!a.headers[b]);if(0===g.length)continue;let h=e.label||a.host;d.push({id:`headers:${e.id}`,det:"security-headers",cat:"site",...at((0,R.ph)("disc.securityHeaders","{0} sends {1} of {2} protective headers.",h,f.length,c.length)),why:"Each one closes a small door - browsers do the enforcing, the server just has to ask.",detail:[...f.map(a=>`${a} ✓`),...g.map(a=>`${a} - not sent`)],steps:["Most hosts set these in one config block, in the headers section of your host's settings. Start with strict-transport-security and x-content-type-options - they're the safe two."],dev:`final URL ${a.finalUrl} \xb7 HTTP ${a.status}`})}return d}},{id:"mixed-content",speed:"slow",async run({cfg:a,skip:b}){let c=[];for(let d of(await aZ(a.urls,b))){let a=await a$(d.url);if(!a){b(d.url);continue}if(!a.html||!a.finalUrl.startsWith("https:"))continue;let e=[...a.html.matchAll(/(?:src|href)=["'](http:\/\/[^"']+)["']/gi)].map(a=>a[1]).filter(a=>!/^http:\/\/(localhost|127\.)/.test(a));if(0===e.length)continue;let f=d.label||a.host;c.push({id:`mixed:${d.id}`,det:"mixed-content",cat:"site",...at(a.html.length>=4e5?(0,R.ph)("disc.mixedContentFloor",`{0} is secure, but af360 found {1} piece${1===e.length?"":"s"} loading over plain http in the part of the page it read.`,f,e.length):(0,R.ph)("disc.mixedContent",`{0} is secure, but {1} piece${1===e.length?"":"s"} of it load over plain http.`,f,e.length)),why:"Browsers block or flag mixed pieces - images vanish, scripts don't run, the padlock loses its meaning.",detail:[...new Set(e)].slice(0,6),steps:["Change the http:// references to https:// (or protocol-relative) - most of these hosts already answer on https."]})}return c}},{id:"redirect-chain",speed:"slow",async run({cfg:a,skip:b}){let c=[];for(let d of(await aZ(a.urls,b))){let a=await a$(d.url);if(!a){b(d.url);continue}let e=d.label||a.host;a.chain.length>=3&&c.push({id:`redirect:${d.id}`,det:"redirect-chain",cat:"site",...at((0,R.ph)("disc.redirectChain","A visit to {0} takes {1} hops before landing.",e,a.chain.length-1)),why:"Each hop is a round trip - on slow connections the chain is the loading time.",detail:a.chain,steps:["Point the first address straight at the final one - one redirect (http → https) is normal, more is detour."]});let f=a.host,g=f.split(".");if(2===g.length||3===g.length&&"www"===g[0]){let a=f.startsWith("www.")?f.slice(4):`www.${f}`,b=await a$(`https://${a}/`);b&&b.ok||c.push({id:`www:${d.id}`,det:"redirect-chain",cat:"site",...at(b?(0,R.ph)("disc.wwwMismatch","{0} answers but {1} returns an error - links with the other form strand visitors.",f,a):(0,R.ph)("disc.wwwNoAnswer","{0} answers and af360 got no reply at all from {1} - links with the other form may strand visitors.",f,a)),why:"People type both. One should redirect to the other.",detail:[`${f} - answers ✓`,`${a} - ${b?`HTTP ${b.status}`:"no answer"}`],steps:["Add a redirect for the unused form at your DNS host or registrar - usually one toggle."]})}}return c}},{id:"robots-blocking",speed:"slow",async run({cfg:a,skip:b}){let c=[];for(let d of(await aZ(a.urls,b))){let a=await a$(d.url);if(!a){b(d.url);continue}if(!a.ok)continue;let e=await a1(new URL("/robots.txt",a.finalUrl).href);if(e&&function(a){let b=!1;for(let c of a.split("\n")){let a=c.trim().toLowerCase();if(a.startsWith("user-agent:"))b="*"===a.slice(11).trim();else if(b&&/^disallow:\s*\/\s*$/.test(a))return!0}return!1}(e)){let b=d.label||a.host;c.push({id:`robots:${d.id}`,det:"robots-blocking",cat:"site",...at((0,R.ph)("disc.robotsBlocking","{0}'s robots.txt turns every search engine away.",b)),why:"Right for a staging site, surprising for a live one - the site won't appear in search either way.",detail:e.split("\n").slice(0,6).map(a=>a.trim()).filter(Boolean),steps:["If this site should be findable, remove the Disallow: / line. If it is meant to be private, this file is not what makes it private - it asks search engines not to list the site, and anyone who has the address can still open it."]})}}return c}},{id:"sitemap-missing",speed:"slow",async run({cfg:a,skip:b}){let c=[];for(let d of(await aZ(a.urls,b))){let a=await a$(d.url);if(!a){b(d.url);continue}if(!a.ok)continue;let e=new URL(a.finalUrl).origin,f=await a1(e+"/robots.txt");if(f&&/^sitemap:/im.test(f))continue;let g=await a2(e+"/sitemap.xml");if(g>=200&&g<400)continue;let h=d.label||a.host;c.push({id:`sitemap:${d.id}`,det:"sitemap-missing",cat:"site",...at((0,R.ph)("disc.sitemapMissing","af360 did not find a sitemap for {0} in the two usual places - search engines find pages slower without one.",h)),why:"Not urgent; most site generators can emit one with a flag.",detail:[`${e}/sitemap.xml - ${g>0?`HTTP ${g}`:"no answer"}`,"robots.txt lists no Sitemap: line"],steps:["Check whether your site already publishes one at another address - WordPress uses /wp-sitemap.xml and Yoast /sitemap_index.xml - and if not, turn on your framework's sitemap option, then add a Sitemap: line to robots.txt."]})}return c}},{id:"deploy-sites",speed:"slow",async run({projects:a,cfg:b,skip:c}){let d=await ad(a,b.urls.map(a=>a.url),c),e=new Set(b.urls.map(a=>(0,S.Pm)(a.url)).filter(Boolean)),f=new Map(b.declinedSites.map(a=>[a.host,a])),g=[],h=new Set;for(let a of d.found){let b;if(e.has(a.host)||h.has(a.host))continue;let c=ae(a),d=f.get(a.host);if(d){if(d.citation===c)continue;b={before:d.citation,after:c}}h.add(a.host),g.push({id:`propose:${a.host}`,det:"deploy-sites",cat:"site",...at((0,R.ph)("disc.deploySite","{0} is named in {1} - af360 isn't watching it yet.",a.host,a.project)),why:"af360 can only watch an address it has been told about; this one was already written down in your own project.",detail:[`${a.project} - ${a.file} line ${a.line}`],steps:["Watch it if the address is yours and live; set it aside if it isn't."],devDetail:[`${a.projectPath} - ${a.file}:${a.line}`],proposal:{host:a.host,url:`https://${a.host}/`,project:a.project,file:a.file,line:a.line,citation:c,changed:b}})}let i=new Map(b.declinedDates.map(a=>[a.host,a])),j=function(a,b,c){let d=new Set;for(let b of a)!b.manual&&b.name&&d.add(b.name.trim().toLowerCase());for(let a of b)a.name&&d.add(a.name.trim().toLowerCase());for(let a of c){let b=(0,S.Pm)(a.url);b&&d.add(b)}return d.delete(""),d}([],b.manualExpiries,b.urls),k=new Set;for(let a of d.found){let b;if(a.fromProse||k.has(a.host)||j.has(a.host))continue;let c=ae(a),d=i.get(a.host);if(d){if(d.citation===c)continue;b={before:d.citation,after:c}}k.add(a.host),g.push({id:`datepropose:${a.host}`,det:"deploy-sites",cat:"money",...at((0,R.ph)("disc.dateSuggestHost","{0} deploys on {1} - track its renewal?",a.project,a.host)),why:"af360 read the address out of the file that deploys this project. It has no way to know when that renews, so it is asking rather than filling anything in.",detail:[`${a.project} - ${a.file} line ${a.line}`],steps:["Track it and give it the date, or set it aside."],devDetail:[`${a.projectPath} - ${a.file}:${a.line}`],dateProposal:{host:a.host,name:a.host,project:a.project,file:a.file,line:a.line,citation:c,changed:b}})}if(d.unread.length>0){let a=[...new Set(d.unread.map(a=>a.file))];g.push({id:"deploy-sites-unread",det:"deploy-sites",cat:"site",...at((0,R.ph)("disc.deploySiteUnread",`af360 could not look inside {0} deployment file${1===d.unread.length?"":"s"} - any address they name is not in the list below.`,d.unread.length)),why:"A file af360 could not open is not a file with nothing in it, so this says so instead of staying quiet.",detail:d.unread.map(a=>`${a.project} - ${a.file}`),steps:["Check the file is readable if you expect an address to come from it; nothing here needs doing otherwise."],dev:`unreadable: ${a.join(", ")}`})}return g}},{id:"inventory-burst",speed:"slow",async run(a){let b=await aB(a),c=[];for(let{p:a,d}of b.diffs)if(ak(d)&&(c.push({id:`gone:${a.path.toLowerCase()}`,det:"inventory-burst",cat:"time-capsule",...at((0,R.ph)("disc.massDeletion","{0} files are gone from {1} since af360 last looked.",d.gone.length,a.name)),why:"A drop that size is usually deliberate, and it is worth being sure it was.",detail:[`${d.before} files before, ${d.after} now - last looked ${(0,o.KN)(new Date(d.since).toISOString())}`,...d.gone],steps:["If this was you, nothing to do. If it was not, a backup or your git remote is where the files still are."],devDetail:[a.path]}),(0,Q.logEvent)(`${d.gone.length} files are gone from ${a.name} since the last look.`)),al(d)){let b=d.added.length+d.edited.length;c.push({id:`burst:${a.path.toLowerCase()}`,det:"inventory-burst",cat:"time-capsule",...at((0,R.ph)("disc.changeBurst","{0} files changed in {1} since your last look.",b,a.name)),why:"You'd want to know whether that was you.",detail:[`${d.added.length} new, ${d.edited.length} edited - since ${(0,o.KN)(new Date(d.since).toISOString())}`,...d.added,...d.edited],steps:["Nothing to do here. If the work is not committed yet, that is the thing worth doing."],devDetail:[a.path]})}return b.unreadable.length>0&&c.push({id:"inventory-unread",det:"inventory-burst",cat:"time-capsule",...at((0,R.ph)("disc.inventoryUnread",`af360 could not look inside {0} watched folder${1===b.unreadable.length?"":"s"} this time - nothing above counts them.`,b.unreadable.length)),why:"A folder af360 could not read is not a folder with nothing in it, so this says so instead of staying quiet.",detail:b.unreadable.map(a=>`${a.name} - ${a.reason}`),steps:["Nothing needs doing unless you expected a report about one of these."]}),c}},{id:"file-gone",speed:"slow",async run(a){let b=await aB(a),c=[];for(let{p:a,d}of b.diffs){if(!d.measured||0===d.standing.length)continue;let e=(b.gitHeld.get(a.path)??aq).files;for(let b of d.standing){let d=b.rel,f=e.has(d);c.push({id:`filegone:${a.path.toLowerCase()}:${d.toLowerCase()}`,det:"file-gone",cat:"time-capsule",...at((0,R.ph)("disc.fileGone","{0} is not in {1} any more.",d,a.name)),why:f?"git still holds it - one command brings it back.":"af360 noticed because it was there the last time it looked.",detail:[`last seen ${(0,o.KN)(new Date(b.since).toISOString())}`,`last known path: ${i().join(a.path,d)}`],steps:f?["Run this in the project folder to bring the file back:",ar(d)]:["If you meant to delete it, nothing to do. If not, a backup is where it still is - af360 could not find it in git."],copy:f?{text:ar(d)}:void 0,devDetail:[`${a.path} - ${d}`]})}}return c}},{id:"dns-watch",speed:"slow",async run({cfg:a,skip:b}){let c=[],e=i().join((0,l.yF)(),"dns.json"),f={};try{f=JSON.parse(await d.promises.readFile(e,"utf8"))}catch{}let g=!1;for(let d of[...new Set(aW(a.urls,5,b,a=>a.id).map(a=>{try{return new URL(a.url).hostname}catch{return null}}).filter(Boolean))]){let a=await a3(d);if(!a)continue;let b=f[d];if(b){let e=[];for(let c of new Set([...Object.keys(b.records),...Object.keys(a)])){let d=b.records[c]??[],f=a[c]??[],g=d.join(", ")||"(none)",h=f.join(", ")||"(none)";g!==h&&(("A"===c||"AAAA"===c)&&d.some(a=>f.includes(a))||e.push(`${c}: was ${g} → now ${h}`))}e.length>0&&(c.push({id:`dns:${d}`,det:"dns-watch",cat:"site",...at((0,R.ph)("disc.dnsWatch","{0}'s DNS changed since last look.",d)),why:"You'd want to know whether that was you.",detail:e,steps:["If you made this change, nothing to do. If not, check your registrar account - DNS is the first thing a hijack touches."],dev:`previous snapshot ${new Date(b.ts).toLocaleString()}`}),(0,Q.logEvent)(`${d}'s DNS records changed - ${e.length} record type${1===e.length?"":"s"}.`))}else(0,Q.logEvent)(`DNS snapshot taken for ${d} - I'll mention it if the records change.`);f[d]={ts:Date.now(),records:a},g=!0}if(g)try{await d.promises.mkdir((0,l.yF)(),{recursive:!0}),await d.promises.writeFile(e,JSON.stringify(f,null,2),"utf8")}catch{}return c}},{id:"agent-effects",speed:"slow",async run(a){let b=await aB(a),c=function(a,b=12e5){let c=[...a??[]].filter(a=>a&&Number.isFinite(a.ts)).sort((a,b)=>a.ts-b.ts),d=[];for(let a of c){let c=d[d.length-1];c&&a.ts-c.lastAsk<=b?(c.lastAsk=a.ts,c.end=a.ts+b,c.asks+=1,c.callers.add(a.caller??null)):d.push({start:a.ts,lastAsk:a.ts,end:a.ts+b,asks:1,callers:new Set([a.caller??null])})}return d.map(({callers:a,...b})=>{let c=[...a].filter(a=>a&&!F.includes(String(a).toLowerCase()));return{...b,named:1===c.length&&1===a.size?c[0]:null}})}(await (0,P.Wq)().catch(()=>[]));if(0===c.length)return[];let d=Date.now(),e=function(a,b,c=Date.now(),d={}){let e=d.floor??5,f=d.isBurst??(()=>!1),g=d.isMassDeletion??(()=>!1),h=[];for(let{project:d,diff:k}of a??[]){if(!k||!k.measured)continue;let a=(k.added?.length??0)+(k.edited?.length??0),l=k.gone?.length??0;for(let m of function(a,b){return(b??[]).filter(b=>a.start<b.end&&b.start<a.end)}({start:k.since,end:c},b))for(let[b,n]of[["burst",a],["gone",l]])if(0!==n&&("burst"!==b||!(n<e))){var i,j;h.push({key:`${b}:${function(a){let b=5381,c=String(a??"").toLowerCase();for(let a=0;a<c.length;a++)b=((b<<5)+b^c.charCodeAt(a))>>>0;return b.toString(36)}(d.path)}:${k.since}:${m.start}`,kind:b,project:d.name,n,window:{since:k.since,until:c},session:{start:m.start,lastAsk:m.lastAsk,end:m.end,asks:m.asks},named:m.named,wouldFire:{burst:"burst"===b&&!!f(k),massDeletion:"gone"===b&&!!g(k)},counts:{added:k.added?.length??0,edited:k.edited?.length??0,gone:l,before:k.before,after:k.after},headline:(i=d.name,"gone"===b?1===n?H(G["disc.agentWindowGoneOne"],i):H(G["disc.agentWindowGone"],n,i):1===n?H(G["disc.agentWindowBurstOne"],i):H(G["disc.agentWindowBurst"],n,i)),who:(j=m.named)?H(G["disc.agentNamed"],j):G["disc.agentUnnamed"],honesty:function({project:a,unreadable:b=!1,first:c=!1}={}){let d=[G["disc.agentAlsoYou"]];return d.push(H(G["disc.agentWindowIs"],a)),d.push(G["disc.agentOnlyWhenOpen"]),b&&d.push(H(G["disc.agentUnread"],a)),c&&d.push(H(G["disc.agentFirstLook"],a)),d}({project:d.name,unreadable:!1,first:!1}),grace:function(a=12e5){return H(G["disc.agentGrace"],Math.round(a/6e4))}()})}}return h}(b.diffs.map(({p:a,d:b})=>({project:{name:a.name,path:a.path},diff:b})),c,d,{isBurst:al,isMassDeletion:ak});try{await O(e,d)}catch{}return[]}}],aE={".ts":"TypeScript",".tsx":"TypeScript",".js":"JavaScript",".jsx":"JavaScript",".mjs":"JavaScript",".py":"Python",".css":"CSS",".scss":"CSS",".html":"HTML",".md":"Markdown",".go":"Go",".rs":"Rust",".java":"Java",".rb":"Ruby",".php":"PHP",".c":"C",".h":"C",".cpp":"C++",".cs":"C#",".swift":"Swift",".kt":"Kotlin",".sql":"SQL",".sh":"Shell",".ps1":"PowerShell",".json":"JSON",".yml":"YAML",".yaml":"YAML"},aF=new Set(["node_modules",".git",".next","dist","build","out",".venv","venv","__pycache__","coverage",".turbo"]);async function aG(a,b=()=>{}){let c=av.__af360Estate;if(c&&Date.now()-c.ts<36e5)return c;let e=a.filter(a=>!a.looksDownloaded),f=0,g=0,h=!1,j={};for(let a of e){let c=1500,e=Date.now()+15e3;async function k(a,b){let h;if(!(c<=0||b>6||Date.now()>e)){try{h=await d.promises.readdir(a,{withFileTypes:!0})}catch{return}for(let l of h){if(c<=0||Date.now()>e)return;let h=i().join(a,l.name);if(l.isSymbolicLink())continue;if(l.isDirectory()){aF.has(l.name)||l.name.startsWith(".")||await k(h,b+1);continue}let m=aE[i().extname(l.name).toLowerCase()];if(m){c--;try{if((await d.promises.stat(h)).size>524288)continue;let a=(await d.promises.readFile(h,"utf8")).split("\n").length;f++,g+=a,j[m]=(j[m]??0)+a}catch{}}}}}await k(a.path,0),(c<=0||Date.now()>e)&&(b(a.path.toLowerCase()),h=!0)}let l={ts:Date.now(),bounded:h,projectCount:e.length,files:f,lines:g,languages:j};return av.__af360Estate=l,l}async function aH(a,b=()=>{}){let c=av.__af360Census;if(c&&Date.now()-c.ts<36e5)return c;let d={ts:Date.now(),...await a5(a,b)};return av.__af360Census=d,d}async function aI(a,b=()=>{}){let c=0;for(let d of a)("dormant"===d.staleness||"forgotten"===d.staleness)&&(c+=await aP(i().join(d.path,"node_modules"),b));return c}async function aJ(a,b){try{let{stdout:c}=await as("git",(0,e.tQ)(b,a),{cwd:a,timeout:8e3,windowsHide:!0,env:(0,e.tl)()});return c.trim()||null}catch{return null}}function aK(a){return a>=1e9?`${(a/1e9).toFixed(1)} GB`:a<1e6?"under 1 MB":`${Math.round(a/1e6)} MB`}async function aL(a){try{return await d.promises.access(a),!0}catch{return!1}}async function aM(a){try{let b=i().join(a,"package.json");if((await d.promises.stat(b)).size>2e6)return[];let c=JSON.parse(await d.promises.readFile(b,"utf8"));return[...Object.keys(c?.dependencies??{}),...Object.keys(c?.devDependencies??{})]}catch{return[]}}let aN=new Set([".git"]);async function aO(a,b=0,c=Date.now()+2e4,e={truncated:!1}){let f;if(b>8||Date.now()>c)return e.truncated=!0,0;let g=0;try{f=await d.promises.readdir(a,{withFileTypes:!0})}catch{return 0===b&&(e.unreadable=!0),0}for(let h of f){if(Date.now()>c){e.truncated=!0;break}let f=i().join(a,h.name);if(!h.isSymbolicLink()){if(h.isDirectory())aN.has(h.name)||(g+=await aO(f,b+1,c,e));else if(h.isFile())try{g+=(await d.promises.stat(f)).size}catch{}}}return g}async function aP(a,b=()=>{},c={truncated:!1}){let d=a.toLowerCase(),e=aw.get(d);if(e&&Date.now()-e.ts<432e5)return e.truncated&&(b(d),c.truncated=!0),e.bytes;let f={truncated:!1,unreadable:!1},g=await aO(a,0,Date.now()+2e4,f);return aw.set(d,{ts:Date.now(),bytes:g,truncated:f.truncated}),f.unreadable&&(c.unreadable=!0),f.truncated&&(b(d),c.truncated=!0),g}async function aQ(a){try{let b=i().join(a,"README.md");if((await d.promises.stat(b)).size>1048576)return null;let c=(await d.promises.readFile(b,"utf8")).trim(),e=c.slice(0,200);if(/\bTODO\b|coming soon/i.test(e)){let a=e.match(/\bTODO\b[^\n.]*|coming soon[^\n.]*/i);return(a?.[0]??"TODO").trim().slice(0,60)}if(c.length>0&&c.length<80)return c.split("\n")[0].slice(0,60);return null}catch{return null}}let aR=new Map;async function aS(a){let b=i().join(a,"package-lock.json"),c=b.toLowerCase();if(aR.has(c))return aR.get(c)??null;let e=null;try{(await d.promises.stat(b)).size<=3e7&&(e=JSON.parse(await d.promises.readFile(b,"utf8")))}catch{}return aR.set(c,e),aR.size>100&&aR.clear(),e}let aT=new Map,aU=new Map;function aV(a,b){let c=aU.get("estate");if(c)return c;let d=C(a.map(a=>({name:a.name,path:a.path,isGit:a.isGit})),b);return aU.set("estate",d),d}function aW(a,b,c,d){for(let e of a.slice(b))c(d(e));return a.slice(0,b)}let aX=null;async function aY(a){let b=Date.now(),c=[],d=[],e=0,f=async()=>{for(;;){let f=e++;if(f>=a.length)return;let g=a[f];if(Date.now()-b>=2e4){d.push(g.id);continue}try{await a$(g.url)}catch{}c.push(g)}};return await Promise.all(Array.from({length:Math.min(6,Math.max(1,a.length))},f)),{reached:c,skipped:d}}async function aZ(a,b){aX||(aX=aY(a));let c=await aX;for(let a of c.skipped)b(a);return c.reached}function a$(a){let b=a.toLowerCase(),c=aT.get(b);if(c)return c;let d=a_(a);return aT.set(b,d),d}async function a_(a){let b=[],c=a;try{for(let a=0;a<6;a++){b.push(c);let a=new AbortController,d=setTimeout(()=>a.abort(),1e4);try{let d=await fetch(c,{signal:a.signal,redirect:"manual",cache:"no-store"});if(d.status>=300&&d.status<400){let a=d.headers.get("location");if(!a)return a0(c,d,b,null);try{d.body?.cancel()}catch{}c=new URL(a,c).href;continue}let e=d.ok&&(d.headers.get("content-type")??"").includes("text/html")?(await d.text()).slice(0,4e5):null;return a0(c,d,b,e)}finally{clearTimeout(d)}}return null}catch{return null}}function a0(a,b,c,d){let e={};b.headers.forEach((a,b)=>{e[b.toLowerCase()]=a.slice(0,200)});let f=a;try{f=new URL(a).hostname}catch{}return{finalUrl:a,host:f,status:b.status,ok:b.ok,chain:c,headers:e,html:d}}async function a1(a){let b=new AbortController,c=setTimeout(()=>b.abort(),8e3);try{let c=await fetch(a,{signal:b.signal,redirect:"follow",cache:"no-store"});if(!c.ok)return null;return(await c.text()).slice(0,1e5)}catch{return null}finally{clearTimeout(c)}}async function a2(a){let b=new AbortController,c=setTimeout(()=>b.abort(),8e3);try{let c=await fetch(a,{signal:b.signal,redirect:"follow",cache:"no-store"});try{c.body?.cancel()}catch{}return c.status}catch{return 0}finally{clearTimeout(c)}}async function a3(a){let b={},c=async(a,c)=>{try{let d=(await c()).sort();d.length>0&&(b[a]=d)}catch{}};return await Promise.all([c("A",()=>k().resolve4(a)),c("AAAA",()=>k().resolve6(a)),c("CNAME",()=>k().resolveCname(a)),c("NS",()=>k().resolveNs(a)),c("MX",async()=>(await k().resolveMx(a)).map(a=>`${a.priority} ${a.exchange}`))]),Object.keys(b).length>0?b:null}let a4=new Set([".ts",".tsx",".js",".jsx",".mjs",".py",".css",".html",".md",".go",".rs",".java"]);async function a5(a,b=()=>{}){let c=0,e=0,f=!1,g=null;for(let j of a){let a=600,k=Date.now()+15e3;async function h(b,f){let l;if(!(a<=0||f>6||Date.now()>k)){try{l=await d.promises.readdir(b,{withFileTypes:!0})}catch{return}for(let m of l){if(a<=0||Date.now()>k)return;let l=i().join(b,m.name);if(!m.isSymbolicLink()){if(m.isDirectory()){aF.has(m.name)||m.name.startsWith(".")||await h(l,f+1);continue}if(a4.has(i().extname(m.name))){a--;try{let a=await d.promises.stat(l);if(a.size>262144)continue;let b=(await d.promises.readFile(l,"utf8")).match(/\/\/\s*(TODO|FIXME)[^\n]*|#\s*(TODO|FIXME)[^\n]*|<!--\s*(TODO|FIXME)[^\n]*/g);if(b&&b.length>0&&(c+=b.length,e++,!g||a.mtimeMs<g.mtime)){let c=(0,E.dK)(b[0].replace(/^[/#<!\-\s]+/,"")).slice(0,90);g={mtime:a.mtimeMs,quote:c,from:j.name}}}catch{}}}}}}await h(j.path,0),(a<=0||Date.now()>k)&&(b(j.path.toLowerCase()),f=!0)}let j=g;return{count:c,files:e,bounded:f,quote:j?.quote,from:j?.from}}},84848:(a,b,c)=>{c.d(b,{W7:()=>i,Wq:()=>h});var d=c(79748),e=c(5731),f=c(65970);let g=new Set([...e.vh,e.G2]);async function h(a=Date.now()){let b=[];for(let a of[e.aC,e.Q6])try{b.push(await (0,d.readFile)(a,"utf8"))}catch{}if(0===b.length)return[];let c=[];for(let d of b.join("").split("\n")){let b=function(a){let b=a.split(" ");if(2!==b.length&&3!==b.length)return null;let c=b[1];if(!g.has(c))return null;let d=Date.parse(b[0]);if(!Number.isFinite(d))return null;if(2===b.length)return{ts:d,tool:c,caller:null};let f=b[2];return f===e.z_||e.Mx.test(f)?{ts:d,tool:c,caller:f}:null}(d);b&&a-b.ts<6048e5&&c.push(b)}return c.sort((a,b)=>a.ts-b.ts),c.slice(-400)}async function i(a,b=Date.now()){let c=await h(b);return 0===c.length?[]:(function(a){let b=[],c=new Map;for(let d of a){let a=`${d.tool}\0${d.caller??""}`,e=c.get(a);if(e&&d.ts-e.ts<6e4){e.count+=1;continue}let f={ts:d.ts,tool:d.tool,caller:d.caller??null,count:1};c.set(a,f),b.push(f)}return b})(c).map(c=>({ts:c.ts,text:function(a,b,c=Date.now()){let d=function(a,b){if(!a)return null;let c=null;for(let d of a.looks??[])d&&"number"==typeof d.scannedAt&&d.scannedAt<=b&&(null===c||d.scannedAt>c)&&(c=d.scannedAt);return"number"==typeof a.scannedAt&&a.scannedAt<=b&&(null===c||a.scannedAt>c)&&(c=a.scannedAt),c}(b,a.ts),g=null===d?null:(0,f.fF)(d,a.ts),h=a.count>1?` x${a.count}`:"",i=a.caller&&a.caller!==e.z_?a.caller:null,j=i?` by ${i}`:"",k=a.caller===e.z_?" af360 could not identify what asked.":"";if(a.tool===e.G2)return i?`${i} asked for a tool af360 does not have${h} - refused.`:`an agent asked for a tool af360 does not have${h} - refused.${k}`;let l=null===g?"answered from af360's last look.":`answered from last look (${g}).`;return`${a.tool} asked${j}${h} - ${l}${k}`}(c,a,b),kind:"mcp",caller:c.caller&&c.caller!==e.z_?c.caller:null}))}},87078:(a,b,c)=>{function d(a){try{return new URL(a).hostname.toLowerCase()}catch{return null}}function e(a){if(!a)return null;let b=a.trim();if(!b)return null;let c=null,d=null,e=/^(?:[^@/]+@)?([A-Za-z0-9._-]+):(?!\/\/)(.+)$/.exec(b);if(e&&!/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(b))c=e[1],d=e[2];else try{let a=new URL(b);c=a.hostname,d=a.pathname}catch{return null}let f=(d??"").replace(/^\/+/,"").replace(/\/+$/,"").replace(/\.git$/i,"");return c&&f?`${c.toLowerCase()}/${f.toLowerCase()}`:null}c.d(b,{Pm:()=>d,p8:()=>e})},88804:(a,b,c)=>{c.d(b,{nn:()=>e,ph:()=>f,pl:()=>g,v1:()=>d});let d={and:" and ",comma:", "};function e(a,b,c){return 0===a.length?"":1===a.length?a[0]:a.slice(0,-1).join(c)+b+a[a.length-1]}function f(a,b,...c){return{k:a,en:function(a,b=[]){return a.replace(/\{(\d+)\}/g,(a,c)=>{let d=b[Number(c)];return void 0===d?a:String(d)})}(b,c),slots:c}}function g(a,b){return{k:a,en:b}}}};
@@ -1 +0,0 @@
1
- "use strict";exports.id=6584,exports.ids=[6584],exports.modules={13020:(a,b,c)=>{c.d(b,{Xq:()=>h,eY:()=>g,fL:()=>f,lz:()=>i});var d=c(31296),e=c(40850);function f(a){let b=[];for(let c of a.projects)c.remoteKnown&&c.statusKnown&&b.push(c.path.toLowerCase());for(let c of a.urls)c.current&&b.push(c.entry.id);for(let c of(b.push(...a.expiryIds),a.missingRoots))b.push(`missing:${c.toLowerCase()}`);return b}function g(a,b,c){let d={};for(let b of a)b.handled||(d[b.id]=b.label);return{ts:c,items:d,measured:b}}function h(a,b){let c=Object.keys(b.items).length;if(!a)return{first:!0,since:null,cleared:[],appeared:[],remaining:c,unmeasured:0};let d=new Set(a.measured),e=new Set(b.measured),f=a=>{let b=a.startsWith("resurface:")?a.slice(10):a;return d.has(b)&&e.has(b)},g=[],h=[],i=0;for(let[c,d]of Object.entries(a.items))if(!(c in b.items)){if(!f(c)){i++;continue}g.push(d)}for(let[c,d]of Object.entries(b.items))if(!(c in a.items)){if(!f(c)){i++;continue}h.push(d)}return{first:!1,since:a.ts,cleared:g,appeared:h,remaining:c,unmeasured:i}}function i(a,b,c){if(a.first)return b("check.first",d.UI.check.first);let f=b(e.wo.and,e.v1.and),g=b(e.wo.comma,e.v1.comma),h=[(0,e.GS)(b("check.since",d.UI.check.since),[c])];return a.cleared.length>0&&h.push((0,e.GS)(b("check.cleared",d.UI.check.cleared),[(0,e.nn)(a.cleared,f,g)])),a.appeared.length>0&&h.push((0,e.GS)(b("check.appeared",d.UI.check.appeared),[(0,e.nn)(a.appeared,f,g)])),0===a.cleared.length&&0===a.appeared.length&&0===a.unmeasured&&h.push(b("check.unchanged",d.UI.check.unchanged)),h.push((0,e.GS)(b("check.remaining",d.UI.check.remaining),[a.remaining])),a.unmeasured>0&&h.push((0,e.GS)(b("check.unmeasured",d.UI.check.unmeasured),[a.unmeasured])),h.join(" ")}},29876:(a,b,c)=>{c.d(b,{dK:()=>e});let d="[secret masked]";function e(a){let b=a;return(b=(b=(b=(b=(b=(b=(b=(b=(b=b.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,d)).replace(/\b([A-Za-z0-9_.-]*(?:SECRET|TOKEN|PASSWORD|PASSWD|API[_-]?KEY|APIKEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|CLIENT[_-]?SECRET|AUTH|CREDENTIAL|[A-Z0-9]_KEY|^KEY)[A-Za-z0-9_.-]*)(\s*[:=]\s*)(["'`]?)([^\s"'`,;]{4,})\3/gi,(a,b,c)=>`${b}${c}${d}`)).replace(/\b([A-Za-z0-9_.-]*(?:URL|URI|DSN|CONN)[A-Za-z0-9_.-]*)(\s*[:=]\s*)(["'`]?)([^\s"'`,;]{4,})\3/gi,(a,b,c)=>/[A-Za-z]/.test(b)&&(b.includes("_")||b===b.toUpperCase())?`${b}${c}${d}`:a)).replace(/\b([A-Za-z][A-Za-z0-9+.-]*):\/\/([^\s/:@]{1,128}):([^\s/@]{1,256})@/g,(a,b,c)=>`${b}://${c}:${d}@`).replace(/(^|[\s(<'"`=,])([A-Za-z0-9._%+-]{1,64}):([^\s:@/\\]{4,128})@((?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}|\d{1,3}(?:\.\d{1,3}){3}|localhost)(?=[:/?\s'"`)>,]|$)/g,(a,b,c,e,f)=>`${b}${c}:${d}@${f}`)).replace(/gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}/g,d)).replace(/(?<![A-Za-z0-9])(AKIA[0-9A-Z]{12,}|ASIA[0-9A-Z]{12,}|sk_(?:live|test)_[0-9A-Za-z]{10,}|rk_(?:live|test)_[0-9A-Za-z]{10,}|pk_live_[0-9A-Za-z]{10,}|glpat-[A-Za-z0-9_-]{16,}|AIza[0-9A-Za-z_-]{30,}|ya29\.[0-9A-Za-z_-]{20,}|xox[baprs]-[0-9A-Za-z-]{8,}|npm_[A-Za-z0-9]{30,}|dop_v1_[a-f0-9]{40,}|SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}|hf_[A-Za-z0-9]{30,}|sk-ant-[A-Za-z0-9_-]{20,}|sk-[A-Za-z0-9]{32,})(?![A-Za-z0-9])/g,d)).replace(/\b(bearer|basic)\s+[A-Za-z0-9._~+/=-]{12,}/gi,(a,b)=>`${b} ${d}`)).replace(/(?<![A-Za-z0-9])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9])/g,d)).replace(/(?<![A-Za-z0-9])[0-9a-f]{32,}(?![A-Za-z0-9])/gi,d)).replace(/(?<![A-Za-z0-9])[A-Za-z0-9+/]{40,}={0,2}(?![A-Za-z0-9+/=])/g,d)}},87333:(a,b,c)=>{c.d(b,{K:()=>f});var d=c(38301);c(13020);var e=c(94387);function f(a){let[b,c]=(0,d.useState)(null),[f,g]=(0,d.useState)(null),h=(0,e.c)();return a.attention&&a.radar&&a.expiry,(a.attention?.items??[]).map(a=>a.id).join("|"),{delta:b,remember:()=>{h||a.demo||!f||fetch("/api/check",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({snapshot:f})}).catch(()=>{})}}}}};