af360 0.1.6 → 0.1.13

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 (148) hide show
  1. package/dist/.next/BUILD_ID +1 -1
  2. package/dist/.next/app-build-manifest.json +92 -92
  3. package/dist/.next/app-path-routes-manifest.json +18 -18
  4. package/dist/.next/build-manifest.json +2 -2
  5. package/dist/.next/prerender-manifest.json +39 -39
  6. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  7. package/dist/.next/server/app/_not-found.html +1 -1
  8. package/dist/.next/server/app/_not-found.rsc +3 -3
  9. package/dist/.next/server/app/ahead/page.js +1 -1
  10. package/dist/.next/server/app/ahead/page_client-reference-manifest.js +1 -1
  11. package/dist/.next/server/app/ahead.html +1 -1
  12. package/dist/.next/server/app/ahead.rsc +4 -4
  13. package/dist/.next/server/app/api/browse/route_client-reference-manifest.js +1 -1
  14. package/dist/.next/server/app/api/card/route.js +1 -1
  15. package/dist/.next/server/app/api/card/route.js.nft.json +1 -1
  16. package/dist/.next/server/app/api/card/route_client-reference-manifest.js +1 -1
  17. package/dist/.next/server/app/api/check/route_client-reference-manifest.js +1 -1
  18. package/dist/.next/server/app/api/config/route.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.js +1 -1
  28. package/dist/.next/server/app/api/handoff/route_client-reference-manifest.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_client-reference-manifest.js +1 -1
  38. package/dist/.next/server/app/api/report/route.js +1 -1
  39. package/dist/.next/server/app/api/report/route.js.nft.json +1 -1
  40. package/dist/.next/server/app/api/report/route_client-reference-manifest.js +1 -1
  41. package/dist/.next/server/app/api/services/route_client-reference-manifest.js +1 -1
  42. package/dist/.next/server/app/api/status/route.js +1 -1
  43. package/dist/.next/server/app/api/status/route.js.nft.json +1 -1
  44. package/dist/.next/server/app/api/status/route_client-reference-manifest.js +1 -1
  45. package/dist/.next/server/app/connections/page.js +1 -1
  46. package/dist/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  47. package/dist/.next/server/app/connections.html +1 -1
  48. package/dist/.next/server/app/connections.rsc +4 -4
  49. package/dist/.next/server/app/dashboard/page_client-reference-manifest.js +1 -1
  50. package/dist/.next/server/app/dashboard.html +1 -1
  51. package/dist/.next/server/app/dashboard.rsc +3 -3
  52. package/dist/.next/server/app/discoveries/page.js +2 -2
  53. package/dist/.next/server/app/discoveries/page.js.nft.json +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 +2 -2
  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 +1 -1
  83. package/dist/.next/server/app/report/page_client-reference-manifest.js +1 -1
  84. package/dist/.next/server/app/report.html +1 -1
  85. package/dist/.next/server/app/report.rsc +4 -4
  86. package/dist/.next/server/app/settings/page.js +2 -2
  87. package/dist/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  88. package/dist/.next/server/app/settings.html +1 -1
  89. package/dist/.next/server/app/settings.rsc +4 -4
  90. package/dist/.next/server/app/sites/page.js +1 -1
  91. package/dist/.next/server/app/sites/page_client-reference-manifest.js +1 -1
  92. package/dist/.next/server/app/sites.html +1 -1
  93. package/dist/.next/server/app/sites.rsc +4 -4
  94. package/dist/.next/server/app/welcome/page_client-reference-manifest.js +1 -1
  95. package/dist/.next/server/app/welcome.html +1 -1
  96. package/dist/.next/server/app/welcome.rsc +4 -4
  97. package/dist/.next/server/app-paths-manifest.json +18 -18
  98. package/dist/.next/server/chunks/1155.js +1 -0
  99. package/dist/.next/server/chunks/2346.js +1 -1
  100. package/dist/.next/server/chunks/2465.js +1 -1
  101. package/dist/.next/server/chunks/2817.js +1 -1
  102. package/dist/.next/server/chunks/4134.js +1 -1
  103. package/dist/.next/server/chunks/7511.js +1 -1
  104. package/dist/.next/server/chunks/876.js +1 -0
  105. package/dist/.next/server/chunks/9170.js +1 -1
  106. package/dist/.next/server/middleware-manifest.json +1 -1
  107. package/dist/.next/server/pages/404.html +1 -1
  108. package/dist/.next/server/pages/500.html +1 -1
  109. package/dist/.next/static/chunks/1681-b233404f9ec2fc2e.js +1 -0
  110. package/dist/.next/static/chunks/3192-6b341d547a49fa85.js +1 -0
  111. package/dist/.next/static/chunks/5623-ec4f1a6f7695d8d1.js +1 -0
  112. package/dist/.next/static/chunks/7897-6e6c509aea495560.js +1 -0
  113. package/dist/.next/static/chunks/app/ahead/page-e72bed448dd40bd5.js +1 -0
  114. package/dist/.next/static/chunks/app/connections/page-5d6cae02e6f2b49a.js +1 -0
  115. package/dist/.next/static/chunks/app/discoveries/page-52696e6ee0c8d3d5.js +1 -0
  116. package/dist/.next/static/chunks/app/error-37987420de14c10c.js +1 -0
  117. package/dist/.next/static/chunks/app/log/page-45b4cfd8f1a29d09.js +1 -0
  118. package/dist/.next/static/chunks/app/projects/page-75a41d4a90675d93.js +1 -0
  119. package/dist/.next/static/chunks/app/radar/page-4b4aad549dffcd3a.js +1 -0
  120. package/dist/.next/static/chunks/app/report/page-145496e8f0d5bc86.js +1 -0
  121. package/dist/.next/static/chunks/app/settings/page-a38c457b7c57567b.js +1 -0
  122. package/dist/.next/static/chunks/app/sites/page-96a7dcf3c1e9aa19.js +1 -0
  123. package/dist/.next/static/css/94d302c686de7e23.css +1 -0
  124. package/dist/build-id.json +134 -134
  125. package/dist/package.json +1 -1
  126. package/dist/shell/bin/af360-bridge.exe +0 -0
  127. package/lib/mcpActivity.mjs +318 -23
  128. package/package.json +1 -1
  129. package/shell/bin/af360-bridge.exe +0 -0
  130. package/dist/.next/server/chunks/8748.js +0 -1
  131. package/dist/.next/server/chunks/9601.js +0 -1
  132. package/dist/.next/static/chunks/3192-3df9f9fedb9e38b1.js +0 -1
  133. package/dist/.next/static/chunks/5449-42f9c47cf519229a.js +0 -1
  134. package/dist/.next/static/chunks/7482-8061a087c022a160.js +0 -1
  135. package/dist/.next/static/chunks/7897-f8f2f8f462a8bbed.js +0 -1
  136. package/dist/.next/static/chunks/app/ahead/page-2d37c636630d12e7.js +0 -1
  137. package/dist/.next/static/chunks/app/connections/page-7e0dbe13ce939fc9.js +0 -1
  138. package/dist/.next/static/chunks/app/discoveries/page-0ac8bd1bf0178837.js +0 -1
  139. package/dist/.next/static/chunks/app/error-1b4c20b17d1105f5.js +0 -1
  140. package/dist/.next/static/chunks/app/log/page-e54cb98e7adc6b08.js +0 -1
  141. package/dist/.next/static/chunks/app/projects/page-bc124fc3f5f66391.js +0 -1
  142. package/dist/.next/static/chunks/app/radar/page-24540a7c154a41b8.js +0 -1
  143. package/dist/.next/static/chunks/app/report/page-f1ede34a209ac047.js +0 -1
  144. package/dist/.next/static/chunks/app/settings/page-3ca6b23c69637aab.js +0 -1
  145. package/dist/.next/static/chunks/app/sites/page-7becc8e4436c5016.js +0 -1
  146. package/dist/.next/static/css/f0c4f79b3d7295b1.css +0 -1
  147. /package/dist/.next/static/{71f4c38dc0c65050446da → da1c2e66999c334d135d9}/_buildManifest.js +0 -0
  148. /package/dist/.next/static/{71f4c38dc0c65050446da → da1c2e66999c334d135d9}/_ssgManifest.js +0 -0
@@ -1 +0,0 @@
1
- "use strict";exports.id=9601,exports.ids=[6988,9601],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}},59601:(a,b,c)=>{c.d(b,{r_:()=>ak,od:()=>at,ag:()=>as,t7:()=>au});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:"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),F=c(22191),G=c(88804),H=c(87078);let I=new Set(["co","com","org","net","ac","gov","edu","ltd","plc"]);function J(a){let b=a.toLowerCase().split(".").filter(Boolean);return b.length<=2?b.join("."):I.has(b[b.length-2])&&b.length>=3?b.slice(-3).join("."):b.slice(-2).join(".")}let K=[{file:"CNAME",extract:a=>P(a)},{file:"public/CNAME",extract:a=>P(a)},{file:"docs/CNAME",extract:a=>P(a)},{file:"railway.toml",extract:a=>Q(a,/^\s*(?:domain|domains|url|custom_domain)\s*=/i)},{file:"railway.json",extract:a=>Q(a,/"(?:domain|domains|url|customDomain)"\s*:/i)},{file:"wrangler.toml",extract:a=>Q(a,/^\s*(?:route|routes|pattern|custom_domain|zone_name)\s*=/i)},{file:"vercel.json",extract:a=>Q(a,/"(?:alias|domains?|host)"\s*:/i)},{file:"netlify.toml",extract:a=>Q(a,/^\s*(?:from|to|url|base_url)\s*=/i)},{file:"next.config.js",extract:a=>Q(a,/(?:domains|assetPrefix|hostname|destination)\s*[:=]/i)},{file:"next.config.mjs",extract:a=>Q(a,/(?:domains|assetPrefix|hostname|destination)\s*[:=]/i)},{file:"next.config.ts",extract:a=>Q(a,/(?:domains|assetPrefix|hostname|destination)\s*[:=]/i)},{file:"package.json",extract:a=>Q(a,/"homepage"\s*:/i)},{file:"docker-compose.yml",extract:a=>R(a)},{file:"docker-compose.yaml",extract:a=>R(a)},{file:"compose.yaml",extract:a=>R(a)},{file:"Dockerfile",extract:a=>R(a)},{file:"README.md",prose:!0,extract:a=>S(a)},{file:"docs/README.md",prose:!0,extract:a=>S(a)}],L=/\b((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z-]{1,23})\b/gi,M=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"]),N=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"]),O=["your-","your.","yourdomain","example","sample","placeholder","localhost","<",">","mydomain","changeme"];function P(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 Q(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(L))c.push({host:a[1].toLowerCase(),line:e+1+d})}),c}function R(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(L))b.push({host:d[1].toLowerCase(),line:c+1})}),b}function S(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 T(a,b,c=3){let e=[],f=[],g=new Set;for(let h of K){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(O.some(a=>b.includes(a))||M.has(b))return!1;let c=b.split(".");if(c.length<2)return!1;let d=c[c.length-1];return!(N.has(d)||/^\d+$/.test(d)||b.endsWith(".localhost"))}(d.host)&&(!h.prose||function(a,b){if(b.apexes.has(J(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 U(a,b=[],c=()=>{}){let d=function(a,b){let c=new Set;for(let a of b){let b=(0,H.Pm)(a);b&&c.add(J(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 T(b,d);e.push(...a.found),f.push(...a.unread)}return{found:e,unread:f}}function V(a){return`${a.file}:${a.line}`}var W=c(26988);let X=e.EO,Y=new Set(["node_modules",".git",".next","dist","build","out",".venv","venv","__pycache__","coverage",".turbo",".cache","target","vendor",".pytest_cache"]);async function Z(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(Y.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 $={measured:!1,since:0,before:0,after:0,gone:[],added:[],edited:[],standing:[]};function _(a=(0,l.yF)()){return i().join(a,"inventory.json")}async function aa(a=(0,l.yF)()){try{let b=JSON.parse(await d.promises.readFile(_(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 ab(a,b=(0,l.yF)()){try{await d.promises.mkdir(b,{recursive:!0}),await d.promises.writeFile(_(b),JSON.stringify(a),"utf8")}catch{}}async function ac(a){try{let{stdout:b}=await X("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 ad={answered:!1,files:new Set};function ae(a){return`git restore -- "${a}"`}let af=e.EO;function ag(a){return{verdict:a.en,verdictP:a}}function ah(a,b){return{list:{slot:a,items:[b]}}}let ai=globalThis,aj=ai.__af360NmSizes??(ai.__af360NmSizes=new Map);async function ak(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=ai.__af360Engine;if(!c.fresh&&f&&f.key===e&&Date.now()-f.ts<36e5)return al(f,d);let g={key:e,ts:Date.now(),order:[],pending:new Set,deepTotal:0,failed:new Set,unscanned:new Set};ai.__af360Engine=g,aF.clear(),aJ=null,aG.clear();let h=ap.filter(a=>!d.has(a.id)),i={projects:a,cfg:b,skip:a=>{ai.__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=>{ai.__af360Engine===g&&g.order.push(...a)}).catch(()=>{if(ai.__af360Engine!==g)return;g.failed.add(b.id);let a=n.find(a=>a.id===b.id)?.name??b.id;(0,F.logEvent)(`${a} didn't finish this time`)}).finally(()=>{g.pending.delete(b.id)});return al(g,d)}function al(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]}}let am=globalThis;function an(a){let b=ai.__af360Engine,c=am.__af360InvPass;if(c&&c.state===b)return c.p;let d=ao(a);return am.__af360InvPass={state:b,p:d},d}async function ao({projects:a,skip:b}){let c=await aa(),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 Z(h.path),i=function(a,b){if(!a)return{...$,first:!0,reason:"af360 has not looked inside this folder before"};if(!a.complete)return{...$,since:a.ts,reason:a.reason??"af360 could not read this folder last time"};if(!b.complete)return{...$,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 ac(h.path):ad,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 ab(d),{diffs:e,unreadable:f,gitHeld:g}}let ap=[{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",...ag((0,G.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",...ag((0,G.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 aI(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",...ag(1===e.length?(0,G.ph)("disc.noUpstream","{0} has {1} local branches that push nowhere.",a.name,a.noUpstream.length):(0,G.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",...ag((0,G.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",...ag(1===f.length?(0,G.ph)("disc.noGitFolder","{0} is not in git, and it holds {1} files.",a.name,a.files):(0,G.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",...ag((0,G.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 aI(a,60,b,a=>a.path.toLowerCase())){let a=await aC(c.path);if(a)return[{id:`readme:${c.path.toLowerCase()}`,det:"readme-stub",cat:"time-capsule",...ag(c.lastActivity?(0,G.ph)("disc.readmeStubSince",'{0}\'s README has said "{1}" since about {2}.',c.name,a,(0,o.KN)(c.lastActivity).replace(" ago"," back")):(0,G.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,G.pl)("disc.whenToday","today"):1===f?(0,G.pl)("disc.whenTomorrow","tomorrow"):(0,G.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",...ag(null!=c.commitCount?{...(0,G.ph)("disc.anniversaryCommits","{0} turns {1} {2} - {3} commits since the first.",c.name,e,h,c.commitCount),...ah(2,g)}:{...(0,G.ph)("disc.anniversary","{0} turns {1} {2}.",c.name,e,h),...ah(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 aI(a.filter(a=>("dormant"===a.staleness||"forgotten"===a.staleness)&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=(await ay(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",...ag((0,G.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 aI(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 ax(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",...ag((0,G.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,G.ph)("disc.ageYears",`{0} year${1===f?"":"s"}`,f):(0,G.ph)("disc.ageDays","{0} days",e.ageDays),j=h.en;return[{id:"ssh-keys",det:"ssh-keys",cat:"identity",...ag({...(0,G.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),...ah(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 av(process.cwd(),["config","--global","user.email"])??"";if(!c)return[];let d=(await Promise.all(b.map(async a=>({p:a,email:await av(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",...ag((0,G.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",...ag((0,G.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 aI(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",...ag((0,G.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 aI(a.filter(a=>a.isGit&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=await av(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",...ag((0,G.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}/,"AWS key"],[/sk_live_[0-9a-zA-Z]{16,}/,"Stripe live key"],[/ghp_[A-Za-z0-9]{36}/,"GitHub token"],[/github_pat_[A-Za-z0-9_]{22,}/,"GitHub token"],[/AIza[0-9A-Za-z_-]{35}/,"Google API key"],[/xox[baprs]-[0-9A-Za-z-]{10,}/,"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 af("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;for(let a of b.matchAll(f)){let b=g(a[0]);c.add("private key block"===b?"a private key block":`${b} ...${a[0].slice(-4)}`)}c.size>0&&j.push({p:a,samples:[...c].slice(0,4),count:c.size})}catch{b(a.path.toLowerCase())}return 0===j.length?[]:[{id:"history-secrets",det:"history-secrets",cat:"safety",...ag(1===j.length?(0,G.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,G.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,G.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=>`${a.p.name} - ${a.count} key-shaped string${1===a.count?"":"s"}: ${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,W.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",...ag(1===c.length?(0,G.ph)("disc.staleRemoteOne","{0}'s remote points at a name GitHub has moved.",c[0].p.name):(0,G.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 aI(a,40,b,a=>a.path.toLowerCase())){let a=await aE(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",...ag((0,G.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 aI(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",...ag(1===f.length?(0,G.ph)("disc.unbackedIgnored","{0} has no remote, and git is ignoring {1} things inside it.",a.name,a.found.length):(0,G.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",...ag((0,G.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 aH(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",...ag(1===c.length?"deleted"===d.uncommittedKind?(0,G.ph)("disc.depDeleted","{0}'s dependency list was deleted and git has not recorded it yet.",d.name):"added"===d.uncommittedKind?(0,G.ph)("disc.depAdded","{0} has a new dependency list that git has not recorded yet.",d.name):(0,G.ph)("disc.depUncommitted","{0}'s dependency list changed and git has not recorded it yet.",d.name):(0,G.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",...ag(d<0?(0,G.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,G.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 aB(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",...ag(d.truncated?f>1e8?(0,G.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.",aw(e),aw(f)):(0,G.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.`,aw(e),c.length):f>1e8?(0,G.ph)("disc.nodeModulesDormant","Your node_modules folders weigh {0} - {1} belongs to projects you haven't opened in a month or more.",aw(e),aw(f)):(0,G.ph)("disc.nodeModules",`Your node_modules folders weigh {0} across {1} project${1===c.length?"":"s"}.`,aw(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} - ${aw(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 aB(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",...ag(e.truncated?(0,G.ph)("disc.buildArtifactsFloor","At least {0} sits in folders that build and test tools create - af360 stopped measuring some of them before the end.",aw(f)):(0,G.ph)("disc.buildArtifacts","{0} sits in folders that build and test tools create.",aw(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} - ${aw(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 aB(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",...ag(c.truncated?(0,G.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"}`:""}.`,aw(h),f.length):g.length>0?(0,G.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"}.`,aw(h),f.length,g.length):(0,G.ph)("disc.diskWeight",`{0} sits in tool caches across {1} project${1===f.length?"":"s"}.`,aw(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} - ${aw(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 aI(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",...ag(f.length>0?(0,G.ph)("disc.runtimeCensus",`{0} project${1===f.length?" asks":"s ask"} for a Node version this machine is not running.`,f.length):(0,G.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",...ag(b.length>0?b.every(a=>1===a.lacks.length&&"licence"===a.lacks[0])?(0,G.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,G.ph)("disc.frontDoorReadme",`{0} project${1===b.length?" with a remote has":"s with a remote have"} no README.`,b.length):(0,G.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,G.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=aI(a,40,b,a=>a.path.toLowerCase());for(let a of d){let b=await aE(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 aB(i().join(a.path,"node_modules",f),b);let[,h]=e[0];return[{id:"dep-versions",det:"dep-versions",cat:"health",...ag(g>0?(0,G.ph)("disc.depVersionsSize","{0} exists in {1} different versions across the {3} projects af360 read - {2} of the same library.",f,h.size,aw(g),d.length):(0,G.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 aH(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",...ag(1===f?(0,G.ph)("disc.depArrival","{0} {1} joined {2}'s dependency list, recorded on {3}.",b.name,b.version,a.name,c):(0,G.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",...ag(1===f.length?(0,G.ph)("disc.depUnread","af360 has no dependency answer for {0}.",f[0].name):(0,G.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",...ag(1===g.length?(0,G.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,G.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 as(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",...ag(c.bounded?(0,G.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,G.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 at(a,b);return 0===c.count?[]:[{id:"todo-census",det:"todo-census",cat:"time-capsule",...ag(c.bounded?(0,G.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,G.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 aI(a.filter(a=>a.isGit&&!a.looksDownloaded),40,b,a=>a.path.toLowerCase())){let a=await av(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",...ag((0,G.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 aB(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",...ag((0,G.ph)("disc.duplicateDownloads",`{0} downloaded folder${1===f.length?"":"s"} share a name with a project you already have - {1} sitting idle.`,f.length,aw(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} - ${aw(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 aL(a.urls,b))){let a=await aM(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",...ag(0===e.missing.length?(0,G.ph)("disc.ogSharp","{0} looks sharp when shared - this is the card people see.",f):e.missing.includes("image")?(0,G.ph)("disc.ogSharedBare","{0} shows up bare when shared - no {1}.",f,e.missing.join(", no ")):(0,G.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 aL(a.urls,b))){let a=await aM(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",...ag((0,G.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 aL(a.urls,b))){let a=await aM(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",...ag(a.html.length>=4e5?(0,G.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,G.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 aL(a.urls,b))){let a=await aM(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",...ag((0,G.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 aM(`https://${a}/`);b&&b.ok||c.push({id:`www:${d.id}`,det:"redirect-chain",cat:"site",...ag(b?(0,G.ph)("disc.wwwMismatch","{0} answers but {1} returns an error - links with the other form strand visitors.",f,a):(0,G.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 aL(a.urls,b))){let a=await aM(d.url);if(!a){b(d.url);continue}if(!a.ok)continue;let e=await aP(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",...ag((0,G.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 aL(a.urls,b))){let a=await aM(d.url);if(!a){b(d.url);continue}if(!a.ok)continue;let e=new URL(a.finalUrl).origin,f=await aP(e+"/robots.txt");if(f&&/^sitemap:/im.test(f))continue;let g=await aQ(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",...ag((0,G.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 U(a,b.urls.map(a=>a.url),c),e=new Set(b.urls.map(a=>(0,H.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=V(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",...ag((0,G.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,H.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=V(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",...ag((0,G.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",...ag((0,G.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 an(a),c=[];for(let{p:a,d}of b.diffs)if(function(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)}(d)&&(c.push({id:`gone:${a.path.toLowerCase()}`,det:"inventory-burst",cat:"time-capsule",...ag((0,G.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,F.logEvent)(`${d.gone.length} files are gone from ${a.name} since the last look.`)),d.measured&&d.added.length+d.edited.length>=30){let b=d.added.length+d.edited.length;c.push({id:`burst:${a.path.toLowerCase()}`,det:"inventory-burst",cat:"time-capsule",...ag((0,G.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",...ag((0,G.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 an(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)??ad).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",...ag((0,G.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:",ae(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:ae(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(aI(a.urls,5,b,a=>a.id).map(a=>{try{return new URL(a.url).hostname}catch{return null}}).filter(Boolean))]){let a=await aR(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",...ag((0,G.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,F.logEvent)(`${d}'s DNS records changed - ${e.length} record type${1===e.length?"":"s"}.`))}else(0,F.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}}],aq={".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"},ar=new Set(["node_modules",".git",".next","dist","build","out",".venv","venv","__pycache__","coverage",".turbo"]);async function as(a,b=()=>{}){let c=ai.__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()){ar.has(l.name)||l.name.startsWith(".")||await k(h,b+1);continue}let m=aq[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 ai.__af360Estate=l,l}async function at(a,b=()=>{}){let c=ai.__af360Census;if(c&&Date.now()-c.ts<36e5)return c;let d={ts:Date.now(),...await aT(a,b)};return ai.__af360Census=d,d}async function au(a,b=()=>{}){let c=0;for(let d of a)("dormant"===d.staleness||"forgotten"===d.staleness)&&(c+=await aB(i().join(d.path,"node_modules"),b));return c}async function av(a,b){try{let{stdout:c}=await af("git",(0,e.tQ)(b,a),{cwd:a,timeout:8e3,windowsHide:!0,env:(0,e.tl)()});return c.trim()||null}catch{return null}}function aw(a){return a>=1e9?`${(a/1e9).toFixed(1)} GB`:a<1e6?"under 1 MB":`${Math.round(a/1e6)} MB`}async function ax(a){try{return await d.promises.access(a),!0}catch{return!1}}async function ay(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 az=new Set([".git"]);async function aA(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())az.has(h.name)||(g+=await aA(f,b+1,c,e));else if(h.isFile())try{g+=(await d.promises.stat(f)).size}catch{}}}return g}async function aB(a,b=()=>{},c={truncated:!1}){let d=a.toLowerCase(),e=aj.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 aA(a,0,Date.now()+2e4,f);return aj.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 aC(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 aD=new Map;async function aE(a){let b=i().join(a,"package-lock.json"),c=b.toLowerCase();if(aD.has(c))return aD.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 aD.set(c,e),aD.size>100&&aD.clear(),e}let aF=new Map,aG=new Map;function aH(a,b){let c=aG.get("estate");if(c)return c;let d=C(a.map(a=>({name:a.name,path:a.path,isGit:a.isGit})),b);return aG.set("estate",d),d}function aI(a,b,c,d){for(let e of a.slice(b))c(d(e));return a.slice(0,b)}let aJ=null;async function aK(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 aM(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 aL(a,b){aJ||(aJ=aK(a));let c=await aJ;for(let a of c.skipped)b(a);return c.reached}function aM(a){let b=a.toLowerCase(),c=aF.get(b);if(c)return c;let d=aN(a);return aF.set(b,d),d}async function aN(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 aO(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 aO(c,d,b,e)}finally{clearTimeout(d)}}return null}catch{return null}}function aO(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 aP(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 aQ(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 aR(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 aS=new Set([".ts",".tsx",".js",".jsx",".mjs",".py",".css",".html",".md",".go",".rs",".java"]);async function aT(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()){ar.has(m.name)||m.name.startsWith(".")||await h(l,f+1);continue}if(aS.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}}},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";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3192],{436:(e,t,o)=>{o.d(t,{Ac:()=>a,Lf:()=>i,TX:()=>r,UI:()=>d,eV:()=>c});var n=o(9178);let a=[{href:"/radar",icon:"radar",label:"Radar"},{href:"/projects",icon:"projects",label:"Projects",count:"projects"},{href:"/sites",icon:"sites",label:"Sites",count:"sites"},{href:"/discoveries",icon:"discoveries",label:"Discoveries",count:"discoveries"},{href:"/connections",icon:"connections",label:"Connections"},{href:"/report",icon:"report",label:"Estate Report"},{href:"/ahead",icon:"ahead",label:"Ahead"},{href:"/log",icon:"log",label:"Log"}],i=[{href:"/settings",icon:"settings",label:"Settings"}],s=[];function r(e){var t,o;return null!=(o=null==(t=[...a,...i,...s].find(t=>t.href===e))?void 0:t.label)?o:""}let c={unsaved:"Changes you have made but not saved into the project's history yet.",deleted:"Files that were in this project and are not on the disk any more. If they were committed, git still holds them.",unpushed:"Saved on this machine, but not sent to the online copy yet.",onlyHere:"There is no online copy. If this disk failed, the work would be gone.",noHistory:"This folder has no version history, so there is nothing to go back to.",dormant:"Nothing has changed in this project for a long time.",duplicate:"Another folder on this machine holds the same repository - af360 read both remotes and they match.",sharesName:"A folder elsewhere on this machine has the same name, and af360 read both remotes: it is a different repository, not a copy.",clean:"Nothing af360 watches is waiting to be saved or pushed.",downloadedCopy:"This looks like something you downloaded rather than something you wrote.",remote:"Whether this project's history also lives on a remote you push to - the copy that survives this machine.",ignore:"Stop mentioning this. It stays scanned, and comes back only if it changes.",later:"Quiet this until tomorrow. The row stays here, dimmed, and stops counting.",watch:"Add this folder so af360 looks at it on every scan.",unwatch:"Stop looking at this. It is only removed from af360, never from your disk.",check:"Mark that you have seen this and are dealing with it.",pin:"Keep this at the top of the list.",copyPath:"Copy this folder's location so you can paste it elsewhere.",scanNow:"Look at every watched folder, site and date again right now.",details:"Show what was seen, and what is worth doing about it.",fix:"See what is happening here and what to do about it.",copyCommands:"Put these exact commands on your clipboard, ready to paste into a terminal.",looked:"the last full look at your folders - sites are checked every minute while af360 is running",copyContext:"Put a short brief about this project on your clipboard, ready to paste into an AI assistant. Secrets are left out.",copyEstateReport:"Put the whole estate report on your clipboard, ready to paste into an AI assistant. Folder paths are shortened and secrets are left out.",verdict:"Counts only the work that exists nowhere but this machine.",moreActions:"The actions you rarely need for this row.",tagLosable:"This work exists nowhere but this machine.",tagMoney:"Costly or public if it lapses, but not lost.",tagHygiene:"Worth tidying. Nothing is at risk today.",age:"How long this has been true.",keyboard:"Show the keyboard shortcuts.",ssl:"The certificate that puts the padlock in the address bar.",domain:"The yearly renewal that keeps the web address yours.",detectors:"A check af360 runs while it looks around. Turn one off and it stops running.",muteSwitch:"On: this check runs. Off: it stops running, and the Discoveries page says so.",updateSwitch:"On: once a day af360 asks the npm registry - the tester channel - whether a newer af360 exists. Nothing about you is sent. Store installs are updated by Windows instead.",chooseExclude:"Pick a folder every scan should skip, even inside a folder you watch.",markdown:"A plain text file that most editors, note apps and AI assistants read.",estate:"Everything you have built: the folders, projects and sites af360 watches.",simpleMode:"Plain English only",devMode:"Adds codes, timings, git detail",theme:"Switch between the day and night look",editEntry:"Change the name, the date, your note or the money tag"},d={common:{scanning:"Scanning…",loading:"Looking around…",checking:"checking…",undo:"Undo",save:"Save",done:"Done",remove:"Remove",close:"Close",cancel:"Cancel",back:"Back",show:"Show ▾",hide:"Hide ▴",collapse:"Collapse ▴",details:"Details ▾",less:"less ▴",more:"more ▾",somethingWrong:"Something went wrong - try again."},appbar:{aria:"Estate",shortcuts:"Shortcuts",shortcutsTip:"Show the keyboard shortcuts.",roomAria:"Current room"},log:{title:"Log",purpose:"Everything af360 has recorded on this machine, newest first.",filterAll:"All",filterAgents:"Agents",filterConnections:"Connections",filterClosed:"Resolved",untaggedNote:"af360 tags three kinds of entry: an agent asked something, a connection changed, a risk ended. Everything else is an untagged line. All shows everything.",dayToday:"Today",dayYesterday:"Yesterday",floor:"Seven days is as far back as this record goes.",empty:"af360 has not written anything down yet - scans, site checks and agent questions all land here."},shell:{clockSkew:n.lt.at,clockSkewCore:n.lt.core,navAria:"Main",modeAria:"Mode",themeAria:"Look",moreGroup:"More",railAria:"Live",modeSimple:"Simple",modeDev:"Dev",themeDay:"Day",themeNight:"Night",guardianSoon:"soon",footBy:"by",footBrand:"Afeleos",footLine:"Your files stay on your machine.",railLive:"Live",railLog:"Log \xb7 7 days",railLogUnknown:"Log",railLogSince:e=>"Log \xb7 since ".concat(e),railLogSinceTime:e=>"Log \xb7 since ".concat(e),railLogTruncated:e=>"Log \xb7 since ".concat(e," \xb7 limit reached"),railLogUnreadable:e=>"".concat(e," ").concat(1===e?"entry":"entries"," could not be read"),railLogEmpty:"Log \xb7 nothing yet",railNext:"Next",railConfigFault:"I can't read your settings",railConfigFaultSub:"so I don't know what to watch",railQuiet:"quiet so far",railQuietSub:"incidents and scans land here",railNoEvents:"no incidents yet",railNoEventsSub:"scans land here",railNoRecord:"nothing recorded yet",railNoRecordSub:"af360 hasn't finished a scan",railOlder:"everything older is in the Log",railSwept:"git status swept",railMissing:"still missing",railNextSiteCheck:"site check",railNextScan:"folder scan",railNextSsl:"SSL judgment"},roomError:{invite:"This room didn't open",line:"Something in this room went wrong, so af360 stopped drawing it rather than show you something half-built. af360 has not judged the other rooms from here.",fixLead:"What to do:",fixRetry:"Try the room again. If it keeps failing, the other rooms will tell you whether they are working.",retry:"Try again"},configFault:{invite:"I can't read your settings",line:"Your settings file is where it should be, but I can't read it, so I don't know what you asked me to watch. I have not changed or written over it.",whereLabel:"The file is here:",fixLead:"What usually fixes it:",fixOpen:"Open the file and check it is complete - a half-written or hand-edited file is the usual cause.",fixRestore:"If you keep backups, restore your last good copy over it.",fixRename:"Or rename the file. af360 will start fresh, and your old one stays on disk for you to look at.",retry:"Try again",recovered:"Your settings file could not be read. af360 restored the previous copy."},radar:{title:"Radar",purpose:"what needs you right now, in one place",scanNow:"Scan now",shareReport:"Share a report",shareReportTip:"A tidy file of what needs attention, to share or paste into an AI",looked:"looked",needsYou:"Needs you",projects:"Projects",projectsMeta:"checking backups, unsaved work, activity",watchMore:"Watch more folders",sites:"Sites",sitesMeta:"checked every minute while af360 is running",discoveries:"Discoveries",discoveriesMeta:"what the estate is quietly telling you",discoveriesTop3:"showing the top 3",ignored:"Ignored",ignoredLabel:"Ignored",ignoredStrip:e=>"Ignored: ".concat(e),ignoredMeta:"still scanned - they resurface only if things change",watchAgain:"Watch again",stillIgnore:"Still ignore",findIt:"Find it",stopWatching:"Stop watching",stopWatchingFolder:"Stop watching this folder",stopWatchingSite:"Stop watching this site",ignore:"Ignore",ignoreIt:"Ignore it",keepWatching:"Keep watching",later:"Later",check:"Check",handled:"handled ✓",folder:"Folder",copyPath:"Copy path",copyAddress:"Copy address",copied:"Copied ✓",copyFailed:"Couldn't copy",whatISaw:"What I saw",worthDoing:"Worth doing",viewGrid:"Grid",viewRows:"Rows",viewAria:"How to show this section",pin:"Pin to top",pinLabel:"Pin",ignoreLabel:"Ignore",unpin:"Unpin",emptyInvite:"Nothing to watch yet - which makes this the perfect starting line.",emptyLine:"af360 keeps an eye on the folders you work in and the websites you own, and tells you when one of them needs you - work that has no copy anywhere, a site that stops answering, a renewal creeping up.",emptyPickFolder:"Choose a folder to watch",emptyPickSite:"Watch a website",emptySomeday:"Building something someday? af360 will be ready - it costs nothing to keep around.",emptyBack:"Back",sitePlaceholder:"yoursite.com",siteNamePlaceholder:"a name for it (optional)",siteAddrAria:"Website address",siteNameAria:"Name for this website",siteAdd:"Watch it",siteAdding:"Adding…",siteAdded:"Watching it. af360 checks it every minute while it is running.",firstCheck:"first check under way…",historyBuilds:"history builds",sparkAria:"Response time, last 24 hours",proposals:"Worth watching",proposalsMeta:"found in your own project files",proposalNamedIn:"named in {0} - line {1}",proposalIn:"in {0}",proposalChanged:"you set this aside when it was named in {0}; it is named in {1} now",proposalNotThis:"Not this one",proposalSetAside:"Set aside. af360 won't ask again unless the evidence changes.",seeAll:e=>"See all ".concat(e," discoveries →"),needsYouAlways:"all shown, always \xb7 ordered by consequence",tagLosable:"losable",tagMoney:"money",tagHygiene:"hygiene",moreActions:"More",moreActionsAria:"More actions for this finding",effIgnore:"hide until it changes",effLater:"resurfaces tomorrow",effStillIgnore:"keep it hidden",effStopWatching:"removed from af360 only",effPin:"keep on top",effUnpin:"back in normal order",fix:"Fix",ignoredStill:"still scanned \xb7 resurfaces on change",ignoredShow:"Show",ignoredHide:"Hide",seeAllProjects:e=>"See all ".concat(e," projects →"),seeAllSites:e=>"See all ".concat(e," sites →"),moreCleanBacked:e=>"and ".concat(e," more with nothing needing you"),moreClean:e=>"and ".concat(e," more, all clean"),moreProjects:e=>"and ".concat(e," more on the Projects page"),moreSitesUp:e=>"and ".concat(e," more with nothing needing you"),moreSites:e=>"and ".concat(e," more on the Sites page"),bannerLead:"This is a",bannerStrong:"sample estate",bannerRest:"so you can see everything working - try Check, Ignore, and Later.",bannerLink:"Point af360 at your real projects",bannerTail:"and it becomes yours.",ignoredToast:"Ignored {0} - it's not gone, it moved to the Ignored line under Needs you (still scanned).",confirmProject:"This project isn't fully backed up. Ignore anyway? I'll stop mentioning it.",confirmProjectShort:"This project isn't backed up. Ignore anyway? I'll stop mentioning it.",confirmSite:"This site is down right now. Ignore anyway? I'll stop mentioning it.",movedTail:"moved - pick its new home. The old entry is replaced."},unread:{reason:"a scan that did not finish",what:e=>"af360 could not finish reading ".concat(e,"."),evidence:"git did not answer for this folder, so nothing below is a verdict",why:"af360 will not guess - it would rather say it could not look than tell you something untrue.",step:"Nothing to do: the next scan tries again. If it keeps saying this, the folder or git itself is the thing to check."},session:{verdictHint:"click to see them",verdictJumpAria:"Show the work that would be lost",wereIn:"You were in",uncommitted:e=>"".concat(e," file").concat(1===e?"":"s"),uncommittedTail:"uncommitted",clean:"clean",lastCommit:"last commit",touched:"touched",noRecent:"Nothing recent - your estate is below",subline:"most recent activity across your watched folders",copyPath:"Copy path",copyContext:"Copy project brief",copyContextShort:"Copy project brief",copying:"Assembling…",copied:"Copied ✓",copyFailed:"Couldn't copy",copyHandoff:"Copy for your agent",handoffCopied:"Copied: what needs doing, the commands for each, and what a dead disk would cost. Secrets masked.",fix:"Fix",fixHeadingWhat:"What is happening",fixHeadingSteps:"What to do",fixHeadingCommands:"Commands",copyCommands:"Copy commands",commandsCopied:"Commands copied ✓",briefToast:e=>"Copied: ".concat(e.join(", "),"."),briefFailed:"Couldn't assemble the brief - try again.",briefRedacted:"secrets left out",actSave:e=>"Save ".concat(e),actPush:e=>"Push ".concat(e),actBackUp:e=>"Back up ".concat(e),actDuplicate:e=>"Decide on ".concat(e,"'s duplicate"),actSiteDown:e=>"".concat(e," is down"),actRenew:e=>"Renew ".concat(e),actCertLate:e=>"Chase ".concat(e,"'s certificate"),actFind:e=>"Find ".concat(e),actDecide:e=>"Decide on ".concat(e),actOffline:"Check this machine's connection"},stream:{starting:"still looking - the deeper checks are starting, results land here as they finish",counting:(e,t)=>"still looking - ".concat(e," of ").concat(t," deeper check").concat(1===t?"":"s"," done, results land here as they finish"),railWatchedNothing:e=>"watched since ".concat(e," - nothing has changed"),railScan:(e,t)=>"scanning ".concat(e," folder").concat(1===e?"":"s").concat(t>0?" \xb7 ".concat(t," site").concat(1===t?"":"s"):""," \xb7 deeper checks next")},keys:{title:"Keyboard shortcuts",sub:"They do not fire while you are typing in a box - except Escape, which always closes what is open.",close:"Close",aria:"Keyboard shortcuts",rooms:"Go to a room",actions:"Do something",goRadar:"Radar",goDiscoveries:"Discoveries",goReport:"Estate Report",goAhead:"Ahead",goSettings:"Settings",doScan:"Scan now",doCopy:"Copy the project brief for the project you were in",doHelp:"Show this list",doEsc:"Close this list",copiedToast:"Project brief copied - paste it into an assistant.",noProject:"af360 has no current project to copy context for."},discoveries:{title:"Discoveries",purpose:"what af360 noticed about the estate, in one place",backToRadar:"Back to Radar",loading:"Looking for discoveries…",emptyInvite:"No discoveries yet.",emptyLine:"Watch some folders on Radar and the estate starts telling you things - unpushed work, forgotten costs, sites worth a look.",goRadar:"Go to Radar",bannerLead:"Sample discoveries -",bannerLink:"point af360 at your real projects",bannerTail:"and these become yours.",mutedTitle:"Muted in Settings, so not running",failedTitle:"Didn't finish this time",failedTail:"Anything these would have found is missing from this page. The next scan tries again.",unmute:"Unmute in Settings →",found:"Found:",worth:"Worth doing:",showMe:"Show me ▾",seeCard:"See the card it sends ▾",fixSteps:"Fix steps",whatISaw:"What I saw",ogBare:"No title, no description, no image - the link shows up naked.",ogNoTitle:"(no title)",stillLooking:e=>"still looking - ".concat(e," deeper check").concat(1===e?"":"s"," running, results land here"),cat:{"time-capsule":"Time capsule",safety:"Safety",money:"Money",identity:"Identity",health:"Health and housekeeping",site:"Your sites"}},projects:{title:"Projects",purpose:"every folder af360 watches, all on one page",backToRadar:"Back to Radar",needing:e=>"".concat(e," need").concat(1===e?"s":""," you, the rest are clean"),allClean:"all clean",emptyInvite:"No projects watched yet.",emptyLine:"Point af360 at a folder on Radar and everything inside it appears here.",goRadar:"Go to Radar"},sites:{title:"Sites",purpose:"every site af360 watches, all on one page",backToRadar:"Back to Radar",checking:"Checking your sites…",answering:(e,t)=>"".concat(e," of ").concat(t," answering"),emptyInvite:"No sites watched yet.",emptyLine:"Add a site on Radar and af360 checks it every minute while it runs.",goRadar:"Go to Radar",groupYours:"Your sites",unitSite:"site",unitSites:"sites",newGroup:"New group",groupNameAria:"Group name",groupNamePlaceholder:"Client work",createGroup:"Create",removeGroup:"Delete group",removeGroupNote:"The sites in it move back to Your sites. None of them are removed.",fileIn:"Group",groupServices:"Services you depend on",servicesPurpose:"af360 asks each of these one question: did it answer? None of af360's site checks run here.",svcAnswered:"af360 asked, and it answered.",svcErrorReply:"af360 asked, and the reply was an error.",svcNoAnswer:"af360 asked, and got no answer.",svcAsking:"af360 is asking…",svcNotTheWorld:"This is what happened when af360 asked, from this machine. It is not a report on the service.",svcViaStatus:"af360 asks its published status page.",svcViaHome:"It publishes no status page, so af360 asks its home page.",removeService:"Remove",servicesNoPresets:"af360 has no service presets yet.",addService:"Add a service",servicesNoneAdded:"af360 contacts nothing here until you add it."},card:{heroLabel:"lines of code across {0} projects",heroSub:"Counted on this machine, not from an API.",gridHead:"Twelve weeks of commits",statLanguages:"Languages",statAge:"Estate age",statCommits:"Commits, twelve weeks to today",statClean:"Clean",statReadProjects:"Repositories read",statBusiestDay:"Busiest day",statBusiestProject:"Busiest project, {0}",emptyEstate:"Nothing measured yet.",statBranches:"Local branches",capped:"Measured across the {0} projects af360 read.",seal:"Measured on this machine \xb7 build {0}",sealNoBuild:"Measured on this machine \xb7 no build fingerprint",foot:"af360 by Afeleos",button:"Save the card"},report:{title:"Estate Report",purpose:"the whole picture in one look, kept on this machine",heading:"Your estate, at a glance",asOf:"as of",sub:"The whole picture in one look. This stays on your machine; your personal view - Share a report makes the file meant for others.",download:"Save as image",downloadText:"Save as text",vNeeding:e=>"need".concat(1===e?"s":""," your attention"),vClean:"clean and backed up",vSites:"sites answering",rightNow:"Right now",rUnsaved:"Unsaved work at risk",rUnsavedVal:(e,t)=>"".concat(e," project").concat(1===e?"":"s",", ").concat(t," change").concat(1===t?"":"s"),rNone:"none ✓",rUnpushed:"Commits only on this machine",rUnpushedVal:(e,t)=>"".concat(e,", in ").concat(t," project").concat(1===t?"":"s"),rEverythingElse:"Everything else",rBackedUp:"backed up ✓",rNoBackup:"No copy anywhere but here",rNoBackupVal:e=>"".concat(e," project").concat(1===e?"":"s"),rSites:"Sites",rSitesAllUp:"all up, no incidents",rSitesUpNow:"all up right now",rSitesDown:e=>"".concat(e," not answering"),rNext:"Next date to know",dateIn:(e,t)=>"".concat(e," in ").concat(t,"d"),inNumbers:"Your estate in numbers",langAria:"Share of your code by language",langRest:"everything else",linesWord:"lines",fLines:"lines of code",fProjects:"projects",fProjectsExcluded:e=>"".concat(e," downloaded ").concat(1===e?"copy":"copies"," excluded"),fLanguages:e=>"languages, mostly ".concat(e),fCommits:"commits tracked",worthKnowing:"Worth knowing",worthKnowingSub:"true and useful, no action needed - the facts behind your estate",detailsLabel:"Details",catTimeCapsule:"time-capsule",catHousekeeping:"housekeeping",catHistory:"history",catMilestone:"milestone",wTodoFact:(e,t)=>"Your oldest note to yourself is still open, from ".concat(e,': "').concat(t,'"'),wTodoWhy:"the estate's eldest note to yourself, still waiting",wTodoFrom:e=>"the estate's eldest note to yourself, from ".concat(e),wSpaceFact:e=>"".concat(e," of downloaded code libraries sit in projects you have not touched in months"),wSpaceWhy:"free to delete, one command brings it back",wBusiestFact:(e,t)=>"".concat(e," is your busiest project, ").concat(t.toLocaleString()," commits"),wBusiestWhy:"the most-worked-on thing you own",wBirthdayFact:(e,t,o)=>"".concat(e," turns ").concat(t," on ").concat(o),wBirthdayWhy:"counted from its first commit",imageFoot:"runs on your machine \xb7 your files stay on it",bannerLead:"Sample report -",bannerLink:"point af360 at your real projects",bannerTail:"and these become your numbers.",counting:"Counting the estate… first time takes a minute.",failed:"The report didn't load - reload the page to try again.",emptyInvite:"Nothing to report yet.",emptyLine:"Watch a folder on Radar and the report writes itself - lines, languages, birthdays, all of it.",goRadar:"Go to Radar",todosTip:"Notes you left in your own code marking something still to do. af360 reads them where they sit and changes nothing.",reclaimableTip:"Space held by the downloaded code libraries (node_modules) of projects you have not touched in months. Deleting them frees the space; one command puts them back.",busiestTip:"Counted from the commits in each watched project's own history. It measures how often you saved work, not how long you spent.",birthdayTip:"Counted from the first commit in the project's history, which is the earliest date the project can prove about itself."},ahead:{title:"Ahead",purpose:"everything af360 knows a date for, before it surprises you",next90:"Next 90 days",further:"Further out",anniversaries:"Anniversaries",anniversariesMeta:"your estate's birthdays",undetermined:"No renewal date",addYourOwn:"Add your own",add:"Add",adding:"Adding…",reading:"Reading certificates…",emptyInvite:"Nothing on the horizon.",emptyLine:"Add trials or renewals below, or watch a site on Radar to track its certificate.",added:"On the timeline.",namePlaceholder:"hosting trial, license renewal…",notePlaceholder:"what happens then?",nameAria:"Name",dateAria:"Date",noteAria:"Note",bannerLead:"Sample timeline -",bannerLink:"watch your real sites",bannerTail:"and their certificate dates appear here, judged - with domain dates too if you turn that on.",weekJudg:"your week, counted on this machine - a fresh one starts Monday",nothingWatched:"nothing watched yet",nothingToDo:"nothing to do",kind:{ssl:"SSL certificate",domain:"domain renewal",manual:"your entry"},yours:"you added this",costTag:"costs money",costLabel:"it costs money when it comes",costAria:"Costs money",edit:"Edit",editAria:"Edit this entry",save:"Save",cancel:"Cancel",saved:"Changed.",removedNamed:"{0} removed.",done:"Done",undone:"Not done",doneMeta:"dealt with",passed:"passed",passedLine:"This date has passed.",reDate:"Give it a new date",secSuggested:"Worth tracking",suggestedMeta:"found on this machine - af360 adds nothing on its own",suggestAdd:"Track it",suggestNo:"Not this one",suggestSetAside:"Set aside.",suggestFilled:"Filled in below - check it and press Add.",suggestFromCert:"{0}'s certificate expires on this date, and af360 has no sign it renews itself"},settings:{title:"Settings",purpose:"preferences - everything lives in one folder on your machine",secWatching:"Watching",watchingHelp:"The folders af360 watches. It scans these on a rhythm; it also looks in the usual places when you ask it to find projects, and it shows you exactly where it looked.",watchingEmpty:"No folders yet - af360 is not looking anywhere.",watchingAdd:"Add a folder…",watchingPick:"Watch this folder",watchingAlready:"Already watching this folder.",watchingProject:"project",watchingProjects:"projects",watchingUnclaimed:"outside every folder above",stopWatching:"Stop watching",watchStopped:"{0} is no longer watched.",connHelp:"Let an AI agent ask af360 about this estate. af360 answers with what it last saw, and it answers read-only: nothing an agent asks can change anything.",connLooking:"Looking for AI tools on this machine…",connNone:"af360 could not find any of the AI tools it knows how to connect to on this machine.",connConnect:"Connect to",connDisconnect:"Disconnect",connConnected:"Connected",connNotConnected:"Not connected",connElsewhere:"Points at another af360",connUnverified:"af360 can't tell which af360 this points at",connOldShape:"Points at this af360 the old way - press Reconnect",connGitHubRefused:"GitHub refused this connection",connGitHubStale:"af360 cannot renew this connection",connUnreadable:"af360 can't read this one's settings file",connAlsoWorks:"Also works with {0} when installed.",connWillWrite:"af360 will add this to {0}'s settings file:",connWillRemove:"af360 will take this out of {0}'s settings file:",connWillRepoint:"af360 will point {0} at this af360 instead - it is set to a different one:",connLineCount:"{0} line(s) added, {1} removed, from line {2}.",connWillBackup:"A copy of the file as it is now will be saved first, at {0}",connConfirmWrite:"Write this change",connConfirmRemove:"Remove this",connRefused:"af360 stopped, and wrote nothing.",connWrote:"af360 is connected to {0}.",connRemoved:"af360 is no longer in {0}'s settings.",connBackupAt:"The file as it was is at {0}",connRestart:"Restart {0} to finish - it reads this when it starts.",connDropped:"{0} rewrote its own settings when it closed and af360's entry went with it. This happens, and it is not something you did.",connReconnect:"Reconnect",connRunning:"{0} is running - it may drop this when it closes. Reconnecting later is one click.",secLooking:"Looking around",cadenceLabel:"How often should I look around?",cadenceHelp:"Disk scan runs on open, then on this rhythm",cadenceAria:"Scan cadence",watchLabel:"Keep checking after I close the window",watchHelp:"af360 keeps checking your estate after you close the window. It stops when you shut down, restart or sign out, and you can quit it any time from its icon near the clock.",watchAria:"Keep checking after the window closes",startupLabel:"Start af360 when I sign in",startupHelp:"af360 opens with Windows and starts watching straight away. It does not run before you sign in, and you can turn it off here or in Task Manager.",startupAria:"Start af360 when I sign in to Windows",startupBlocked:"Turned off in Task Manager. Switch it back on there, under Startup, and af360 starts with Windows again.",startupPolicyOff:"af360 does not start with Windows on this machine, and that is set by policy rather than by you. Whoever manages this machine decides it, and af360 does not override them.",startupPolicyOn:"af360 starts with Windows on this machine, and that is set by policy rather than by you. Whoever manages this machine decides it, and af360 does not override them.",cadenceHourly:"Every hour",cadence6h:"Every 6 hours",cadenceDaily:"Once a day",cadenceCustom:"Custom…",customLabel:"Custom rhythm",customHelp:"Every how many hours? 1 to 24 - that is how often af360 looks while it is running",hoursAria:"Hours",secAppearance:"Appearance",viewLabel:"Default view",viewHelp:"Grid or rows - each section remembers your own override",viewAria:"Default view",viewCards:"Grid",viewRows:"Rows",langLabel:"Interface language",langHelp:"Room names, buttons and section titles. Your folder paths, findings and the sentences af360 writes about your estate stay in English.",langAria:"Interface language",langEn:"English",langAr:"العربية",secData:"Your data",dataLabel:"~/.af360/config.json",dataHelp:"Your watched folders, sites, entries and preferences live in the ~/.af360 folder on your machine, nowhere else. af360 never changes anything inside your projects: it reads them and reports. No accounts, no telemetry. af360 runs a small server on this machine so the window has something to talk to, and it is not reachable from anywhere else.",domainLabel:"Check when my domains renew",domainHelp:"Off by default. Turning it on lets af360 ask a public registry (rdap.org) when the domains behind your sites are due to renew, so those dates join the Ahead timeline with everything else. It sends the site's full address - including the part in front of the domain, which is usually your project's name - and nothing else: no file names, no contents, nothing about you or this machine. The request also carries af360's own name and version, and the answer format it can read. Left off, af360 still watches your sites and still reads their certificates; it just will not know their renewal dates.",updateLabel:"Tell me about new versions",updatePackaged:"Updates arrive through the Microsoft Store. af360 makes no check of its own.",updateHelp:"Once a day, af360 asks the public npm registry whether a newer af360 exists and prints one line if there is. That registry is the tester channel: it is where af360 installed with npm gets its updates. An af360 installed from the Microsoft Store is updated by Windows itself, automatically, and this check has nothing to tell it. It sends nothing about you or this machine. Everything else af360 does online happens because you asked for it, and each one has its own switch.",on:"On",off:"Off",secHow:"How af360 works",how:["It reads the folders you choose, here on this machine, and never changes anything inside them.","It looks at them when you open af360, then again on the rhythm you set above.","While af360 is running, it checks each site you watch once a minute.","It reads certificate dates for those sites, and the dates you add yourself, and says which ones actually need you. Domain renewal dates too, if you turn that on.","It points out quiet facts it noticed while looking. Those are suggestions, not alarms.","It writes its own file in the ~/.af360 folder. The one other thing it can write is an AI tool's config file, and only when you press Connect - it shows you the exact change first."],documentsHelp:"The two documents you accepted when af360 started. They are stored in af360 and open here; nothing opens a browser.",documentsOpen:"Read",secRules:"Rules",rulesMeta:"both feed the engine directly",excludeLabel:"Don't watch here",excludeHelp:"Folders every scan skips - even inside watched folders",excludePick:"Don't watch this folder",alreadyExcluded:"Already skipped ✓",chooseFolder:"Choose a folder…",customBad:"Pick a whole number of hours between 1 and 24.",muteLabel:"Mute a discovery type",muteHelp:"Muted detectors stop running; the Discoveries page says which are off.",muted:"Muted",running:"Running",cat:{money:"Money",identity:"Identity","time-capsule":"Time-capsule",safety:"Safety",health:"Health",site:"Site"}},connections:{title:"Connections",purpose:"Who may ask af360 about this estate, and what af360 may look at on your behalf.",secIn:"Agents reach in",inHelp:"A connected assistant can ask af360 about this estate on its own, without you doing anything. af360's answers quote text found on this machine - folder and branch names, commit messages, the notes files a project keeps for assistants - marked as content af360 read, never as instructions to follow. Connecting is yours to choose.",secOut:"af360 reaches out",outHelp:"af360 can also look outward - read-only - at services your estate already lives on, and fold what they know into the picture.",githubName:"GitHub",githubOffer:"CI results, pull requests, issues - read-only, from this machine only",githubBlind:"Until this is connected, af360 cannot check your remotes - if one has been renamed or moved, nothing here will say so.",githubConnect:"Connect GitHub…",githubReconnect:"Reconnect GitHub…",githubAuthorizeOnly:"Already installed af360 on GitHub? Authorize only",githubAuthorizeOnlyHelp:"Reconnects af360 without changing which repositories you granted.",secActivity:"Activity",activitySub:"which tool asked the witness, when, and what af360 could see asking",activityNone:"No agent has asked af360 anything in the last seven days - that is as far back as this record goes. When one does, it appears here and in the Log.",setupTitle:"Connect GitHub",setupLede:"af360 wants permission to look, never touch. It asks GitHub for read-only access to the repositories you choose - and GitHub, not af360, is where you choose them.",setupTouches:"af360 gives you a one-time code. You enter it on GitHub, choose which repositories af360 may read, and af360 picks it up from there.",deviceCodeLabel:"Your one-time code",deviceCopy:"Copy code",deviceCopied:"Copied",deviceGoLabel:"Enter it at",deviceExpires:"This code stops working in {0}.",deviceExpired:"This code has expired, and nothing was stored. Press Connect GitHub for a fresh one.",setupOpen:"Choose repositories on GitHub",setupStarting:"Asking GitHub for a code…",setupWaiting:"af360 is checking with GitHub every few seconds. This updates itself when you are done.",setupCancel:"Cancel",setupNoApp:"This build of af360 has no GitHub App registration, so it cannot connect yet.",receiptHead:"BEFORE ANYTHING IS STORED - the receipt:",receiptStored:"stored in ~/.af360/config.json, on this machine only, masked in every brief and answer",receiptCalls:"used from this machine to github.com and api.github.com only - the sign-in comes back to this computer, and no server of ours is involved, ever",receiptRefuse:"af360 asks GitHub for read-only permissions and nothing else - and it refuses the connection outright if GitHub ever reports otherwise",receiptLeak:"If this token ever leaked, it could read the repos you selected - nothing more, and it can never write. Revoking it is one click on GitHub (Settings - Applications - Authorized GitHub Apps - af360-app - Revoke access).",receiptBehalf:"GitHub's own screen will say af360 can \"act on your behalf\" - that is GitHub's wording for calling its API as you, and af360's calls are read-only, only on the repositories it has been granted, and it asks for nothing else",refusedLine:"af360's access for {0} was refused - reconnect to restore it. Until then GitHub answers nothing.",staleNoAppLine:"af360 holds a credential for {0} that is no longer working, and this build has no GitHub App registration to renew it with.",verifiedAs:"Connected as {0}",verifiedMeta:"read-only \xb7 {0} repositories \xb7 checked just now",verifiedSees:"af360 can see: {0}. For the ones that are folders af360 watches, it folds their CI results, pull requests and issues into the rooms when it scans.",repoOnAccount:"{0} on {1}",connectedMeta:"read-only \xb7 {0} \xb7 connected",repoCountOne:"1 repository",repoCountMany:"{0} repositories",grantedShow:"show the list",grantedHide:"hide the list",grantedAccount:"on {0}",grantedFloor:"af360 could not finish reading this list, so it may be short.",ghCardChecked:"GitHub checked",ghCardNever:"GitHub not checked yet",repoCountFloor:"at least {0}",verifiedNone:"af360 is connected, read-only, and cannot see any repositories yet. On GitHub, open Settings - Applications and find af360-app under your installed GitHub Apps, then choose the repositories af360 may read. If you asked to install it on an organisation, an owner there may still need to approve it.",verifiedNoInstall:"af360 is authorized, read-only, and is not installed on any account yet - so there is nothing for it to read. Installing it is the step that grants repositories: open af360-app on GitHub and install it on the account whose repositories you want af360 to watch. If you install it on an organisation, an owner there may need to approve it before af360 can see anything.",verifiedEmptyAccount:"af360 is connected to {0}, read-only, and everything you granted is in place - that account just has no repositories yet. Nothing to fix. When you make one and keep a copy in a folder af360 watches, af360 will pick it up on its next scan.",verifiedPartial:"af360 could not read the repository list for {0} of the installations it has been granted, so this list is what it can confirm and may be short. It will ask again the next time you connect.",refusedWrite:"Not connected - this token can write.",refusedWriteBody:"af360 only connects where it has been granted read access and nothing more. Its GitHub App asks for read-only permissions and nothing else, so a grant that can write should not be possible - nothing was stored. You can review what the installation grants on GitHub, under Settings - Applications - af360-app.",disconnect:"Disconnect",disconnected:"GitHub disconnected. The token was removed from config.json.",githubUnreadable:"af360 has a GitHub connection stored here that it cannot read. It was encrypted for a different Windows account or a different machine - a copied profile or a restored backup does this. Press Connect to sign in again."},diag:{title:"Share a diagnostic report",intro:"Everything af360 flags right now - what needs attention, why it matters, and the suggested fix - as one tidy file to hand to someone or paste into an AI assistant.",safe:"What you see here is exactly what gets saved. Folder paths are shortened, anything shaped like a password or key is hidden, and your machine name is never included.",hintText:"puts it on your clipboard now",hintFile:"saves a plain text file (.md) that editors and AI assistants read",hintPage:"saves a readable page you can print or save as PDF",downloadText:"Download as text",downloadPage:"Download as page",copyForAi:"Copy estate report",copied:"Copied ✓",dialogAria:"Diagnostic report"},check:{first:"First check - af360 will compare the next one against this.",since:"Since your last check on {0}:",cleared:"cleared {0}.",appeared:"new {0}.",unchanged:"nothing changed.",remaining:"{0} still open.",unmeasured:"{0} didn't run this time - not compared."},landing:{tag:"Everything you've built, watched from one place.",sub:"Runs on your machine. Your files stay on it.",open:"Open af360 →"},firstLight:{moment:"One moment.",gateTitle:"Welcome to af360",gateLead:"Let's get you set up. It takes about a minute.",gateBody:"af360 keeps an eye on the projects on this machine and tells you what is going on in them. It only reads - it never changes your files, and your files stay on your machine. The one thing it sends on its own is a once-a-day check for a newer af360, and you can switch that off. Everything else it does online happens because you asked for it, one switch at a time.",gateBodyPackaged:"af360 keeps an eye on the projects on this machine and tells you what is going on in them. It only reads - it never changes your files, and your files stay on your machine. af360 sends nothing on its own. Everything it does online happens because you asked for it, one switch at a time.",docsLead:"Continuing accepts the",docsAnd:"and the",docsTail:"Both are always in Settings.",termsName:"Terms of Use",privacyName:"Privacy Policy",docVersion:"Version",docsUnreadable:"af360 could not read its own Terms of Use and Privacy Policy from the files it ships. This is a fault in this copy of af360, and it cannot ask you to accept documents it cannot show you.",accept:"Accept and continue",accepting:"One moment…",notNow:"Not now",declinedTitle:"No problem",declinedBody:"af360 has not looked at your projects. Nothing on this machine was scanned. The only thing it has read is its own settings file, to see whether you had answered this before, and the only thing it saves is the size of this window. You can close it whenever you like.",declinedBack:"If you change your mind, just open af360 again and it will ask once more.",takeMeBack:"Take me back",sweepTitle:"Let's find your projects",sweepSearched:e=>"Searched ".concat(e),sweepFoundSoFar:e=>1===e?"1 found so far":"".concat(e," found so far"),sweepNoneYet:"Nothing found yet.",sweepFailed:"af360 could not finish looking. You can point it at a folder yourself once you are in.",pickTitle:e=>1===e?"Here's what af360 found: 1 project":"Here's what af360 found: ".concat(e," projects"),pickHelp:"Pick the ones you'd like af360 to keep an eye on. You can add or remove folders any time.",startWatching:e=>1===e?"Start watching 1 folder":"Start watching ".concat(e," folders"),continueWithout:"Continue without watching",watching:"Saving…",emptyTitle:"af360 did not find any projects",emptyBody:"af360 looked in the usual places on this machine and did not find any projects. If your work lives somewhere else, you can point af360 at the right folder once you are in.",emptyCapped:e=>"af360 stopped after finding ".concat(1===e?"1 folder that looks like a download":"".concat(e," folders that look like downloads")," rather than your own projects, so it did not open the places listed below. Point af360 at the folder your work lives in and it will look there."),goInEmpty:"Take me in"},setup:{look:"Look for my projects",pickMyself:"I'll pick the folder myself",browse:"Browse folders",looking:"Looking in your usual places…",lookAgain:"Look again",addAnother:"Add another place",whereLooked:"Where I looked:",nothingNew:"nothing new",lookedCapped:(e,t)=>"af360 stopped after ".concat(e," projects to keep this quick, so it did not open ").concat(t," of the places below. Add a folder yourself if something of yours is missing."),notLooked:"not opened - af360 had already found enough",pickWhere:"Pick where to look:",up:"↑ Up",noFolders:"Nothing here to watch - af360 does not list hidden folders or node_modules.",watchThis:"Watch this folder",alreadyWatching:"Already watching this folder ✓",watch:"Watch",watchAll:"Watch all",watching:"Watching ✓",foundCount:e=>"Found ".concat(e," project").concat(1===e?"":"s",". Watch the ones you care about:"),adding:"Adding…",noneFound:"I didn't find any new projects in the places below.",previewOff:"Preview mode - config changes are off.",searchFailed:"Something went wrong while searching - try again.",downloadedHeader:"Downloads sharing a name",downloadedNote:"Inside Downloads, or archive-style names - probably not your estate. Watch any that really are yours:",downloadedTag:"Looks like a download",elsewhere:"Not what you expected? Your projects might live somewhere else - a second drive, an external disk.",elsewhereBtn:"Point me somewhere else"}}},4576:(e,t,o)=>{o.d(t,{PF:()=>n,dv:()=>d,pA:()=>r,pl:()=>c,qt:()=>i,rM:()=>s,tO:()=>a});let n="af",a="360",i=n+a,s="Afeleos",r="https://afeleos.com";function c(e){return'<span class="brand">'.concat(n,'<em style="font-style:normal;color:').concat(e,'">').concat(a,"</em></span>")}function d(e,t,o,i,s){let r=e.textAlign;e.textAlign="left",e.fillStyle=i,e.fillText(n,t,o);let c=e.measureText(n).width;e.fillStyle=s,e.fillText(a,t+c,o);let d=t+c+e.measureText(a).width;return e.textAlign=r,d}},7861:(e,t,o)=>{o.d(t,{Brand:()=>s,SweepMark:()=>r,o:()=>c});var n=o(5155),a=o(4576),i=o(8938);function s(e){let{className:t}=e;return(0,n.jsxs)("span",{className:t?"word ".concat(t):"word",children:[a.PF,(0,n.jsx)("em",{children:a.tO})]})}function r(e){let{scanning:t}=e;return(0,n.jsxs)("svg",{className:"mark".concat(t?" scanning":""),viewBox:i.$r,"aria-hidden":!0,children:[(0,n.jsx)("circle",{cx:"13",cy:"13",r:"10",fill:"none",stroke:"rgba(148,163,214,.25)",strokeWidth:"2.5"}),(0,n.jsx)("path",{d:i.JX,fill:"none",stroke:"var(--accent)",strokeWidth:"2.5",strokeLinecap:"round"}),(0,n.jsx)("circle",{cx:"13",cy:"13",r:"2.6",fill:"var(--accent)"})]})}function c(e){let{scanning:t=!1,href:o="/radar",className:a}=e;return(0,n.jsxs)("a",{className:a?"brand ".concat(a):"brand",href:o,children:[(0,n.jsx)(r,{scanning:t}),(0,n.jsx)(s,{})]})}},8938:(e,t,o)=>{o.d(t,{$r:()=>i,CV:()=>r,JX:()=>a});let n="#2B6FE3",a="M13 3 A10 10 0 1 1 4.34 8",i="0 0 26 26",s="rgba(148,163,214,.75)";function r(e){return"data:image/svg+xml,".concat(encodeURIComponent(function(e){let t="unknown"===e?s:n;return'<svg xmlns="http://www.w3.org/2000/svg" viewBox="'.concat(i,'">')+'<circle cx="13" cy="13" r="10" fill="none" stroke="'.concat("rgba(148,163,214,.45)",'" stroke-width="2.5"/>')+'<path d="'.concat(a,'" fill="none" stroke="').concat("unknown"===e?s:n,'" stroke-width="2.5" stroke-linecap="round"/>')+'<circle cx="13" cy="13" r="2.6" fill="'.concat(t,'"/>')+("needs"===e?'<circle cx="21" cy="5" r="5.6" fill="#0B1020"/><circle cx="21" cy="5" r="4" fill="#E9A23B"/>':"")+"</svg>"}(e)))}},9178:(e,t,o)=>{o.d(t,{I:()=>r,LS:()=>c,fF:()=>s,lt:()=>i,nW:()=>n});function n(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Date.now(),o="number"==typeof e?e:new Date(e).getTime();return Number.isFinite(o)&&t-o<0}let a="a time this machine's clock has not reached yet",i={core:a,at:"at ".concat(a),recorded:"recorded at ".concat(a),since:"since ".concat(a)};function s(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Date.now(),o="number"==typeof e?e:new Date(e).getTime(),n=t-o;if(n<0)return i.recorded;if(n<6e4)return"just now";if(n<36e5)return"".concat(Math.floor(n/6e4)," min ago");if(n<864e5)return c(Math.floor(n/36e5),"hour")+" ago";if(n<12096e5)return c(Math.floor(n/864e5),"day")+" ago";if(n<48384e5)return c(Math.floor(n/6048e5),"week")+" ago";let a=new Date(o);return a.toLocaleDateString([],{month:"short",day:"numeric",...a.getFullYear()!==new Date(t).getFullYear()?{year:"numeric"}:{}})}function r(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Date.now(),o=t-("number"==typeof e?e:new Date(e).getTime());return o<0?i.since:"for ".concat(c(Math.floor(o/864e5),"day"))}function c(e,t){return"".concat(e," ").concat(t).concat(1===e?"":"s")}}}]);