🏡


to read (pdf)

  1. I don't want your PRs anymore
  2. JitterDropper | OALABS Research
  3. DomainTools Investigations | DPRK Malware Modularity: Diversity and Functional Specialization
  4. EXHIB: A Benchmark for Realistic and Diverse Evaluation of Function Similarity in the Wild
  5. Neobrutalism components - Start making neobrutalism layouts today

  1. June 22, 2026
    1. 🔗 tomasz-tomczyk/crit v0.16.4 release

      What's Changed

      Internal refactors

      New Contributors

      Full Changelog : v0.16.3...v0.16.4

    2. 🔗 backnotprop/plannotator v0.21.1 release

      Follow @plannotator on X for updates

      Missed recent releases? Release | Highlights
      ---|---
      v0.21.0 | Direct document editing in annotate mode, live git-status file tree, in-app agent terminal, open files in external apps, HTML renders as HTML
      v0.20.3 | Annotations no longer lost when clicking away, off-screen indicator for open comments
      v0.20.2 | Pierre CodeView all-files review, large-PR pipeline and instant-open checkout, unified agent engine selection, Pi programmatic plan mode
      v0.20.1 | Pi extension install hotfix (pinned @pierre/diffs after a broken upstream release)
      v0.20.0 | Multi-repo workspace reviews, semantic diff overview, UI 2.0 themes and plan look chooser, leaner single-source skill install
      v0.19.27 | Kiro CLI integration, Glimpse native window, annotate-last message picker
      v0.19.26 | Amp plugin production fixes, Mermaid rendering fix, Settings flicker fix, update notification toast and shimmer
      v0.19.24 | Amp integration, configurable data directory, Auto Mode permission option, Pi plan approval fix
      v0.19.23 | Droid integration, Windows Pi AI fix, quieter update indicator
      v0.19.22 | Safari copy fix in plan viewer, CLAUDE_CONFIG_DIR support for session logs
      v0.19.21 | Ask AI in plan review and annotate mode, shared AI runtime, origin-aware provider defaults


      What's New in v0.21.1

      A single-fix patch release. Annotating the last assistant message could open a blank page in any conversation with more than one response. This release fixes that.

      Annotate-Last No Longer Blanks on Multi-Message Sessions

      Running /plannotator-last (or plannotator last) opened a blank page whenever the session had two or more recent assistant messages. With zero or one message it worked, so it looked intermittent, but it was deterministic: the failure tracked message count. It was most visible on the Pi extension, where multi-turn sessions are the norm.

      The cause was a React rendering bug. When the annotate UI assembled the feedback payload during render, it reached a helper that wrote component state as a side effect. Because that state was a freshly built map on each pass, React never settled, hit its re-render limit, and threw before drawing anything, leaving an empty page. The fix makes that render-path helper a pure read of the same data, so the work that belongs at submit time no longer fires during render. The exported feedback is unchanged; the page renders.

      PR #950 closing #949, reported, diagnosed, and fixed by @emmaneugene.

      Install / Update

      macOS / Linux:

      curl -fsSL https://plannotator.ai/install.sh | bash
      

      Windows:

      irm https://plannotator.ai/install.ps1 | iex
      

      Extra skills (compound, setup-goal, visual-explainer), opt-in:

      npx skills add backnotprop/plannotator/apps/skills/extra
      

      Claude Code Plugin: Run /plugin in Claude Code, find plannotator , and click "Update now".

      OpenCode: Clear cache and restart:

      rm -rf ~/.bun/install/cache/@plannotator
      

      Then in opencode.json:

      {
        "plugin": ["@plannotator/opencode@latest"]
      }
      

      Pi: Install or update the extension:

      pi install npm:@plannotator/pi-extension
      

      Droid: Install via the plugin marketplace:

      droid plugin marketplace add backnotprop/plannotator
      droid plugin install plannotator@plannotator
      

      Amp: Install the CLI first, then copy the plugin:

      mkdir -p ~/.config/amp/plugins
      curl -fsSL https://raw.githubusercontent.com/backnotprop/plannotator/main/apps/amp-plugin/plannotator.ts \
        -o ~/.config/amp/plugins/plannotator.ts
      

      Kiro CLI: The installer auto-detects Kiro and installs skills automatically. After installing the CLI, launch with:

      kiro-cli chat --agent plannotator
      

      Upgrading from before v0.20.0? Read the v0.20.0 release notes first; that release changed how skills install.


      What's Changed

      • fix(editor): stop setState-during-render loop in multi-message annotate by @backnotprop in #950

      Community

      Thanks to @emmaneugene, who reported the blank-page regression in #949, traced it to the exact render-path state write, and supplied the fix that this release ships.

      Full Changelog : v0.21.0...v0.21.1

    3. 🔗 earendil-works/pi v0.79.10 release

      New Features

      • Extension compaction event context - Extension session_before_compact and session_compact events now include reason and willRetry, so extensions can distinguish manual /compact, threshold auto-compaction, and overflow retry flows. See session_before_compact / session_compact and Custom Summarization via Extensions.
      • Safer update flow - pi update installs the exact checked Pi version, and update notices show the changelog URL, making upgrades more predictable. See Install and Manage.

      Added

      • Added reason and willRetry metadata to extension session_before_compact and session_compact events so extensions can distinguish manual, threshold, and overflow compaction flows (#5962 by @PizzaMarinara).

      Fixed

      • Fixed the find tool to respect nested git repository boundaries when parent .gitignore rules ignore the nested repo (#5960).
      • Fixed the usage docs slash command table to include /trust and /import (#5959).
      • Fixed inherited OpenAI-compatible streaming to preserve encrypted reasoning_details that arrive before matching tool call deltas (#5114).
      • Fixed broken TUI documentation links to the plan-mode extension example (#5957).
      • Fixed transient extension UI and session-start messages emitted during session replacement or reload so they remain visible, and kept reload input blocked until reload completes (#5943).
      • Fixed the plan-mode example to preserve active custom tools, skip the action prompt when no plan is found, and queue refinement/execution follow-ups correctly from agent_end (#5940).
      • Fixed pi update to install the exact version returned by the Pi update check, make --force reinstall that checked version, fail instead of falling back to an unversioned reinstall when no version is available, and report both the old and updated versions.
      • Fixed update notifications to display the actual changelog URL as the hyperlink text.
    4. 🔗 r/reverseengineering AirPlay 2 realtime audio sender, the encrypted RAOP/RTSP path reconstructed and documented rss
    5. 🔗 r/reverseengineering /r/ReverseEngineering's Weekly Questions Thread rss

      To reduce the amount of noise from questions, we have disabled self-posts in favor of a unified questions thread every week. Feel free to ask any question about reverse engineering here. If your question is about how to use a specific tool, or is specific to some particular target, you will have better luck on the Reverse Engineering StackExchange. See also /r/AskReverseEngineering.

      submitted by /u/AutoModerator
      [link] [comments]

    6. 🔗 r/reverseengineering HexWalk 2.1.0 Hex analyzer new release, new light theme, export analysis to csv, works both on Windows, Linux and MacOs, give it a try! rss
    7. 🔗 HexRaysSA/plugin-repository commits sync repo: +1 plugin, +1 release rss
      sync repo: +1 plugin, +1 release
      
      ## New plugins
      - [oh-my-dump](https://github.com/nevergiveupcpp/oh-my-dump) (1.0.15)
      
    8. 🔗 r/reverseengineering JavaScript Obfuscator rss
  2. June 21, 2026
    1. 🔗 IDA Plugin Updates IDA Plugin Updates on 2026-06-21 rss

      IDA Plugin Updates on 2026-06-21

      New Releases:

      Activity:

      • atelier
        • f9876a21: fix(web-fetch): allow loopback and nonstandard ports
        • 743aea4a: feat(search): honest-empty verdicts to stop retrieval spirals
        • 2b39647e: chore: bump landing submodule (checkout, license flow, device manager)
        • af644844: feat(licensing): self-service device management (CLI + web magic-link)
        • 0f4a13b9: docs(contributing): add CLA and CLA-assistant workflow
        • e58f7269: feat(bench): sandbox egress guard and harbor agent updates
        • e2047308: feat(licensing): device-bound leases with auto-refresh and 3-device l…
        • 7d8108fb: refactor(doc-sync): slim agent-context generator and unify managed ma…
        • 4ade68fa: refactor(mcp): consolidate code-intel tools into explore
        • d295eaa4: chore(license): relicense under FSL-1.1-ALv2
      • disrobe
        • 0926b0b3: bump py recovery to 93.45% (5874/6286); update svgs
        • d36baae4: guard loop detection against else-arm misidentification and fix fused…
        • ef7dc095: regenerate recovery/card/verification svgs for py 93.05 bump
        • a95df9d6: recover dropped else branch when a guarded try is followed by a plain…
        • 5c2e53a0: add missing semicolon in nir-lift python emitter (clippy semicolon_if…
        • bd3334ff: extend arg-rest oracle with boa-graded leading-param shift cases (fun…
        • ca8543ea: remove stray UnaryOperator import from arg_rest.rs (unused after walk…
        • a38f4a96: rescue a try block preceded by a conditional guard into a guarded-try…
        • c68e5967: extend bracket_to_dot/literal_logic/template_literal walkers to cover…
        • e68b733f: pin the per-method javac recompile floor to 122/131 now that the reus…
        • e716daac: harden the go buildinfo pointer-form decode with explicit ptr-size bo…
        • d85546f2: split a local slot that holds a reference value and a primitive value…
        • 933f28f5: split a local slot that holds a reference value and a primitive value…
        • 18c7c78f: recover go modulename from the buildinfo blob, decode 32-bit abi type…
        • 9b8bc0e1: undo bundler export aliasing: rename a local binding to the name in `…
        • 2044ce4d: declare a local object when it holds both a constructed value and a c…
        • 959d7bf3: undo bundler import aliasing: restore the developer name from `import…
        • 15608126: recover the babel _createForOfIteratorHelper loop to for…of, runnin…
        • 65738ad2: apply rustfmt across all crates that diverged from fmt canonical
        • 6ca94bf1: apply rustfmt to disrobe-nir and disrobe-query
      • doki-ida
        • bd337e40: Update the theme now automatically crawl
      • NexusRE-MCP
        • 4825ef77: fix: Phase 0-2 architectural overhaul — runtime crashes, cache/sessio…
      • oh-my-dump
        • d75ba667: fix: make HCLI package pass lint
        • 0162fda9: Merge pull request #2 from nevergiveupcpp/dev
        • f76ca8e7: feat: add HCLI support
        • 7afef692: docs: update README
      • PseudoForge
        • db3109ea: docs: update README for temp provenance reporting
        • a75c6953: test: cover address taken temp provenance blockers
        • b503a16e: feat: strengthen temp base provenance reporting
        • 9c14c12f: feat: add cleanup integrity QA gate
        • 726eddad: feat: add dense structural hints
        • 743a7dc1: fix: clear stale llm fallback artifacts
        • 9c0eb6e2: fix: suppress risky unassigned llm renames
        • b1788466: fix: preserve multiline wrapped statements
        • 56b8ab95: feat: track indexed callback corpus evidence
        • 1ce6015e: feat: score indexed callback layout evidence
        • 3156ab47: feat: report indexed callback layout evidence
        • 8e5f5c84: feat: emit opaque target replay eas
        • 1c88af25: feat: group opaque merge targets
        • 074099d9: feat: report opaque merge call targets
        • 1729cc84: feat: classify parameter merge provenance
        • cde94c07: feat: expose merge provenance classes
        • eb014955: feat: report allocation null dominance metrics
        • c06ad448: feat: report call result equivalence metrics
        • e35146a8: feat: report temporary merge provenance metrics
        • 6b1339eb: fix: mask offset derefs in provenance comments
      • server_info
    2. 🔗 Simon Willison sqlite-utils 4.0rc1 adds migrations and nested transactions rss

      sqlite-utils is my combined Python library and CLI tool for working with SQLite databases. It provides an extensive set of higher-level operations on top of Python's default sqlite3 package, including support for complex table transformations, automatic table creation from JSON data and a whole lot more.

      I released sqlite-utils 4.0rc1, the first release candidate for sqlite-utils v4. The major version bump indicates some (minor) backwards incompatible changes, so I'm interested in having people try this out before I commit to a stable release.

      New feature: migrations

      There are two significant new features in this RC compared to the previous 4.0 alphas.

      The first is support for database migrations. This isn't a completely new implementation - it's a slightly modified port of the sqlite-migrate package I released a few years ago. I think that package has proved itself over time, so I'm now ready to bundle it with sqlite-utils directly.

      Here's what a set of migrations in a migrations.py file looks like:

      from sqlite_utils import Database, Migrations
      
      migrations = Migrations("creatures")
      
      @migrations()
      def create_table(db):
          db["creatures"].create(
              {"id": int, "name": str, "species": str},
              pk="id",
          )
      
      @migrations()
      def add_weight(db):
          db["creatures"].add_column("weight", float)

      This defines a set of two migrations, one creating the creatures table and another adding a column to it.

      You can then run those migrations either using Python:

      db = Database("creatures.db")
      migrations.apply(db)

      Or with the command-line migrate command:

      sqlite-utils migrate creatures.db migrations.py

      The system is deliberately small: it doesn't provide reverse migrations, so any mistakes you make should be fixed by deploying a fresh migration to undo them.

      Its predecessor has been used by LLM and various other projects for several years, so I'm confident that the design is stable and works well.

      The new migrations feature is documented here.

      New feature: db.atomic() transactions

      This feature is a lot less exercised than migrations, so it deserves more attention from testers.

      Previously, sqlite-utils mostly left transaction management up to its users, via a with db.conn: construct that reused the sqlite3 mechanism directly.

      SQLite supports nested transactions in the form of savepoints, so I wanted an abstraction that could make those as easy to use as possible.

      I borrowed the terminology "atomic" from Django and Peewee. Here's what the new API looks like:

      with db.atomic():
          db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
          try:
              with db.atomic():
                  db.table("dogs").insert({"id": 2, "name": "Pancakes"})
                  raise ValueError("skip this one")
          except ValueError:
              pass
          db.table("dogs").insert({"id": 3, "name": "Marnie"})

      More details in the documentation.

      Backwards incompatible changes

      The backwards incompatible changes in v4 were described in the alpha release notes. For 4.0a0:

      • Upsert operations now use SQLite's INSERT ... ON CONFLICT SET syntax on all SQLite versions later than 3.23.1. This is a very slight breaking change for apps that depend on the previous INSERT OR IGNORE followed by UPDATE behavior. (#652)
      • Python library users can opt-in to the previous implementation by passing use_old_upsert=True to the Database() constructor, see Alternative upserts using INSERT OR IGNORE.
      • Dropped support for Python 3.8, added support for Python 3.13. (#646)
      • sqlite-utils tui is now provided by the sqlite-utils-tui plugin. (#648)
      • Test suite now also runs against SQLite 3.23.1, the last version (from 2018-04-10) before the new INSERT ... ON CONFLICT SET syntax was added. (#654)

      And for 4.0a1:

      • Breaking change: The db.table(table_name) method now only works with tables. To access a SQL view use db.view(view_name) instead. (#657)
      • The table.insert_all() and table.upsert_all() methods can now accept an iterator of lists or tuples as an alternative to dictionaries. The first item should be a list/tuple of column names. See Inserting data from a list or tuple iterator for details. (#672)
      • Breaking change: The default floating point column type has been changed from FLOAT to REAL, which is the correct SQLite type for floating point values. This affects auto-detected columns when inserting data. (#645)
      • Now uses pyproject.toml in place of setup.py for packaging. (#675)
      • Tables in the Python API now do a much better job of remembering the primary key and other schema details from when they were first created. (#655)
      • Breaking change: The table.convert() and sqlite-utils convert mechanisms no longer skip values that evaluate to False. Previously the --skip-false option was needed, this has been removed. (#542)
      • Breaking change: Tables created by this library now wrap table and column names in "double-quotes" in the schema. Previously they would use [square-braces]. (#677)
      • The --functions CLI argument now accepts a path to a Python file in addition to accepting a string full of Python code. It can also now be specified multiple times. (#659)
      • Breaking change: Type detection is now the default behavior for the insert and upsert CLI commands when importing CSV or TSV data. Previously all columns were treated as TEXT unless the --detect-types flag was passed. Use the new --no-detect-types flag to restore the old behavior. The SQLITE_UTILS_DETECT_TYPES environment variable has been removed. (#679)

      Try it out

      You can install the new RC like this:

      pip install sqlite-utils==4.0rc1

      Or try the CLI version directly with uvx like this:

      uvx --with sqlite-utils==4.0rc1 sqlite-utils --help

      Come chat with us about it in the sqlite-utils Discord channel, or file any bugs in GitHub Issues.

      You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options.

    3. 🔗 r/reverseengineering Scrutari - forensic statistical analyzer for opaque firmware blobs rss
    4. 🔗 Confessions of a Code Addict What Does a Page Table Entry Actually Store? rss

      This is the 5th video in our virtual memory series. In the previous video, we learned about the page table and how the hardware performs a page table walk to do address translation. But merely finding the physical frame address in the page table is not sufficient for the hardware to do a memory access. It also needs to do certain additional checks to make sure that the access is valid. For example, it has to ensure that the page table mapping itself is valid (e.g., the page might have been swapped). Similarly, when doing a memory write, the hardware has to ensure that the page(s) are writable.

      All these checks are done during address translation by looking up additional metadata stored against the page table entry. In this video, we cover what all these metadata bits needed by the hardware are, what their purpose is, and how they are stored in the page table.

      In the next video, we will discuss demand paging. In the meantime, if you haven't yet read the VM article, please do. And if you prefer reading it offline, get the ebook from the link below.

      Get the ebook

      Read more

    5. 🔗 r/reverseengineering Reverse once, run forever: designing client-side defenses that assume the attacker has already read every line rss
    6. 🔗 r/reverseengineering I reverse engineered Windows Copilot into a free OpenAI compatible API (GPT-4o, no API key, no billing) rss
    7. 🔗 r/reverseengineering NØW — Word-Based Shellcode Encoder rss
    8. 🔗 r/reverseengineering Note20 ABL Odin out-of-bounds read rss
    9. 🔗 Mitchell Hashimoto Pledging Another $400,000 to the Zig Software Foundation rss
      (empty)
    10. 🔗 Jamie Brandon 0060: SF rss
      (empty)
  3. June 20, 2026
    1. 🔗 IDA Plugin Updates IDA Plugin Updates on 2026-06-20 rss

      IDA Plugin Updates on 2026-06-20

      Activity:

      • atelier
        • 5e89dc0c: fix(skills): make perf-review stack-agnostic for distribution
        • 410417f0: fix(skills): make perf-review stack-agnostic for distribution
        • dae584cd: feat(skills): add perf-review skill with measured perf gates
        • 62b12a81: test: realign stale assertions with the shipped agent/tool surface
        • f6cd080c: docs(readme): rocket emoji on hero + reformat benchmark tables
      • disrobe
        • eb995072: skip the grpc serve e2e on windows where the spawned server deadlocks…
        • 1318b65c: skip the gnu-bash base64 oracles on macos where bsd base64 differs
        • 0d6362a4: use is_ok_and in the bash base64 probe to satisfy clippy
        • 96e8427c: skip bash base64 oracle when gnu base64 (-w0/-d) is unavailable (eg m…
        • d26bc23b: skip the stack-string oracle when gcc emits no elf object (eg macos m…
        • c7b54edf: skip the gcc resolver oracle when the object is not elf (eg macos mac…
        • 70f3f91f: revert py-decompile chain to source-terminal output to preserve mcp/c…
        • 4843e690: make py-decompile chain manifest deterministic for portable goldens a…
        • 841eca10: js: emit the recovered source as a chain child so auto surfaces it un…
        • 1d7a2a12: fix electron e2e to read recovered js from the mixed fan-out output l…
        • a4a44ba1: js: bless the chain golden for the mixed fan-out sidecar output
        • 8da0b285: lua chain pass emits recovered source + lua.manifest.json sidecar so …
        • a4196d29: js-deob chain: emit detection/recovery/pipeline sidecars as auto chil…
        • 038502a6: py.deob auto/chain emits the peelresult manifest sidecar
        • 906a9ebb: py.decompile chain emits source + manifest sidecar for auto parity
        • 0f4263b3: py.disasm chain emits the structured .dis.json sidecar child so auto …
        • 691b23f6: native packer-unpack chain pass emits dedicated sidecars for auto parity
        • ddd4c944: make external-tool round-trip gates skip when the tool is unavailable…
        • 8766e68f: fmt the hand-edited capabilities rules and frisk tests
        • 6bbaaa90: recover swift protocol requirements from __swift5_protos descriptors
      • rikugan
        • a9bcac0c: chore(release): bump version to 1.2.1
        • 0566d891: Merge branch 'feat/ui-review-sprint1'
      • ToCode
        • 3eba5881: Merge pull request #9 from buzzer-re/dev/improves
        • 942b3320: Gate IDA cache rebuild behind -purge-cache
        • 31b19fb5: Recover from a broken cached IDA database
        • bad636ac: Rebuild stale IDA caches and relativize absolute source paths
        • 4f3502f2: Merge pull request #8 from buzzer-re/dev/improves
        • 3e932ecd: Ignore missing elftools imports in mypy
        • 11073372: Import DWARF line/file info in the IDA loader
        • dba1445a: Fix IDA source/type recovery to use the real IDA APIs
    2. 🔗 earendil-works/pi v0.79.9 release

      New Features

      • Chat-template thinking compatibility - OpenAI-compatible custom providers can map Pi thinking levels into chat_template_kwargs, enabling vLLM/Hugging Face chat-template models such as DeepSeek to use provider-native thinking controls. See Custom Provider API Types and OpenAI Compatibility.
      • GLM-5.2 provider improvements - GLM-5.2 now has corrected Fireworks OpenAI-compatible routing and OpenRouter xhigh thinking support, improving /model behavior and high-effort reasoning for GLM-5.2 users. See Model Options.

      Added

      • Added inherited configurable chat-template thinking support for OpenAI-compatible providers that use chat_template_kwargs, such as DeepSeek models behind vLLM (#5673).

      Fixed

      • Fixed inherited Fireworks GLM-5.2 metadata to use the OpenAI-compatible Chat Completions endpoint with reasoning_effort support (#5923).
      • Fixed same-directory session switches to reuse imported extension modules while preserving fresh extension instances and lifecycle events (#5905).
      • Fixed deep session branches taking quadratic time to build context or branch paths (#5909).
      • Fixed inherited OpenRouter GLM-5.2 metadata to expose xhigh reasoning and send OpenRouter's native xhigh effort (#5770).
      • Fixed inherited Markdown streaming code fence rendering so partial closing fences no longer make code blocks shrink or flicker while content streams (#5846 by @xl0).
      • Fixed fuzzy edit matches to preserve untouched line blocks instead of rewriting the whole file through normalized content (#5899).
      • Fixed bash commands through legacy WSL bash.exe to pass scripts over stdin so shell variables expand in the target bash (#5893).
      • Fixed /model to hide GitHub Copilot models that are unavailable to the authenticated account (#5897).
      • Fixed /model selector search to rank exact provider-prefixed matches before proxy-provider model ID matches (#5892).
    3. 🔗 r/reverseengineering Built a full Android RE suite that runs on-device — Radare2 CFG graphs, 8 Java decompilers (CFR, Procyon, Krakatau…), Flutter & il2cpp support rss
    4. 🔗 r/reverseengineering Reverse engineered Pixel 7 Tensor G2 access rss
    5. 🔗 r/reverseengineering DOS Game "F-15 Strike Eagle II" reversing project needs DOS test pilots rss
    6. 🔗 sacha chua :: living an awesome life La semaine du 7 au 14 juin rss

      lundi 8

      Pendant la routine matinale, j'ai remarqué qu'une des nouvelles boucles d'oreilles de ma fille était perdue. J'ai cherché dans son lit et sa chambre, mais je ne l'ai pas trouvée. Pendant que j'expliquais à mon mari, je l'ai remarquée sur le sol dans la cuisine. J'ai réussi ! C'est ma vie de pie.

      L'école a eu un remplaçant, donc je l'ai prévenue de son absence. Ma fille et moi sommes assises dehors et nous avons fait quelques devoirs comme un jeu de Donjons et Dragons. Par exemple, il fallait que ma fille récapitule le chapitre de son devoir de lecture pendant qu'elle était à l'envers à cause d'un piège que mon roublard a dû désamorcer. Je pense que c'est plus facile de faire ses devoirs si ma fille peut bouger.

      Nous avons alterné entre les devoirs et notre propre partie de Donjons et Dragons. Nous avons essayé le module gratuit qui s'appelle Dagger Danger. L'interface virtuelle de D&D Beyond était un peu difficile à apprendre. Nous avons préféré utiliser les dés physiques.

      J'ai emmené ma fille à son cours de gymnastique. Son entraîneur habituel n'était pas là. Il y avait une remplaçante qui s'appelle Ashley. Ma fille n'aime pas s'habituer aux nouvelles personnes, mais à mon soulagement, elle a continué la séance.

      Après le cours, elle a eu très chaud. Je l'ai emmenée au glacier Scoops pour essayer leur crème glacée. Ils ont une ristourne pour les enfants entre 15h00 et 17h00. Elle a commandé la crème glacée à la fraise dans un cornet sucré et elle a payé cash.

      Ma fille a cueilli des petites fraises dans la cour avant. Il y avait quelques fraises que les insectes ont mangées, mais il y avait certaines fraises qui étaient meilleures que celles que nous achetons au supermarché. J'étais surprise que les écureuils ne les aient pas mangées.

      Mon mari a grillé du poulet au miel et à l'ail. C'était délicieux.

      Avant de faire la vaisselle, ma fille et moi avons eu un très long câlin. Je lui ai dit que je ne veux pas la lâcher en premier, donc j'ai pensé qu'elle avait aussi décidé de ne pas lâcher d'abord. C'était une merveilleuse impasse. Finalement, elle m'a dit qu'elle voulait m'aider avec la vaisselle, donc nous l'avons faite.

      J'ai acheté les livres de mathématiques Beast Academy 4, et nous les avons reçus tard le soir. Ma fille est restée debout tard parce qu'elle a voulu lire tous les livres.

      mardi 9

      Ma fille a décidé de sécher les cours aujourd'hui. Elle était fatiguée et grincheuse parce que j'étais préoccupée par de la paperasse.

      Mon mari, ma fille et moi avons livré des dons pour la Bike Brigade.

      J'ai travaillé sur la paperasse pour les assurances-vie. Je les ai revérifiées contre les confirmations parce que les bénéficiaires irrévocables diffèrent.

      Sur Tileman Reworked dans Stardew Valley : enfin, j'ai accédé au forgeron et au musée le 11 de l'hiver de la quatrième année. J'ai amélioré mon arrosoir. J'ai aussi pêché à la fête du calmar pendant que j'écoutais l'enregistrement de mon rendez-vous avec mon tuteur.

      mercredi 10

      J'ai vérifié que j'avais reçu le virement bancaire, et j'ai envoyé quelques questions sur les assurances-vie.

      J'ai travaillé comme consultante. J'ai mis à jour mon logiciel pour copier des données parce que j'avais remarqué que les nouvelles données avaient changé l'identifiant. Je dois le vérifier après l'avoir exécuté. J'ai aussi ajouté la capacité d'afficher des vidéos à la galerie.

      Pendant que ma fille jouait avec ses amies à la pataugeoire, j'ai corrigé la transcription de la conversation entre ma sœur et notre cousine, une de ses meilleures amies depuis l'enfance. Elles sont toujours allées à la même école, et quand sa famille était notre voisine, ma sœur allait souvent chez elles. La reconnaissance vocale a fait plusieurs erreurs parce que ma sœur et notre cousine avaient utilisé deux langues. La majorité des histoires étaient en anglais, mais il y avait aussi des phrases en filipino. WhisperX peut transcrire quelques phrases philippines, mais d'autres phrases étaient traduites en anglais. Ce n'était pas grave. J'ai écouté l'enregistrement sur mon smartphone et j'ai écrit mes corrections sur ma tablette, et j'ai passé un bon moment.

      J'ai perdu momentanément mon Apple Pencil. Je l'ai trouvé dans mon gilet à nombreuses poches.

      jeudi 11

      J'ai parlé d'Emacs avec Prot.

      J'ai eu rendez-vous chez le dentiste pour faire des plombages. Je lui ai expliqué ma situation avec les limites du petit compte gestion-santé et mon budget pour les frais dentaires par année. C'est définitivement une grande dépense cette année, mais je pense que c'est mieux que je trouve un dentiste qui prend les bonnes précautions contre le COVID.

      Ma fille et moi avons lu ensemble deux livres illustrés sur le TDAH et sur l'autisme.

      Ma fille n'a pas déjeuné parce qu'elle a eu besoin de la salle de bain.

      vendredi 12

      Ma fille n'a pas voulu participer à l'école parce qu'il y avait encore un remplaçant. Nous avons fait deux exercices de ses devoirs ensemble.

      J'ai eu rendez-vous avec mon tuteur pour m'entraîner à la conversation. Nous avons parlé du temps, des voyages et des réseaux.

      J'ai transféré l'argent que j'avais reçu de ma sœur aînée à l'autre banque.

      Ma fille et moi sommes allées à la banque. J'ai payé l'amende et elle a déposé de l'argent.

      Elle a perdu une de ses boucles d'oreilles. Heureusement, je l'ai retrouvée près de sa chaise. La prochaine fois, je dois les resserrer en les nettoyant.

      samedi 13

      J'ai travaillé sur la paperasse de l'assurance-vie.

      Sur Donjons et Dragons, nous avons gardé les prisonniers.

      Sur Stardew, j'ai amélioré ma pioche et ma hache.

      Mon mari, ma fille et moi avons regardé le film Donjons et Dragons parce que maintenant ma fille peut comprendre toutes les blagues. Nous nous sommes amusés. #fr

      dimanche 14

      Mon tuteur français habituel va prendre des vacances la semaine prochaine. Je pense à trouver un autre tuteur pour explorer d'autres méthodes d'apprentissage. Si l'un d'eux me plaît, je peux replanifier mes rendez-vous pour faire une alternance. C'est mieux que je m'entraîne à écouter des personnes variées.

      J'ai mis à jour le mot de passe de ma fille avec l'aide de son enseignant.

      J'ai travaillé comme consultante. J'ai corrigé des erreurs après la mise à jour du système.

      Malgré la pluie, ma fille et moi sommes allées au supermarché à pied pour acheter des œufs et d'autres aliments.

      Lizzy a commencé un nouveau personnage qui s'appelle Celeste, une magicienne tieffeline. J'ai créé une guerrière qui s'appelle Olga pour l'accompagner. Nous avons collecté les chèvres perdues de Cornflower.

      Ma fille était déçue parce que nous n'avions pas d'œufs pour préparer une grande omelette comme d'habitude. Elle s'est assise contre sa porte.

      You can e-mail me at sacha@sachachua.com.

    7. 🔗 r/reverseengineering GitHub - lautarovculic/ioscpy: A macOS CLI that mirrors and controls a jailbroken iPhone over USB. rss
    8. 🔗 r/reverseengineering sterrasec/apk-interceptor: Android deeplink, Intent, and WebView bridge assessment helper rss
    9. 🔗 Stephen Diehl Prism: An Impure Functional Language With Typed Effects rss

      Prism: An Impure Functional Language With Typed Effects

      This is going to be a very nerdy post so bear with me. Here is a function. Read it the way you would read any other function, and then tell me its type.

      fn fib(n) =
        var a := 0
        var b := 1
        repeat(n) fn
          let t = a + b
          a := b
          b := t
        a
      

      That is a mutable loop. There is a var, there is assignment, there is a temporary so the swap does not eat itself. It is, line for line, the fib you would write in Python after deciding that recursion was a young person's game.

      Its type is Int -> Int, it is functional but in place. There is no effect type even though the function has effects, because the effects are not observable from outside the function. As far as anyone calling it is concerned, this function is pure. It mutates two variables in place and then, before the door closes behind it, sweeps up the evidence and leaves no fingerprints. And the compiler does it all for you. It's the code you would write in Python with types you get from OCaml and no monads.

      This is Prism, a proof of concept functional compiler I've been working on for the last three years, built around modeling effects with modern types inspired by the intellectual lineage of OCaml 5, Haskell and Koka. The big idea of the last five or six years of functional programming is that effects are real, effects are fine, and the interesting question is not how to avoid them but how to put them in the type system and then optimize them until they cost nothing.

      Effects Are Interfaces

      The one idea you need is the algebraic effect handler. An effect declares operations; a handler gives them meaning. Here is a producer that yields a sequence and has no idea who is listening:

      effect Gen {
        ctl yield(Int) : Unit
      }
      
      fn produce(n) : !{Gen} Unit =
        if n == 0 then
          ()
        else
          yield(n)
          produce(n - 1)
      

      The !{Gen} in the type is the function confessing, in writing, that it performs the yield operation and someone upstream had better deal with it. Now we hand the same producer to two different handlers:

      fn total(n) =
        handle produce(n) with
          yield(v, k) => v + k(())
          return r => 0
      
      fn count(n) =
        handle produce(n) with
          yield(v, k) => 1 + k(())
          return r => 0
      

      The k is the continuation, the rest of the computation, reified as an ordinary value you can hold in your hand. total resumes it and adds; count resumes it and counts. A handler can ignore k entirely (that is an exception), call it once (that is state, or a generator), or call it many times. This last one is the move that makes algebraic effects more than sugar. Here a handler finds Pythagorean triples by resuming the same continuation once per candidate, which is to say it explores a whole search tree using nothing but straight-line code and a handler that says "yes, and also try the other branch":

      effect Amb {
        ctl choose(Int) : Int,
        ctl reject(Unit) : Int
      }
      
      fn triple(n) : !{Amb} Int =
        let a = choose(n)
        let b = choose(n)
        let c = choose(n)
        if a > 0 && b > 0 && a <= b && a * a + b * b == c * c then
          a * 10000 + b * 100 + c
        else
          reject(())
      
      fn solutions(n) =
        handle triple(n) with
          choose(m, k) => flatten(map(\(i) -> k(i), range(0, m)))
          reject(u, k) => Nil
          return r => Cons(r, Nil)
      
      fn main() =
        let sols = solutions(14)
        println(length(sols))
        println(sum(sols))
      

      choose(n) offers a value in 0..n-1 and reject() prunes a dead branch, and because the handler resumes k once for every candidate, triple reads like a function that just picks three numbers.

      If you have used OCaml 5 this will feel familiar, except OCaml keeps its effects out of the types, so you find out about an unhandled one at runtime, in production, on a Friday. If you have used Haskell this will also feel familiar, except in Haskell you would be assembling a monad transformer stack, lifting each operation through every layer by hand, and explaining to a junior colleague that a monad is just a monoid in the category of endofunctors, what's the problem. Prism's effects are row polymorphic. They union structurally across calls. There is nothing to stack and nothing to lift, because there is no tower, only a set.

      One Trick, Five Ways

      Once effects are first class, a remarkable number of ideas from the last thirty years of language design turn out to be unified under the same mechanism:

      Exceptions are a handler that throws away the continuation. A clause marked final ctl discards k, so its body's value becomes the handler's result and the rest of the computation is simply abandoned. No Result threading, no ? confetti up the call stack, just direct-style code that stops:

      fn safe_grade(n) =
        handle grade(n) with
          final ctl abort(msg) => concat("invalid: ", msg)
          return r => r
      

      And because an exception is just a label in the effect row, you get extensible exceptions for free. Each distinct failure is its own operation, so the row in a function's type spells out exactly which exceptions can escape it, the way !{Gen} spells out that it yields. There is no root Exception class to inherit from and no hierarchy to edit; a new exception is just a new label. They union structurally across calls, so a function that can abort and a function that can timeout compose into one whose row carries both. Handling one of them discharges its label and leaves the rest in the row, which means partial recovery is something the type system tracks rather than something you promise in a comment:

      effect Abort   { ctl abort(String) : Unit }
      effect Timeout { ctl timeout(Int)  : Unit }
      
      -- fetch's row spells out both failures it can raise
      fn fetch(id) : !{Abort, Timeout} String =
        if id < 0  then abort("bad id")
        if id > 99 then timeout(id)
        "ok"
      
      -- discharge Timeout with a fallback; Abort still escapes
      fn with_default(id) : !{Abort} String =
        handle fetch(id) with
          final ctl timeout(_) => "cached"
          return r => r
      

      The handler peels Timeout off, so with_default is left carrying exactly !{Abort}, no more and no less. Java's checked exceptions wanted to be this and could not, because they were welded to the class hierarchy instead of being an open, structural set.

      Generators and streams are a producer that performs emit, transformers that catch it and re-emit, and a consumer that folds. A pipeline is handlers nested around one producer, which means there is no intermediate list, by construction:

      srange(1, n).smap(square).skeep(even).stake(5).ssum()
      

      Stopping early, the stake(5), is just a handler dropping a continuation once it has what it needs. Cancellation produces garbage, and that garbage is reclaimed at a statically known point with no collector involved, which is the good part we will come back to. The stream library was inspired by Haskell's pipes and conduit.

      Lenses are not a library anymore they are language-integrated. They are record-update paths plus the memory model. Given three nested record types, one path expression reaches arbitrarily deep and sets several fields at once:

      type Vec2   = Vec2   { x: Int, y: Int }
      type Player = Player { pos: Vec2, hp: Int }
      type Game   = Game   { player: Player, score: Int } deriving (Lens)
      
      let g2 = { g | player.pos.x = 30, player.hp = 95, score = 110 }
      

      That rebuilds the spine of the nested record, and when the value is uniquely owned, each rebuild reuses the cell it just took apart, so a functional update compiles to a pointer write. No optic types are allocated, nothing is composed at runtime, the path is just addresses. The entire optics ecosystem, the van Laarhoven encoding, the profunctor zoo, the operators that look like a cat walked across the keyboard, all of it collapses here into one syntax rule and a memory discipline. And when you genuinely need to pass an accessor around, deriving (Lens) hands you score_of and with_score as ordinary functions:

      let g3 = with_score(g2, 200)   -- score_of(g3) == 200
      

      They are boring functions, which is the highest compliment in functional programming!

      Mutable state is the var from the opening. Each var desugars to a private effect with get and set operations, discharged by a handler installed at the end of its block. The state never escapes, an analysis rejects any closure that would try to smuggle it out, and the enclosing function keeps its empty row. This is the loop you would write in Python with the signature you would want in Haskell and none of the State monad plumbing in between.

      Failure is the most fun, because it is functional logic programming sneaking in through the effect row. An anonymous Fail effect makes "this expression might not produce a value" a thing the type system already knows how to talk about. fail() performs it, guard(cond) performs it when a check is false, and the consumers read like a wish list:

      let port = cfg.at_map("port") ?? cfg.at_map("https") ?? 443
      let off  = customer?.tier?.discount ?? 0
      let bill = [item for item in sof(cart), if prices.at_map(item) > 4]
      

      ?? falls back when the left side fails. ?. chains through options and short-circuits. The comprehension guard prunes elements that fail instead of crashing. And because var is itself just handler sugar, an entire block can be transactional: transact snapshots every live variable, runs the body in a failure context, and rolls everything back if it fails, so an overdrawn account behaves as if the purchase never happened.

      transact
        balance := balance - price
        guard(balance >= 0)
        balance
      else
        0
      

      Five features. One underlying mechanism, viewed through a five dimensional prism. And that lovely idea is the namesake!

      Modern Types

      So far we haven't seen a lot of type signatures which is the point, most of the time you can write down quasi-Python-looking code and inference is decidable and predictable via the usual complete-and-easy Dunfield-Krishnaswami algorithm. You annotate only where you genuinely cross into higher-rank territory, which is rare, and the algorithm meets you exactly at that boundary. A function can demand a genuinely polymorphic argument, declared with a forall on the binder, and then use it at several types in one body:

      fn pick(g : forall a. (a) -> a) : Int =
        if g(true) then
          g(10)
        else
          g(20)
      
      fn main() =
        println(pick(\(x) -> x))
      

      g is forced to be polymorphic, so pick may apply it to a Bool and an Int in the same breath, which is why main can only hand it the identity function and not, say, a number. A Damas-Milner core would have unified a with Bool on the first call and rejected the second; here the forall survives into the argument.

      Ad-hoc polymorphism is type classes, but Lean-flavored: instances are named values, not anonymous magic resolved by global coherence. You write given Ord(a) to ask for a dictionary, and you can name exactly which one you want at the call site when more than one is in scope:

      instance ordDesc : Ord(Int) {
        fn cmp(x, y) = int_cmp(y, x)
      }
      
      sort_by_ord(xs)            -- the default Ord(Int)
      sort_by_ord[ordDesc](xs)   -- this one, reversed
      

      The instances you do not care about, you do not write. One deriving clause off a type declaration synthesizes the boring ones, and the field accessors too:

      type Vec2 = Vec2 { x: Int, y: Int } deriving (Eq, Ord, Show, Lens)
      
      with_x(v, 7)   -- a derived setter, and FBIP-reused when v is unique
      

      Classes also feed pattern matching, which is the part that tends to make PL people sit up. A pattern can name a class method as its view, and then that one pattern deconstructs every type with an instance, dispatched by dictionary exactly like a method call:

      pattern First(n) for Peek = view peek
      
      fn head_or(x : c, d : Int) : Int given Peek(c) =
        match x of
          First(n) => n
          _ => d
      

      First(n) matches a Box, a Range, or anything else that is Peek, and head_or is generic over all of them at once. The pattern is as polymorphic as the function around it.

      Some ad-hoc polymorphism does not even need a class. show is type-directed: the compiler infers the static type of its argument and synthesizes a structural printer from the real constructor names, recursing into fields, with no instance to write and no runtime type dispatch (the printer is monomorphized from the static type, so it never reads a runtime tag to decide how to print):

      show(42)                   -- "42"
      show([1, 2, 3])            -- "[1, 2, 3]"
      show((7, false))           -- "(7, false)"
      show(Node(Leaf, 1, Leaf))  -- "Node(Leaf, 1, Leaf)"
      

      And the third axis is the one the rest of the post is really about: the same row variable that makes effects composable makes them polymorphic. Here is a higher-order function that calls its argument twice and adds the results. The argument's effect row is a variable e, which is to say twice does not care what f does, only that it returns an Int:

      fn twice(f : (Unit) -> Int ! {| e}) = f(()) + f(())
      

      The {| e} is "this row, and whatever else." Each call site unifies e with the actual row of the thunk it passes, and that is the whole trick: one definition serves a pure argument, an effectful one, and an effectful one of a completely different effect, with no overloads and no wrapping.

      fn pure_use() =
        twice() fn(u)
          21
      

      Here e unifies with the empty row {}. No effect is performed, so no handler is needed, and the result is just 42. Now force the same twice to carry an effect through:

      fn tick_use() =
        handle twice(\(u) -> tick(())) with
          tick(u, k) => \(n) -> k(n)(n + 1)
          return r => \(n) -> r
      

      The thunk performs Tick, so e unifies with {Tick}, and the surrounding handle discharges exactly that one label. Swap in a thunk that performs Say instead and e becomes {Say}; twice itself never changed. A handler only ever names the labels it wants, and e quietly carries everything else along, which is what lets these functions compose without a transformer stack to thread them through.

      The same machinery, three times: one definition, abstracted over types, over dictionaries, and over effects.

      Zero-cost Abstractions

      At this point the cynical hater reader (hi Hacker News!) is thinking the thing most armchair peanut gallery types think about elegant abstractions, which is: sure, but what does it actually cost. Algebraic effects have a reputation. The textbook implementation reifies your whole computation into a free monad, a tree of "here is an operation, and here is a function for what to do with its result," and then an interpreter walks that tree allocating a small cell at every single operation. It is beautiful and it is a heap allocation per yield.

      Prism does not do that on the path that matters. The fast path is evidence passing in the Koka lineage, with my own deviations noted where they matter: instead of reifying the computation and reaching for the handler, it carries the active handler clause to each operation site as an ordinary parameter. A do op becomes a direct call. So we don't need a tree, or interpreter loop, or cell. The only allocation is one closure per handler when you install it, which is O(handlers), not O(operations). You can perform yield a billion times under a handler that was allocated exactly once.

      The free monad is still in the building, because some patterns genuinely need it (a computation that escapes into a data structure, a truly multishot resumption, a masked handler). But it is the fallback, not the default, and the compiler proves which one you are in.

      Now point this at the stream pipeline from earlier:

      for n in srange(1, 11).smap(square).skeep(even) do
        print(n)
      

      An interprocedural flow analysis works out, across function boundaries, exactly which effect evidence each producer and transformer needs, and threads it through. The whole chain lowers to a single loop with zero intermediate lists and zero per-operation cells. This is essentially Haskell-like stream fusion, but the thing Haskell achieves with rewrite rules that fire only if you hold your imports correctly and the wind is blowing precisely the right direction while Mercury is in retrograde, and what Rust achieves by monomorphizing iterator adapters into one another at the type level. Here it falls out of the effect compiler, because once emit is a direct call to a known clause, inlining the clause into the producer is just inlining.

      The cleanup, meanwhile, is the good part promised earlier, and it is not a garbage collector. Prism uses Perceus reference counting: every heap cell is freed at a statically known point, deterministically, with no pauses and no tracing. And then the clever idea is that we have frame-limited reuse, where a cell you just deconstructed in a pattern match gets handed straight back to the constructor on the other side of the arrow, so map over a uniquely owned list mutates the list in place while remaining, on paper, a pure function returning a new one. The lens update compiling to a pointer write is this same machinery. So is the loop in fib not allocating. Purity and mutation turn out to be the same thing viewed from different ends of an ownership analysis, which is either a deep idea about the nature of computation or quite banal, and I'm not quite sure which.

      The Runtime & LLVM Backend

      If Perceus reference counting sounds familiar, it should: it is the same memory discipline Lean 4 runs on, and for the same reason, a dependently typed proof assistant cannot afford GC pauses any more than a game loop can. Lean compiles to C and links a little runtime that does the counting. Prism does the structurally identical thing, except it emits LLVM IR (through inkwell) and, for the same program, a text MLIR module, and then hands the result to clang to link against one hand-written C runtime, prism_rt.c, about a thousand lines.

      That runtime is deliberately tiny. A heap cell is four-plus words, { refcount, tag, arity, fields... }, and the whole file is the allocator, the rc_inc/rc_dec pair that the compiler sprinkles through the code, the in-place reuse allocator that the FBIP pass targets, and the bignum and string primitives. There is no collector thread, no card table, no safepoints, nothing that runs when your code is not running. The reference counting is not a service the runtime provides at runtime; it is code the compiler already wrote into your program, and the C file is just where the four or five operations it calls happen to live.

      It links against plain libc malloc by default (there is an opt-in mimalloc knob for benchmarking, but the whole thesis is "do not allocate," and a fast allocator only hides the allocations you failed to eliminate). A live-cell oracle asserts the heap balances to zero at exit, so "garbage free" is a property the test suite checks rather than a thing the README asserts.

      Runs In Browser through WASM

      The same interpreter that serves as the differential oracle compiles to wasm, so there is a playground where you can type Prism and run it without installing anything. It runs the program in a worker, and the buttons next to it will dump the inferred type signatures (effect rows and all) and the lowered core IR, so you can watch a var loop or a stream pipeline turn into the thing it actually compiles to. Every example in this post is in the dropdown. Poke at the effect rows; they are the whole point. The full source, the Lean model, and the C runtime all live in the prism repository on GitHub.

      Nerd Stuff

      For completeness, if you have read this far you have clearly made some very questionable life choices (hi fellow traveller!) so here's the PL nerd stuff:

      • Type inference is the usual SOTA bidirectional and higher-rank, the complete-and-easy Dunfield-Krishnaswami algorithm, so rank-N polymorphism works without you annotating your way out of it.
      • Typeclasses with Lean-style named instances (instance ordInt : Ord(Int)), explicit override (sort_by_ord[ordRev](xs)), and deriving (Eq, Ord, Show).
      • A mostly OCaml-ish-shaped surface syntax : layout blocks, dot chains (xs.over(f).keep(g).sum()), with sugar for continuation-passing code, string interpolation, effect row aliases.
      • Deep recursion runs in constant stack, both natively (tail calls, and tail recursion modulo a constructor) and in the interpreter (it is essentially a more advanced version of the old CEK machine, so nothing in your program can blow the host stack).

      And, for the genuinely afflicted, the full intellectual lineage. Obviously standing on the work of many FP giants, but the ones that are most directly inspired Prism's design are:

      The thesis, if a toy project gets to have one, is that "purely functional" was always a slightly defensive name for a good idea. The good idea is that effects should be visible, typed, and composable. You do not need to forbid them to get that; you need to track them. And once you are tracking them honestly, the compiler has enough information to make them free, which is the part the purity narrative never promised you, because it was too busy not making eye contact with the IO monad.

      This compiler is essentially my love letter to the old 2010-20s era of functional programming which was a much simpler and happier time (or maybe that's just the rose-tinted glasses of youth in the ZIRP era). This compiler is a toy, it is for all intents and purposes useless except maybe as kind of a piece of art from a bygone time before the Transformer-era of programming. The world we're headed to doesn't really have a place for this kind of thing anymore. We're increasingly headed to a world in which maximally probabilistic hilariously-uninteresting Typescript increasingly runs most of the world as we babysit these token extruders while the economy and markets become increasingly automated by agents. While this makes me kind of sad, it doesn't mean we still can't build this kind of thing just for fun and that's what I did here. Maybe the future of functional programming languages is increasingly niche-but-economically-irrelevant and becomes more like a hobby pursuit for the few of us who still love it for the intellectual beauty of the underlying ideas. So I built it anyways, and it runs, and it was a blast to build. And just maybe that's enough, in this brave new world of software.

  4. June 19, 2026
    1. 🔗 IDA Plugin Updates IDA Plugin Updates on 2026-06-19 rss

      IDA Plugin Updates on 2026-06-19

      Activity:

    2. 🔗 r/reverseengineering Analyzing Bytes: Pre-Disassembly Static Binary Analysis rss
    3. 🔗 backnotprop/plannotator v0.21.0 release

      Follow @plannotator on X for updates

      Missed recent releases? Release | Highlights
      ---|---
      v0.20.3 | Annotations no longer lost when clicking away, off-screen indicator for open comments
      v0.20.2 | Pierre CodeView all-files review, large-PR pipeline and instant-open checkout, unified agent engine selection, Pi programmatic plan mode
      v0.20.1 | Pi extension install hotfix (pinned @pierre/diffs after a broken upstream release)
      v0.20.0 | Multi-repo workspace reviews, semantic diff overview, UI 2.0 themes and plan look chooser, leaner single-source skill install
      v0.19.27 | Kiro CLI integration, Glimpse native window, annotate-last message picker
      v0.19.26 | Amp plugin production fixes, Mermaid rendering fix, Settings flicker fix, update notification toast and shimmer
      v0.19.24 | Amp integration, configurable data directory, Auto Mode permission option, Pi plan approval fix
      v0.19.23 | Droid integration, Windows Pi AI fix, quieter update indicator
      v0.19.22 | Safari copy fix in plan viewer, CLAUDE_CONFIG_DIR support for session logs
      v0.19.21 | Ask AI in plan review and annotate mode, shared AI runtime, origin-aware provider defaults
      v0.19.20 | Interactive goal setup UI, OpenCode submit_plan fixes, browser no-op sentinel handling for Claude agents


      What's New in v0.21.0

      This is a large release for the annotate experience. Seventeen pull requests landed since v0.20.3, five of them from external contributors, including first contributions from four new community members. The headline is a set of features that turn annotate mode into a place where you can read, edit, run an agent, and jump into your own tools without leaving the document. Alongside that, several reliability fixes harden the paths that remote and CI users depend on.

      Edit Documents Directly in Annotate Mode

      Until now, annotation was a one-way conversation: you highlighted text, wrote a comment, and the agent received your notes. If you wanted to change a sentence yourself, you described the change instead of making it.

      This release adds a real markdown editor to annotate and plan mode. You can switch a document into edit mode, change the text directly, and the difference between the original and your edits is captured as a clean diff that travels to the agent alongside your annotations. The edit controls were reworked so the state is always legible: a clear Edits label, Save emphasis when there are unsaved changes, and a Save/Cancel pair where Cancel discards your changes after a confirmation rather than silently keeping them.

      Saved source-file edits now survive a refresh. When you edit a file in annotate-folder mode and save it, the right-side Edits card is persisted in the annotation draft and restored after a reload. Restored edits are validated against the file's current contents on disk, so stale context is dropped rather than resurfaced, and the edit context is never sent to the agent if it no longer matches the file.

      PR #890, #928, #936 by @backnotprop.

      A Live File Tree for Annotated Folders

      When you annotate a folder, the file tree on the left is now live. It uses a server-side file watcher rather than polling, so the tree updates as files change on disk. Git status drives per-file badges and folder totals, showing which files are modified, added, or deleted, with line counts for changes. Direct source-file edits made inside Plannotator show their save state in the tree as well: clean, dirty, saving, saved, or conflict.

      Two follow-up rounds hardened this. Nested ignored folders like node_modules and dist are now pruned by path segment so the watcher never walks into them, watcher teardown is identity-safe so reconnects can't orphan old watchers, and git status reads run asynchronously with timeouts and request coalescing so a slow repository can't block the server. A startup-latency fix ensures the tree's first snapshot renders before the live watcher subscribes, so the tree no longer sits on a loading state while the watcher sets up.

      PR #931, #933, #935 by @backnotprop.

      An Agent Terminal Inside Annotate Mode

      Annotate mode now includes an optional agent terminal panel. You can run a coding agent in a real terminal next to the document you're annotating, so the back-and-forth happens in one place instead of a separate window.

      The terminal is backed by a PTY hosted in a small Node sidecar, since the compiled Plannotator runtime can't load native terminal bindings directly. On a normal install the runtime is provisioned automatically, and where it isn't available the panel disables itself cleanly rather than failing the session. Requires Node.js 20 or newer on your PATH.

      PR #941 by @backnotprop.

      Open Files in Your Editor, Terminal, or File Manager

      A new "open in" control lets you launch the file you're looking at directly in an external application: VS Code, Cursor, Zed, Ghostty, iTerm2, Warp, Sublime Text, Xcode, Android Studio, or reveal it in Finder or Explorer. Only applications you actually have installed appear in the menu, the last one you used becomes the default, and copy-path and copy-diff actions are always available. The control hides itself in remote sessions where opening a local app wouldn't make sense.

      This release also includes a pass on the code-review UI. The semantic diff moved from a separate docked panel into an inline accordion in the all-files view, file headers and the file tree were reworked, and the diff-freshness check was tidied.

      PR #942 by @backnotprop.

      HTML Files Render as HTML

      Annotating an .html file now renders the page as HTML by default instead of converting it to markdown. For most HTML — rendered reports, exported documents, generated pages — this is what you actually want to look at. If you need the old behavior, pass --markdown to convert the page to text with Turndown. The serving path for raw HTML and its assets was also hardened against symlink escapes.

      This is a behavior change for anyone who ran plannotator annotate file.html expecting a markdown view. The conversion is one flag away.

      PR #924, #927 by @backnotprop.

      Additional Changes

      • Remote sessions always show a reachable URL. The session URL is now printed to the terminal for remote sessions regardless of whether URL sharing is enabled, and local sessions print the URL as a fallback when the browser can't be opened (a headless box or devcontainer). This closes a long-standing gap where remote and headless users could be left with no link and a hung review. PR #929, #945 by @backnotprop.
      • Ask AI works on Bedrock and Vertex. Bare model aliases were being rejected with a 400 on Amazon Bedrock and Google Vertex; model ids are now resolved to the correct provider-specific identifiers, with a safe fallback when none is configured. PR #939 closing #938 by @masterpavan.
      • Release binaries ignore callerbunfig.toml. Compiled binaries no longer autoload a bunfig.toml from the directory they're run in, so a stray or hostile config in a project folder can't alter or crash the binary. PR #937 by @jrpat.
      • Light-mode contrast fix in code hover previews. Code hover previews used a dark-mode color in light mode, making text hard to read. PR #934 by @hl662.
      • Turn URL sharing off via config. URL sharing can now be disabled through ~/.plannotator/config.json ({ "share": "disabled" }) in addition to the environment variable. PR #921 by @yonihorn.
      • Save to Obsidian works from annotate sessions. The annotate server was missing the /api/save-notes endpoint, so the "Save to Obsidian" button silently failed; the endpoint is now wired in. PR #884 closing #844 by @titosemi.

      Install / Update

      macOS / Linux:

      curl -fsSL https://plannotator.ai/install.sh | bash
      

      Windows:

      irm https://plannotator.ai/install.ps1 | iex
      

      Extra skills (compound, setup-goal, visual-explainer), opt-in:

      npx skills add backnotprop/plannotator/apps/skills/extra
      

      Claude Code Plugin: Run /plugin in Claude Code, find plannotator , and click "Update now".

      OpenCode: Clear cache and restart:

      rm -rf ~/.bun/install/cache/@plannotator
      

      Then in opencode.json:

      {
        "plugin": ["@plannotator/opencode@latest"]
      }
      

      Pi: Install or update the extension:

      pi install npm:@plannotator/pi-extension
      

      Droid: Install via the plugin marketplace:

      droid plugin marketplace add backnotprop/plannotator
      droid plugin install plannotator@plannotator
      

      Amp: Install the CLI first, then copy the plugin:

      mkdir -p ~/.config/amp/plugins
      curl -fsSL https://raw.githubusercontent.com/backnotprop/plannotator/main/apps/amp-plugin/plannotator.ts \
        -o ~/.config/amp/plugins/plannotator.ts
      

      Kiro CLI: The installer auto-detects Kiro and installs skills automatically. After installing the CLI, launch with:

      kiro-cli chat --agent plannotator
      

      Upgrading from before v0.20.0? Read the v0.20.0 release notes first; that release changed how skills install.


      What's Changed

      • feat(editor): markdown edit mode — direct document editing with diff-to-agent feedback by @backnotprop in #890
      • feat(config): support share toggle via config.json by @yonihorn in #921
      • feat(annotate): render HTML annotations as HTML by default by @backnotprop in #924
      • fix(annotate): block symlink escape in HTML asset serving by @backnotprop in #927
      • fix(annotate): add /api/save-notes POST endpoint to annotate server by @titosemi in #884
      • feat(editor): clearer edit controls — Edits label, Save emphasis, Save/Cancel by @backnotprop in #928
      • fix(server): always show remote URL + close share-html symlink gap by @backnotprop in #929
      • feat(annotate): live file tree workspace status by @backnotprop in #931
      • fix(annotate): polish live file tree refresh by @backnotprop in #933
      • feat(annotate): persist saved file edits in drafts by @backnotprop in #936
      • fix(ai): resolve Bedrock/Vertex model ids for Ask AI by @masterpavan in #939
      • chore(release): disable bunfig autoload for release binaries by @jrpat in #937
      • fix(ui): correct light-mode contrast in code hover preview by @hl662 in #934
      • fix(annotate): live file tree startup latency by @backnotprop in #935
      • feat(annotate): open files in external apps + code-review UX pass by @backnotprop in #942
      • feat(annotate): WebTUI agent panel in annotate mode by @backnotprop in #941
      • fix(server): surface session URL in local mode when the browser can't open by @backnotprop in #945

      New Contributors

      Contributors

      Thanks to the five external contributors in this release.

      @titosemi fixed a bug that several people had run into: the "Save to Obsidian" button silently failing from annotate sessions, because the annotate server was missing the /api/save-notes endpoint. @masterpavan reported and fixed Ask AI's failure on Amazon Bedrock and Google Vertex, where bare model aliases were being rejected with a 400. @jrpat hardened the release binaries so they no longer autoload a bunfig.toml from the caller's directory, closing a path where a stray config could crash a compiled binary. @hl662 fixed a light-mode contrast bug that made code hover previews hard to read. @yonihorn, back for a fourth contribution, added a config-file switch to disable URL sharing.

      Thanks also to the community members whose reports shaped this release:

      Full Changelog : v0.20.3...v0.21.0

    4. 🔗 HexRaysSA/plugin-repository commits sync plugin-repository.json rss
      sync plugin-repository.json
      
      No plugin changes detected
      
    5. 🔗 HexRaysSA/plugin-repository commits fix duplicated plugin idalib-rust-bindings (#33) rss
      fix duplicated plugin idalib-rust-bindings (#33)
      
    6. 🔗 Hex-Rays Blog IDA 9.4: Apple Dyld Shared Cache workflow improvements rss

      IDA 9.4: Apple Dyld Shared Cache workflow improvements

      Over the years, the Apple ecosystem (iOS, macOS, …) has seen steady gains in security, application load-time, and more. One of the cornerstones enabling those is the "Dyld Shared Cache" (DSC): a highly specific collection of common system libraries, pre-optimized on many fronts and used across applications.

    7. 🔗 r/reverseengineering Martyx00/VulnFanatic-NG: BianryNinja plugin for identifying vulnerabilities in decompiled binaries with both programmatic scans and LLM support. rss
    8. 🔗 earendil-works/pi v0.79.8 release

      New Features

      • Selective provider base entry points - SDK users can pair @earendil-works/pi-ai/base and @earendil-works/pi-agent-core/base with explicit provider registration to keep bundled applications from including unused provider transports. See pi-ai Base Entry Point and pi-agent-core Base Entry Point.
      • Mistral prompt caching - Mistral sessions now use provider-side prompt caching with session affinity and cached-token usage/cost accounting. See API Keys and Environment Variables.
      • Post-compaction token estimates - Compact results and compaction events now include estimated post-compaction token counts so clients can show the approximate context reduction. See RPC compact and compaction events.
      • OpenRouter Fusion alias - openrouter/fusion is available as a built-in OpenRouter model alias. See API Keys.

      Added

      • Added inherited @earendil-works/pi-ai/base and @earendil-works/pi-agent-core/base entry points for selective provider registration in bundled applications (#5348 by @FredKSchott).
      • Added inherited Mistral prompt caching using the pi session ID as prompt_cache_key, including cached-token usage and cost accounting (#5854).
      • Added estimated post-compaction token counts to compact results and compaction events (#5877).
      • Added the inherited OpenRouter Fusion alias as openrouter/fusion (#5866 by @dannote).

      Fixed

      • Updated vulnerable runtime dependencies, including undici and the packaged protobufjs transitive dependency.
      • Fixed compaction to refuse sessions with no eligible messages instead of producing empty summaries (#4811).
      • Fixed successful overflow-triggered auto-compaction to avoid retrying completed assistant responses (#5720).
    9. 🔗 MetaBrainz Phishing attempts using MetaBrainz messages rss

      There have been reports of users receiving phishing messages via the MetaBrainz messaging service.

      Remember: MetaBrainz staff will never ask for your password. MetaBrainz staff will never ask you to log in to a third-party site (or 'verify' your username and password in any other way).

      Staff are also likely to email you about account issues directly from an @metabrainz account rather than the user-to-user messaging service.

      You can go a step further and apply these rules to every website and service… you are now 99% phishing-proof!

      Below is an example of a reported phishing message. This is not a message from staff. It is an attempt to compromise a MetaBrainz account by getting a user to enter their username and password into a third-party website.

      If you receive phishing attempts (or spot other scams) via MetaBrainz services please leave a comment here. There is also a thread on the community forums discussing the phishing messages.

    10. 🔗 r/reverseengineering BSim-foundry: pre-built function signatures for zlib, OpenSSL, mbedTLS, SQLite, libcurl and 24 more. headless + GUI + IDA/BN via SightHouse rss
    11. 🔗 Ampcode News Custom Agents rss

      You can now create custom agents in Amp with plugins.

      You can use these custom agents as your main Amp agent, or as subagents. You can use them as a small part of a tool pipeline that you invoke with amp -x. Or you can spawn 25 custom worker agents, then switch between them.

      Each custom agent comes with a custom orb color.

      Here is how you define a custom agent in an Amp plugin:

      // .amp/plugins/focused-reviewer-agent.ts
      import type { PluginAPI } from '@ampcode/plugin'
      
      export default function (amp: PluginAPI) {
          // Create the agent
          const reviewer = amp.createAgent({
              name: 'focused-reviewer',
              model: 'openai/gpt-5.5',
              instructions: [
                  'You are a focused code-review subagent.',
                  'Inspect only the files and concerns named by the caller.',
                  'Return concise findings with severity, evidence, and suggested fixes.',
              ].join(' '),
              tools: 'all',
              display: { label: 'reviewer', color: '#d97706' },
          })
      
          // Register a tool. This agent acts as a subagent
          amp.registerTool({
              name: 'focused_review',
              description: 'Run a focused code-review subagent.',
              inputSchema: {
                  type: 'object',
                  properties: {
                      request: { type: 'string' },
                  },
                  required: ['request'],
              },
      
              async execute(input, ctx) {
                  // Run a one-shot agent turn
                  const result = await reviewer.run(input, {
                      parentThreadID: ctx.thread.id,
                  })
                  return result.text
              },
          })
      
          // Or register the agent as a selectable main thread mode
          amp.registerAgentMode({
              key: 'focused-reviewer',
              description: 'Code Review Expert',
              agent: reviewer.definition,
          })
      }
      

      Threads

      Once you have defined an agent, you can create threads:

      // Spawn a new thread
      const thread = await reviewer.createThread({
          // Tell the UI switch to this thread
          show: true,
      })
      
      // Get an existing thread
      const thread = amp.threads.get(input.threadID)
      

      The Thread object lets you interact with a thread in many different ways, and is where the real power comes in.

      Send a message to a Thread

      Add a new user message to a thread by calling thread.appendUserMessage(). The call returns as soon as Amp has accepted the message; it does not wait for inference to complete before returning.

      await thread.appendUserMessage({
          type: 'user-message',
          content: 'Review the auth changes in this branch.',
      })
      

      Wait for the Agent's response

      When you do want to wait, call waitForResponse() on the thread. It resolves with the next assistant message after the agent finishes its turn.

      const reply = await thread.waitForResponse()
      

      Example: Spawn an async thread that responds to the main thread

      These are just a few primitives provided by the Plugin API. Together, they compose into unique workflows. An example used on the Amp team: spawn an agent in an asynchronous thread, and give it the tools it needs to respond to the parent when it needs to.

      amp.registerTool({
          name: 'start_async_review',
          description: 'Start a review in a background thread.',
          inputSchema: { type: 'object', properties: {} },
          async execute(_input, ctx) {
              const thread = await reviewer.createThread({
                  parentThreadID: ctx.thread.id,
              })
      
              await thread.appendUserMessage({
                  type: 'user-message',
                  content: [
                      'Review the auth changes in this branch.',
                      `When you are done, call send_to_thread with threadID ${ctx.thread.id}`,
                      'and include your review in the message.',
                  ].join(' '),
              })
      
              return `Started background review in ${thread.id}.`
          },
      })
      

      Full documentation is in the manual. Happy Hacking.