AGENTS.md
shark/shark-explorer/AGENTS.mdAGENTS.md
Quality
69/100
Scores the file, not the repository.Length
4,597 words
18 headings · 5 code blocksRepository
30k
— · pushed 0 days agoLast changed
today
First indexed 3 days ago.1# Shark Explorer — agent guide23A desktop app that renders a heap dump's dominator tree as a navigable treemap, as rings around a4centre, or as a stack of rows the way a profiler draws a call tree. The long term goal is a YourKit-style5heap explorer; these are the first surfaces.67This file is scoped to `shark/shark-explorer/`. It only records things an agent would get wrong by8reading the source alone — everything else is in the code. Keep it that way.910## Modules1112| Module | What it is | Constraints |13| --- | --- | --- |14| `shark-explorer-core` | Heap dump → dominator tree → layout model. Layout, hit testing, navigation state. | **No Compose dependency, Java 8 target.** Must stay reusable from the Android `leakcanary-app`. |15| `shark-explorer-jdwp` | Attaches to a live app as a debugger to read the pixels of its bitmaps. | **Imports `com.sun.jdi`, so it needs a JDK and can't be loaded on Android.** That's the whole reason it isn't in `core`. |16| `shark-explorer-app` | Compose Desktop UI: window, the canvas each shape draws into, details panel. | **Java 17 target** — see below. |1718`shark/shark-explorer/` itself holds no code, matching how `shark/` and `leakcanary/` are grouping19directories in this repo.2021Put logic in `shark-explorer-core` by default. Anything in `shark-explorer-app` is hard to unit test22and can't be shared with Android, so it should be limited to composables and wiring.2324## Use `HeapDominatorTree`, not `ApproximateDominatorTree`2526`shark.ApproximateDominatorTree` is the on device BFS approximation, and it is **known to be wrong**27— a cross edge can be processed while the parent's dominator is still stale, so retained sizes get28under-attributed. Don't build on it.2930`shark.HeapDominatorTree` is the exact one, which is what `HeapExplorer` uses. See31`notes/dominator-tree.md` for its memory profile and for the reference reader behaviour that makes a32treemap read strangely until you know about it.3334## The heap dump is read off the UI thread3536A `HeapGraph` is read only and safe to read from several threads at once, so the reason everything that37touches a `HeapExplorer` goes through `HeapDumpSession.read` is **latency, not safety**: labelling a38rectangle is IO, and summarising a selection or walking up to the GC roots is seconds of it on a large39dump. Doing any of that in a composable freezes the window.4041What follows for the UI: a composable never holds a tree, only what was already computed from one. A42laid out, labelled view is a `TreemapPresentation` or a `RadialPresentation`, and a selection is a43`HeapObjectSummary`; both arrive a little after whatever asked for them changed.4445The one thing that isn't thread safe is a `Sequence` a `HeapGraph` hands out — iterating one reads46through it — so a thread reading `graph.objects` needs its own rather than a shared one.4748**Cancelling the coroutine that asked for a read stops the read**, which is what a `LaunchedEffect`49being relaunched does. Shark does the stopping, not us: the heap dump is opened with a `CancelSignal`50asking whether the read in flight is still wanted, and it's asked on every record read, so the work51gives up shortly after the question is withdrawn and comes back as a `CancellationException`. A read52given up on while it was still queued never starts at all. So dragging a window edge costs the size it53lands on and a little of each size it passed through, rather than all of them in full.5455Two things follow. **A read is only cancellable at the granularity of what it reads** — a stretch that56computes without reading, like a layout over an already-labelled tree, stops when it next reads — so the57`HOVER_SETTLE_MILLIS` half of this, not starting work that isn't wanted yet, still earns its keep.58And **anything a read mutates has to survive being abandoned half way**: today's reads are safe because59the built-on-first-use indexes are `by lazy` initializers that build a whole object before assigning it60(a cancelled build is simply retried, since `lazy` doesn't cache a failure), and the walks reuse arrays61stamped with a generation per walk rather than cleared at the end.6263**The pointer asks questions on that thread too**, because moving over a rectangle describes it. Which is64why nothing is read until the pointer has been still for `HOVER_SETTLE_MILLIS`, and why what a hover asks65for is capped and index-backed: a chain from a GC root is one walk over `ReferrerIndex` with at most 2066steps read out, and the search for every way an object is held runs for the object clicked and no other.67A new question the panels ask has to be measured before it goes in the hover path — `notes/decisions.md`68has the numbers on the biggest dump in the repo.6970## Every run writes a log file7172`installLogging()` in `shark-explorer-app` points `SharkLog` at stdout **and** at73`~/.shark-explorer/logs/shark-explorer-<when-it-started>.log`, one file per run, the newest74`SessionLog.KEEP_SESSION_COUNT` kept and the rest deleted as a run starts.7576**So ask for that file when someone reports something odd**, and read it before guessing. It holds the77environment (JVM, OS, heap limit — a dump too large for the explorer runs out of exactly that), every78step of opening the dump with its duration, and every read of it through `HeapDumpSession.read` with79what was being read and how long it took. What that makes readable:8081- A read logged as started and never as done is where the app was killed, hung, or ran out of memory.82- The last line being `Shark Explorer closed` is how a session that ended cleanly is told from one that83 didn't.84- Everything the window does silently — a path zoomed out because a node left the tree, a click landing85 on an object the tree has no node for, a list that came back empty — says so there rather than nowhere.86- A run is every window of it, and a window is a heap dump, so the reads of several dumps interleave.87 The `[heap-dump-<file name>]` a line was written from is which dump it is about; lines from the88 window's own thread name the file instead.8990Which is also the rule for new code here: **anything the UI swallows or falls back from gets a91`SharkLog.d` line saying so.** The file is only worth reading if it's complete.9293## Gradle facts that aren't visible from these build scripts9495- **`shark-explorer-app` is excluded by name** from the repo-wide Java 8 target in the root96 `build.gradle.kts`, because Compose Multiplatform's artifacts aren't built for Java 8. If you97 rename or move the module, update that exclusion list or the build breaks confusingly.98- **All three modules are listed in `modulesWithoutPublicApi`** in the root `build.gradle.kts`. They99 are not published to Maven Central, their ABI isn't tracked, and they're left out of the docs site.100 So there is no `api/*.api` file to update and `updateKotlinAbi` doesn't apply.101- **`jdk.jdi` is listed in the app's `nativeDistributions.modules`.** jlink includes only the JDK102 modules it detects a use of, and it detects none through `Bootstrap.virtualMachineManager()`, so a103 packaged build without that line attaches to nothing.104- `compose` and `composeMultiplatform` in `gradle/libs.versions.toml` are **unrelated**: the first105 is the Jetpack Compose version the Android app builds against, the second is Compose106 Multiplatform for this desktop app.107108## The app icon is generated, and its macOS shape is baked in109110`shark-explorer-app/icons/shark-explorer-icon.svg` is the source, and111`shark-explorer-icon-small.svg` beside it is the **same shark with the gills, teeth and brow left112out**, for the sizes where those come out under a pixel. The `.icns`, the `.ico` and113`src/main/resources/shark-explorer-icon.png` are **rendered from the two of them** by114`icons/render-icons.sh`, which picks by size, so edit an SVG and re-run that script rather than115touching a binary. It needs `rsvg-convert` (`brew install librsvg`), and `iconutil`, which is macOS116only.117118**The two drawings share one transform**, hard coded rather than fitted twice, so that the shark119doesn't shift when the dock crosses between them. Changing the shape in one means changing it in the120other and keeping that transform identical.121122The SVG has the macOS app icon grid drawn into it — an 824x824 rounded body inside a 1024x1024123canvas, with its own shadow — because jpackage ships an `.icns` and **nothing masks or insets that for124us**, unlike an Android adaptive icon or a macOS 26 `.icon` bundle. So a redesign has to keep drawing125the body and the padding, and the corners are a superellipse rather than a circular arc.126127**The macOS dock icon of a `./gradlew run` needs no runtime code.** The Compose plugin turns128`nativeDistributions.macOS.iconFile` into `-Xdock:icon` on the run task, verified by A/B: drop that129one line from the build script and the flag is gone from the run's JVM arguments and the default Java130icon is back. So `java.awt.Taskbar` has nothing to add here. `Window(icon = …)` does, but only for the131Windows and Linux title bar — macOS ignores it.132133**`-Xdock:icon` is what puts the icon on the tile, and a bundle around the JVM is not a substitute.**134AWT sets the dock tile from that flag as it starts, and when the flag is absent it sets the tile to135the Java icon — over whatever the bundle asked for. So `runNamed` passes the flag too, even though its136generated bundle declares `CFBundleIconFile`. `CFBundleIconFile` is not ignored, it is just overwritten:137`NSRunningApplication.icon` for a `runNamed` process without the flag hands back the shark, because138that is the LaunchServices record, while the tile on screen is Duke. **Which is the trap** — every API139an agent can read says the icon is right, and only a picture of the dock says otherwise.140141## What macOS calls the run, as against what it calls a window142143A run is one process and many windows, so the OS gets one name for all of them, and `main` sets it from144`--title` before the first window: `apple.awt.application.name`. That reaches the menu bar next to the145Apple logo, the app switcher, and every name macOS reports through an API. **It does not reach the146dock** — nothing a process can do reaches the dock, see the next section. Three things about that line147aren't visible from it.148149**It is read once, as AWT starts**, and the process registers with macOS under whatever it said then.150Setting it after a window is up changes nothing — measured, not assumed — so it belongs between parsing151the command line and `application { }`, which is the only gap there is.152153**It is the same name `-Xdock:name` sets.** That JVM argument only puts the name in an environment154variable AWT reads at that same moment: a run given `-Xdock:name=X` and a run that sets the property to155`X` in `main` produce LaunchServices records differing in nothing but their audit token and check-in156time. So the run task passes no name and an IDE run configuration needs none either, and157`java.awt.Taskbar` is no help — its API is icon, badge, menu and progress, and no name.158159**A packaged app ignores the property and keeps its bundle's name.** `Shark Explorer.app` launched with160`--title="Packaged with a title"` logs that title and is still called `Shark Explorer` by macOS, because161jpackage gives it a real bundle. A run from Gradle has no bundle of its own — it is `/…/bin/java`,162bundle id `net.java.openjdk.java` — which is why it is called after whatever launched it until163something names it.164165## The dock only reads a bundle's file name, so `runNamed` gives it one166167`-Xdock:name` has not named the dock since around macOS 10.9 — [JDK-8173753][dock-bug], still open,168where the reported symptom is exactly what you get: the name reaches the menu bar and the dock goes on169saying `java`. Confirmed here, three explorer runs whose LaunchServices, `NSRunningApplication` and170WindowServer names were all different showed three dock tiles all called `java`. **So don't spend time171looking for the property or the API call that fixes this. There isn't one.**172173What the dock reads is the file name of the bundle a process was launched from. Not `CFBundleName`:174two bundles carrying the same `CFBundleName` and differing only in file name are two differently named175tiles.176177`runNamed` is `run` with a bundle around it, generated per launch and named after `--title`:178179```bash180./gradlew :shark:shark-explorer:shark-explorer-app:runNamed \181 --args="--title=\"Hover previews\" shark/shark-android/src/test/resources/compose_leak.hprof"182```183184- **It is a launcher script and an `Info.plist` around the classes `run` would have run**, not a185 `jpackage` build. Packaging is a minute of jlink per code change, which would be a minute per look;186 this is a compile.187- **The script `exec`s the JVM** rather than starting it as a child. The JVM has to end up being the188 process macOS launched from the bundle or it is a process of its own again, and the dock is back to189 calling it java.190- **`open` gives it no terminal**, so stdout goes to `build/named/<title>.out` — which is where a run191 that died before it could open a log file says why. Everything after that is in the usual place, see192 the logging section.193- **Relaunching a title while a window of that title is open** is the one thing to avoid: the bundle is194 rewritten in place, and that window is reading it.195196**And a Gradle build of any kind kills every explorer window already open.** `run` and `runNamed` both197put the module jars on the classpath rather than a copy of them, and a JVM reads a jar's index once and198then trusts it, so recompiling under a live window makes every class that window hasn't happened to load199yet disappear. What that looks like is the window dying on the next pointer move with200`NoClassDefFoundError: shark/explorer/TreemapPoint`, a `ClassNotFoundException` under it, and a stack201trace through code nobody has touched — the class is in the source and in the jar, which is exactly what202makes it read like a real bug in whatever was being worked on. Measured: a window launched at 16:42, a203`shark-explorer-core:check` rewriting that jar at 16:46, and the first hover after it gone. So **launch204the window you are handing over last**, after everything that builds — and when one dies this way, check205the jar's mtime against the process start before believing the stack trace.206207[dock-bug]: https://bugs.openjdk.org/browse/JDK-8173753208209## Reading these names without being able to see the screen210211```bash212lsappinfo find pid=<pid> # ASN:0x0-0x2338336-"Hover previews":213```214215`NSRunningApplication.localizedName`, which the app switcher shows, and `kCGWindowOwnerName`, which the216WindowServer holds, agree with it and are readable from `osascript -l JavaScript` through `ObjC.import`.217None of them is what the dock displays, which is why all three can say one thing and the tile another.218219The dock tile names, and an app's menu bar, are readable **only with the Accessibility permission**, and220they are the ones worth reading, since they are what someone looking at the screen sees:221222```bash223osascript -e 'tell application "System Events" to tell process "Dock" \224 to get name of UI elements of list 1'225osascript -e 'tell application "System Events" to tell process "<the run>" \226 to get name of menu bar items of menu bar 1' # Apple, <the run>227```228229Without that permission both fail with `-1719 not allowed assistive access`, and the only way to know230what the dock says is to ask the person in front of it. It is granted per responsible process — for an231agent, whichever app launched the session — in System Settings → Privacy & Security → Accessibility.232Screen Recording is separate, and without it a screenshot of another process comes back as wallpaper.233234**A tile's icon, though, only a screenshot of the dock will tell you.** `NSWorkspace.iconForFile` on a235bundle and `NSRunningApplication.icon` on a pid both hand back a PNG an agent can open, but both read236the LaunchServices record rather than the tile, so both are wrong the moment AWT overwrites it — see237the `-Xdock:icon` section. With Screen Recording granted, this is the picture that settles it:238239```bash240# The dock has no window while it is hidden, so a capture of where AX says the tile is comes back blank.241# Post mouse moves down to the bottom edge — one warp isn't enough, it takes an approach and a dwell —242# then ask AX for the tile again: a y that has moved up by the dock's height means it is on screen.243osascript -e 'tell application "System Events" to tell process "Dock" \244 to get {position, size} of (first UI element of list 1 whose name is "<the run>")'245screencapture -x -R <x>,<y>,<w>,<h> tile.png246```247248Put the cursor back where it was afterwards, since it is someone's cursor.249250## Build and test251252```bash253./gradlew :shark:shark-explorer:shark-explorer-core:test254./gradlew :shark:shark-explorer:shark-explorer-jdwp:test255./gradlew :shark:shark-explorer:shark-explorer-app:test # UI tests, headless, no emulator256./gradlew :shark:shark-explorer:shark-explorer-app:check # test + detekt257258# Launch it. Paths are optional; without one, use the "Open heap dump…" button. One window per path,259# and one per heap dump opened from the button — see `notes/decisions.md`.260./gradlew :shark:shark-explorer:shark-explorer-app:run \261 --args="--title=\"Hover previews\" shark/shark-android/src/test/resources/compose_leak.hprof"262```263264The repo has real Android heap dumps to try it on: `shark/shark-android/src/test/resources/*.hprof`265and `leakcanary/leakcanary-android-instrumentation/src/androidTest/assets/large-dump.hprof` (39 MB,266the biggest one). All of them are from API 25 or earlier, so every bitmap in them carries its pixels —267anything about a modern dump has to be tried on one taken off a device. See `notes/bitmaps.md`.268269**Always pass `--title`, and name the run after the piece of work it is for.** Several explorers end up270open at once — one per task, often on the same heap dump — and a name is all the OS gives you to tell271them apart. `--title` goes in front of the heap dump name in every window of that run, including windows272opened from it later, so that two identical `large-dump.hprof` windows never end up on screen.273`ExplorerArguments` is the whole command line, and it is strict: an unknown option is a message saying274what to type, not a heap dump that can't be found.275276**`run` while you work, `runNamed` when you hand a window over.** They take the same command line.277`run` streams the log to the terminal and is a compile away, so it is the one for trying your own278change — don't reach for `runNamed` for that. When the change is done and the app is being started for279someone else to look at, use `runNamed`: it is the only one of the two the dock will name, and with280several explorers open the dock is what they navigate by. See the dock section above.281282`check` runs detekt (config at `config/detekt-config.yml`); CI and the pre-push hook both enforce283it, so run it before pushing.284285Anything that reaches a device — taking a heap dump, fetching bitmaps — can be tried for real with an286emulator running and `leakcanary-android-sample` installed on it287(`ANDROID_SERIAL=emulator-5554 ./gradlew :samples:leakcanary-android-sample:installDebug`). An emulator288older than API 35 is what exercises `shark-explorer-jdwp`, since a newer one is asked through a heap dump289instead.290291**Two things let a process be dumped, and either is enough**: an app built debuggable, or a device whose292whole build is — `ro.debuggable=1`, which is what a `userdebug` or `eng` image sets and what293`ActivityManagerService.enforceDebuggable` skips its check on. So "only a debuggable app can be dumped"294is right for a phone and wrong for a `userdebug` emulator, where every process on the device can be295dumped and attached to. Measured on two emulators here: an API 36 `user` image refuses296`am dumpheap` of `com.android.systemui` with `SecurityException: Process not debuggable` and lists one297pid under `adb jdwp`; an API 29 `userdebug` one writes 16 MB of `com.android.permissioncontroller` and298lists twenty. `AndroidDevice.dumpsAnyProcess` is that property, read from the `getprop` the explorer299already runs.300301**A modern emulator image is a `user` build**, so being an emulator is not what makes a device302permissive — check `ro.debuggable` rather than assuming.303304**None of that is covered by a test**, and it can't be: a JDI client talks to a real VM or to nothing.305Drive it from a throwaway test against a running emulator, read the numbers, and delete the test — the306numbers belong in `notes/bitmaps.md`.307308## Testing conventions309310- **UI tests are headless JVM tests**, not instrumentation tests. They live in `src/test/` and use311 `androidx.compose.ui.test.v2.runComposeUiTest`. Import from the **`.v2` package** — the non-v2312 `runComposeUiTest` is deprecated.313- **A test of the whole window runs at the size a window opens at**, which is what `explorerUiTest` is314 for. The default test window is smaller, and there the panes beside the view squeeze the controls above315 it to zero width, so a test would be pressing a window nobody has.316- **Hovering takes two moves.** A view describes what the pointer *moved* onto and ignores the enter that317 comes with a pointer arriving, so a single injected `moveTo` reports nothing hovered. `hover()` in318 `ExplorerUiTest.kt` moves twice; `notes/decisions.md` says why the views read events that way.319- **An injected scroll only lands after a `waitForIdle()`.** `performMouseInput { scroll(n) }` on the320 stack does scroll it, but the offset is still 0 in the same breath, because the scroll is animated and321 the frame hasn't run — so reading it, or the callback it fires, right after the injection says nothing322 happened. Which reads exactly like a wheel a headless test can't deliver, and cost an afternoon of323 looking for one. **How far one notch scrolls is the platform's**, ten pixels with no AWT wheel event324 behind the pointer event to say otherwise, so a test scrolls by notches and reads the pixels back off325 `SemanticsProperties.VerticalScrollAxisRange` rather than asserting a number of its own.326- **Each shape draws into a single `Canvas`, so there are no per-cell semantics nodes.** UI327 tests can't find cells by tag, and **not by label either** — a cell's label is painted text, so no328 assertion and no wait can reach it. Test layout and hit testing as pure functions in329 `shark-explorer-core`, and have UI tests drive coordinates with `performMouseInput` and assert on what330 is written outside the view: the chain pane and the details panel either side of it, and the card that331 follows the pointer, whose text is real text and so can be found and its bounds read.332- **A clickable block naming an object is one semantics node**, because `Modifier.clickable` merges its333 descendants, so a step of the chain is found by any one of the three lines it prints. The same object is334 usually named in more than one place at once — a step of the chain, the bar above the map, the details335 panel — so an assertion about it either counts `onAllNodesWithText` matches or picks the one it means336 with `hasClickAction()`. `onNodeWithText` failing with "found 2" is that, not a duplicated composable.337- **A UI test knows the map is drawn through `waitForTheTree`**, which waits for the view's338 `contentDescription` with nothing left spinning, because the drawn map itself adds no text to the339 window. Where "the map *moved*" is the point rather than "the map is there", wait on the log line340 every layout writes instead — `ExplorerAppTest.waitUntilZoomedIn`.341- **A headless test can write a PNG of what Skia drew**, which is how an agent gets to look at this342 UI at all: `onRoot().captureToImage().toAwtImage()` and `ImageIO.write`, after a343 `performMouseInput`, renders the hover highlight and the path bar the same as a real window does.344 Nothing outside the JVM can do this — macOS shows a process that lacks Screen Recording only the345 desktop picture and its own windows, so a screenshot of a `./gradlew run` taken from any other346 process comes back as wallpaper. Delete the capture again once it has been looked at; it's347 scaffolding, not a test.348- **The UI tests record `SharkLog` for every test**, not only for the ones asserting on it. A log349 line is built from state — an index into a path, a node id — so a line built from the wrong state350 should fail the test that reaches it rather than wait for a session nobody can read. The `RecordedLog`351 rule does the recording, and is a rule rather than a `@Before` because putting the logger back is the352 part that isn't optional: a test that leaves `SharkLog.logger` set breaks every test after it.353- **What the log says is also how often something happened.** Every read of the heap dump is one line354 through `HeapDumpSession.read`, so counting them is what holds the window to laying the tree out once355 per view asked for, which is what `TreeLayoutTest` does. Those counts are sound because reads queue on356 that one thread in order: anything queued behind the read a test waited for is already logged by then.357- **A `Window` needs a display, so nothing inside `application { }` is covered headless.** Which358 window a heap dump opens in is plain state in `ExplorerWindow.kt`, unit tested by359 `ExplorerWindowTest`. `ExplorerApp` is one window's worth of app and takes the heap dump it shows360 as a parameter, so a UI test drives one window and nothing else.361- **A click is a fraction of the view, never of the window.** `ExplorerAppTest.viewBounds` measures the362 view by its `contentDescription`, and every press helper is relative to that. Window fractions break363 the moment anything above the view changes height, which is a change to the top bar away.364- **A test about `java.lang.ref` strengths needs a dump of a real JVM, taken without collecting first.**365 `JvmReferenceStrengthTest` writes one with `HotSpotDiagnosticMXBean.dumpHeap(path, live = false)`,366 because the collection a heap dump normally begins with clears a weak referent nothing else holds and,367 since JDK 9, a phantom one — so the strengths the test is about would be missing from a dump taken the368 usual way. That leaves it fragile in a way its KDoc explains: anything allocated between its369 `System.gc()` and the dump can trigger a collection that clears the lot. It doesn't replace the370 `dump { }` cases either, ART's reference classes and the lists it keeps them on not being HotSpot's.371- Build test heap dumps with the `hprofFile.dump { }` DSL from `shark-hprof-test` rather than372 checking in binary fixtures or hand-writing hprof bytes. A dump with bitmaps in it is `BitmapDumps.kt`373 in `shark-explorer-core`'s tests — the `"a.b.C" instance { }` shorthand declares a class per instance,374 so two bitmaps built that way are two `android.graphics.Bitmap` classes, which no real dump has.375- **A UI test must pass a `DeviceHeapDumps` built on a fake `Adb`.** `ExplorerApp`'s default shells out to376 the machine's `adb`, so a test that takes it has whatever device is plugged in to answer for — and the377 window can dump the heap of a real process. `FakeAdb` matches command prefixes, because the remote dump378 path contains a timestamp.379- **A synthetic Android class needs the fields the object inspectors read.** `HeapObjectSummary` runs380 `AndroidObjectInspectors`, and those read fields with `!!` — an `android.view.View` without `mParent`,381 `mWindowAttachCount`, `mAttachInfo` and `mContext` makes `summarize()` throw a bare382 `NullPointerException` from inside shark-android, which reads like a bug in the explorer.383384## Notes385386Design decisions and findings, kept current as the work proceeds:387388- `notes/decisions.md` — stack and structure decisions, with rationale389- `notes/dominator-tree.md` — dominator algorithm findings, memory/perf numbers390- `notes/treemap-rendering.md` — adaptive depth model, the two shapes, bugs in the existing Android391 treemap392- `notes/bitmaps.md` — which Android versions put a bitmap's pixels in the heap dump, and the two ways393 the ones that don't are fetched off the device394395Update these in the same change that makes them stale. They're for agents, so keep them short and396skip anything derivable from the code.397
Also in square/leakcanary
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| square/leakcanaryAGENTS.md · 30k | AGENTS.md | buildteststylearch+2 | 89/100 | 3 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| OnlyTerp/prompt-cache-skillsAGENTS.md · 112 | AGENTS.md | setupbuildtestlint-format+5 | 100/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| wpscanteam/wpscanAGENTS.md · 9.7k | AGENTS.md | setupbuildteststyle+6 | 100/100 | 2 days ago | |
| SkeneTechnologies/skene-cookbookAGENTS.md · 51 | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 2 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| trick77/agents-md-syncAGENTS.md · 2 | AGENTS.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago |
