Two things, mostly. deja now installs the way each harness installs everything
else — as a package in its own format, from its own catalogue — and a long bug
hunt went through the parts that report: the usage log, sync between machines,
and what doctor says when a file will not open.
Added
- Gemini CLI installs this repository as an extension:
gemini extensions install https://github.com/vshulcz/deja-vubrings the MCP server, the context file and thedeja-searchskill. The manifest sits at the root, which is also what the extensions gallery crawls. (#1725) - The Agent Plugins manifests are at the root too, so any host that reads the portable v1 format — Qwen Code among them — installs deja without a format of its own. (#1769)
- A Kimi Code plugin, in
extensions/kimi. One/plugins installgives Kimi the MCP tools, the shared skill, a/deja:recallcommand and recall on every prompt through itsUserPromptSubmithook. It stands down wheredeja install kimialready wired the same thing, so having both is not two of anything. (#1573) - A Grok Build plugin, with a licence in every bundle. (#1591)
- A dsh plugin, and auto-recall that reaches the model rather than the transcript. (#1552)
- The Codex plugin carries its own hooks: a marketplace install now brings session-start recall, per-prompt recall and pre-compaction capture with it, instead of only tools and the skill. It stands down where
deja install codex-autoalready wrote the same hook into Codex's own hooks.json. (#1583, #1584) - The opencode, dsh and Zed packages live in this repository, so a change to deja and the change to its packages land together. (#1562)
deja embedreaches an authenticated endpoint:DEJA_EMBED_KEYfor any OpenAI-compatible URL, andOPENAI_API_KEYonly for an HTTPSapi.openai.comone — never forwarded implicitly to localhost or a third party. Contributed by @chrisgeo. (#1919)- PowerShell completion. (#1803)
- The fix pair arrives when a command fails, rather than waiting to be asked. (#1550)
- Recall says when a hit is the other machine's copy of a session this one already holds, and when a session's date is later than this machine's clock. (#1754, #1776)
- An index run says how many lines it could not read, and
doctor --jsoncarries the sync state that used to exist only on the screen. (#1839, #1994)
Changed
- The release archive carries
gemini-extension.jsonandGEMINI.md.gemini extensions installreads the manifest out of the release asset rather than from git, so the documented command failed on every published version until now. (#1935) deja installrefuses rather than guesses in the places it used to guess: a guidance block it cannot bound, acordis.patch.ymlmissing one of deja's markers, a gooseextensions:list where a mapping was expected, an opencode.jsonc that uses trailing commas, a relativeXDG_CONFIG_HOME, and a machine with no home directory. It also writes a config back with the line endings it already had. (#1666, #1669, #1691, #1694, #1696, #1698, #1700, #1702, #1706)- The usage log holds one definition of an event, one week for every counter that shows one, and a record size it can actually hold. Rotation keeps the newest events instead of emptying the file. (#1918, #1921, #1923, #1974, #1981, #1986)
doctorexplains rather than accuses: a corrupt sidecar is not a missing one, an unreadable peers file is not "no machines", a transcript it may not open is named instead of the harness format being blamed, and a peer stamp ahead of this machine's clock is said out loud. (#1711, #1713, #1749, #1841, #1856, #1860, #1961)
Fixed
- Two syncs at once no longer lose machines, and one machine named in two letter cases is one machine — in the watermark, in
doctor's count, and in how a host name is matched, which is the way ssh matches it. (#1854, #1868, #1877, #1879, #1884) - ssh gets a connect timeout, so a sleeping machine costs seconds rather than minutes. (#1773)
- A credential in an uppercase
_KEYenvironment assignment is stripped at index time, and so is a secret whose JSON arrived with its quotes escaped. (#1766, #1958) - The PATH offer in
install.shsat behind a conditioncurl | shcan never meet, so nobody installing the documented way was ever asked. (#1852) - A truncated line — in the log, in the snapshots file, in the hook's dedup file — is closed before the next event is appended, rather than merged into it. (#1910, #1947, #1966)
- Unicode folding covers Arabic hamza and madda, the three Greek marks written on a spacing diaeresis, and letters carrying two marks. (#1836, #1873, #1895, #1914)
- The statusline gives the filename up before the memory half when the bar is too narrow, and uses the count form when even the title will not fit. (#1881, #1904)
- The index resolves a shared session id the same way every time, counts the empty transcripts of the last build rather than of the process, follows a store directory that is a symlink, and says a store went away instead of greeting the reader as new. (#1745, #1764, #1851, #1998)
Changelog
Features
- feat(codex): the listing metadata the plugin directory asks for (#1584)
- feat(codex): the plugin brings its own recall, not just tools (#1583)
- feat(completion): support PowerShell (#1803)
- feat(doctor): report the sync state in --json, not only on the screen (#1839)
- feat(extensions): keep the opencode, dsh and Zed packages in this repository (#1562)
- feat(grok): a Grok Build plugin, and a licence in every bundle (#1591)
- feat(index): say how many lines an index run could not read (#1994)
- feat(kimi): a Kimi Code plugin that recalls what the other agents wrote (#1573)
- feat(recall): say when a hit is the other machine's copy of a session this one holds (#1776)
- feat(recall): say when a session's date is later than this machine's clock (#1754)
- feat: carry the Agent Plugins manifests at the root (#1769)
- feat: dsh plugin, and auto-recall that actually reaches the model (#1552)
- feat: install deja into Gemini CLI as an extension (#1725)
- feat: support authenticated embedding endpoints (#1919)
- feat: the fix pair arrives when the command fails (#1550)
Fixes
- fix(aider): drop bytes that are not utf-8 at ingest, like every other parser (#1741)
- fix(blame): a path with a line number answers for the file (#1626)
- fix(brief): a reader who turned colour off still gets the brief (#1597)
- fix(brief): fit the wire line into a 60-column pane (#1554)
- fix(brief): keep the first screen inside a 60-column pane (#1589)
- fix(brief): say a store was locked instead of no agent ran here (#1980)
- fix(brief): shorten a wide project before the title floor absorbs it (#1593)
- fix(brief): show a project name the same way at every terminal width (#1845)
- fix(compact): strip control bytes from the evidence block (#2001)
- fix(completion): offer every role deja accepts, not the three it once had (#1659)
- fix(completion): the bash function survives a shell with set -u (#1657)
- fix(ctx): an id-prefix answer says when the decision was taken back (#1644)
- fix(ctx): open the session a short id-prefix names instead of denying it exists (#1615)
- fix(docs): dsh refuses plugin add without --profile (#1846)
- fix(doctor): count a machine's sessions whichever case it spells its name in (#1877)
- fix(doctor): emit the stale_stores the JSON schema documents, and name the real policy file (#1711)
- fix(doctor): have --deep say what it compared instead of promising no memory was lost (#1713)
- fix(doctor): learn what a peer calls itself, so its sessions are counted (#1888)
- fix(doctor): line the Sync column up by what the terminal draws (#1822)
- fix(doctor): name the transcript it may not open instead of blaming the harness format (#1749)
- fix(doctor): name the zed, deepseek and omp stores it reads from (#1739)
- fix(doctor): say when a peer's stamp is ahead of this machine's clock (#1856)
- fix(doctor): say when the peers file cannot be read instead of reporting no machines (#1841)
- fix(doctor): tell a corrupt sidecar apart from no sidecar (#1961)
- fix(extensions): carry the licence into the Zed extension directory (#1565)
- fix(extensions): one rule for what a package adds next to deja install (#1570)
- fix(extensions): stop recalling twice when both installs are present (#1567)
- fix(files): refuse a flag with no value, an unknown flag and an empty topic (#1629)
- fix(fix,how): keep a transcript from forging a row of the listing (#1864)
- fix(forget): say a deleted transcript is gone from this machine, not that it came from another (#1756)
- fix(friction): one error stays one wall when pytest or a log stamps the line (#1638)
- fix(friction): the trust note counts the sessions it hid, not their error lines (#1640)
- fix(greeting): count in singular when there is one of something (#1599)
- fix(handoff): an empty --to is refused, not read as no target at all (#1648)
- fix(help): a hook command is documented or deliberately hidden, not skipped wholesale (#1655)
- fix(help): wrap the install target list to the terminal, not to a fixed 76 columns (#1662)
- fix(hooks): close a truncated dedup line before appending (#1968)
- fix(hooks): recall the fix for an error even when the tool log runs past 1 MiB (#1718)
- fix(how): match the command as a word, and refuse a flag that does nothing (#1631)
- fix(how,files): say when the list was cut at the limit (#1633)
- fix(id): an id pasted in the other case opens the session it names (#1621)
- fix(index): clear the evicted-files counter per build, and catch the next one (#1862)
- fix(index): count the empty transcripts of the last build, not of the process (#1851)
- fix(index): follow a store directory that is a symlink instead of calling it empty (#1745)
- fix(index): keep the commands that repair a build, not only the ones that fail (#1636)
- fix(index): pluralise file and message counts on the incremental line (#1585)
- fix(index): re-read the stores that needed a CLI once that CLI is installed (#1761)
- fix(index): resolve a shared session id the same way every time (#1998)
- fix(index): say a store went away instead of greeting the reader as new (#1764)
- fix(index): say when only half a store could be read, in the run and in doctor --json (#1759)
- fix(index): tell a run that waited for another build where the index stands (#1752)
- fix(install): call a mistyped flag a flag, whether or not a target came with it (#1681)
- fix(install): end deja's kimi hook block at the next comment, not the next table (#1700)
- fix(install): ignore a relative XDG_CONFIG_HOME, as the spec requires (#1694)
- fix(install): name the config a permission error stopped, not deja's scratch file (#1687)
- fix(install): refuse a cordis.patch.yml where one of deja's markers is missing (#1702)
- fix(install): refuse a goose extensions: list rather than writing a mapping into it (#1698)
- fix(install): refuse a guidance file whose block deja cannot bound, and repair a duplicated one (#1706)
- fix(install): refuse when deja cannot find a home directory, instead of wiring the working directory (#1691)
- fix(install): say when it replaced a skill file it has no record of writing (#1704)
- fix(install): stop blaming file permissions for a config deja could not parse (#1666)
- fix(install): stop writing a stray comma into an opencode.jsonc that uses trailing commas (#1696)
- fix(install): the PATH offer was behind a condition curl | sh can never meet (#1852)
- fix(install): write a config back with the line endings it already had (#1669)
- fix(json): a stemmed answer reports the stemmed tier, not close (#1617)
- fix(json): drop a timestamp that was never set instead of writing year 1 (#1875)
- fix(last): a bare argument that is not a count is refused, not ignored (#1619)
- fix(lifecycle): a promote note is clipped like every other line on the screen (#1646)
- fix(log): answer --last --json in JSON when there is no digest (#1976)
- fix(log): stop calling an injection that sent bytes an empty result (#1955)
- fix(mcp): answer from the index while a refresh runs instead of sending the agent away (#1734)
- fix(mcp): answer ping instead of method-not-found, so a host keepalive does not read the server as dead (#1722)
- fix(mcp): decide remember's index refresh with one lock attempt instead of a check and a wait (#1805)
- fix(mcp): hold recall_context to the ~8KB it promises, and say when a digest is cut (#1798)
- fix(mcp): keep the offset line on a full recall page, so the rest of the matches stay reachable (#1727)
- fix(mcp): let blame answer from the snapshot while a rebuild runs (#1785)
- fix(mcp): mark the excerpt the recall page budget cut, like every other cut line (#1800)
- fix(mcp): name the phase while a build starts instead of an empty parenthesis (#1732)
- fix(mcp): recall_context opens the session an id names (#1623)
- fix(mcp): refuse a batch as an invalid request instead of claiming it did not parse (#1796)
- fix(mcp): refuse a resource uri with no session id instead of serving whichever came first (#1730)
- fix(mcp): take the environment block out of recall's budget instead of adding it on top (#1807)
- fix(mcp): tell an agent which argument was wrong instead of handing back the JSON decoder's text (#1724)
- fix(nfcfold): fold Arabic hamza and madda (#1914)
- fix(nfcfold): fold letters that carry two marks (#1873)
- fix(nfcfold): fold the three Greek marks written on a spacing diaeresis (#1895)
- fix(notes): count a note deja cannot read (#2006)
- fix(npm): the dsh package page rendered in Chinese (#1837)
- fix(peers): stop two syncs at once from losing machines (#1884)
- fix(peers): treat a host name the way ssh does, case and all (#1868)
- fix(rank): score by sessions, gate by the rarer verdict (#1548)
- fix(redact): redact a secret whose json arrived with its quotes escaped (#1766)
- fix(redact): strip credentials from uppercase _KEY env assignments (#1958)
- fix(release): ship the Gemini manifest in the archive (#1935)
- fix(remember): bound and de-escape the project name the confirmation echoes back (#1793)
- fix(remember): clean and bound a tag where it is written, and where it is echoed (#1811)
- fix(remember): keep checking for duplicates past an oversized note (#1813)
- fix(remember): keep one copy of a note instead of charging every later recall for the same fact (#1737)
- fix(search): --since refuses a window that is not positive (#1611)
- fix(search): a filter flag given an empty value is a mistake, not a wildcard (#1613)
- fix(search): a one-word query scores on the word, not on the punctuation around it (#1604)
- fix(search): a regex match with no width is not a match (#1607)
- fix(search): run the co-occurrence rescue when the words never appear together (#1787)
- fix(search): stop telling a reader who typed --all to add --all (#1609)
- fix(show): tell the two halves of the repeated hour apart (#1789)
- fix(stats): an empty top_projects is [] like its sibling, not null (#1650)
- fix(stats): say which window the impact numbers cover (#1890)
- fix(stats): the impact screen reads at one recall (#1653)
- fix(statusline): let the filename give way so the memory half fits the bar (#1881)
- fix(statusline): the quiet-day line counts one recall as one (#1601)
- fix(statusline): use the count form on a bar too narrow for the title (#1904)
- fix(sync): bound and de-escape peer names on the screens that report them (#1809)
- fix(sync): bound the batch file name an import error reports (#1848)
- fix(sync): bound the peer's output where the failure is turned into an error (#1820)
- fix(sync): bound the ssh capture's error like the rest of the sync path (#1834)
- fix(sync): give ssh a connect timeout so a sleeping machine costs seconds, not minutes (#1773)
- fix(sync): one machine, one watermark, whichever case the name was typed in (#1879)
- fix(sync): sanitise the directory in every sentence that names it (#1858)
- fix(sync): stop calling a push with nothing to send an exchange (#1781)
- fix(sync): treat one machine named twice as one machine (#1854)
- fix(termwidth): count the wide emoji blocks the table stopped short of (#1595)
- fix(termwidth): measure a name the same however it was typed (#1825)
- fix(uninstall): take the shared skill file with the last harness that read it (#1685)
- fix(usage): ask the snapshots file the same question as the log (#1947)
- fix(usage): close a truncated line before appending the next event (#1902)
- fix(usage): close a truncated snapshot line before appending (#1966)
- fix(usage): count a blame and a resource read on both screens (#1908)
- fix(usage): count an injection that carried no project session (#1963)
- fix(usage): keep the injection log under its rotation threshold (#1970)
- fix(usage): one rule for what counts as an event in the log (#1918)
- fix(usage): one week for every counter that shows one (#1921)
- fix(usage): record where a session-start digest went (#1950)
- fix(usage): rotation keeps the newest events rather than emptying the log (#1923)
- fix(view): redact the page it writes instead of trusting what the index kept (#1770)
- fix(zed): one server id for the extension and the installer (#1571)
- fix(zed): read a cut-short sqlite3 answer as the rows it delivered (#1568)
- fix: align daily recall counts with weekly usage (#1569)
- fix: an empty id prefix resolves to nothing, in both lookups (#1774)
- fix: resume Grok Build sessions (#1707)
- fix: version and security policy in the plugin bundles (#1719)
Performance
- perf(ctx): render a digest's turns across cores, 4.5s to 1.2s on a 30001-message session (#1791)
- perf(ctx): stop handing every transcript line to a regexp engine — 22.6s to 4.6s on a 30000-turn session (#1743)
- perf(main): stop parsing every store to count its files (#1990)
- perf(search): build the query side of the record check once (#1886)
- perf(usage): stop re-reading a log that has nothing to drop (#1973)
Documentation
- docs(architecture): say which machines deja treats as one (#1916)
- docs(json): describe deja log --json and pin its keys (#1933)
- docs(json): describe stats --impact --json and pin its keys (#1900)
- docs(json): name the kind Claude sets, and pin the optional session keys (#1937)
- docs(json): say that a peer stamped ahead gets a minute of slack (#1866)
- docs(json): say what a week and a day mean in the stats numbers (#1940)
- docs(json): the peer host is reported as written, not bounded (#1870)
- docs(sitemap): every entry carries a lastmod, from the registry's own dates (#1899)
- docs(termwidth): say that a cut returns composed text, and pin the brief's splice (#1843)
- docs(usage): write down the rotation window the append accepts (#1909)
- docs(zh): point the dsh plugin link at extensions/dsh (#1566)
- docs: Chinese README, and publish the dsh plugin to npm on release (#1553)
- docs: a page for the question people actually type (#1667)
- docs: adding memory to opencode (#1688)
- docs: changelog for 0.19.0 (#2013)
- docs: finding an old session, and several agents on one repository (#1679)
- docs: finish the move off the tap, including the demo GIF (#1767)
- docs: guide pages for Claude Code, Codex, Cursor and Copilot (#1938)
- docs: install from homebrew-core, not the tap (#1763)
- docs: link the guide from the README, which is the page Google crawls most (#1896)
- docs: measure the numbers instead of carrying them forward (#1549)
- docs: memory pages for Gemini CLI and Qwen Code (#1757)
- docs: memory pages for dsh, Kimi Code, Zed and Grok Build (#1689)
- docs: name the one object-shaped answer that carries no schema_version (#1977)
- docs: name where the harness packages publish from now (#1564)
- docs: pages for exporting a session and for syncing across machines (#1673)
- docs: pages for lost context, a full window, and resuming a session (#1771)
- docs: pages for the repeated wall and for switching agents (#1672)
- docs: publish llms.txt, the map an agent reads instead of crawling (#1871)
- docs: put automatic recall and redaction on the first screen (#1560)
- docs: refresh the outside-contribution numbers (#1580)
- docs: say the words people search for, and guard the structured data (#1823)
- docs: scoop install deja-vu, now that the manifest is in the main bucket (#1906)
- docs: tell IndexNow when pages change (#1682)
- docs: the home page had no robots meta, unlike every guide page (#1893)
- docs: two more pages for questions people type, and a guard for the set (#1670)
- docs: what memory costs in tokens (#1674)
- docs: when the rules file grows, and auditing what an agent did (#1678)
Other
- Merge pull request #1912 from vshulcz/merge/hunt-2026-08-23
- Merge pull request #2011 from vshulcz/merge/hunt-2026-08-26
- Merge remote-tracking branch 'origin/hunt/2026-08-23' into merge/hunt-2026-08-23
- Merge remote-tracking branch 'origin/hunt/2026-08-23' into merge/hunt-2026-08-26
- chore(dsh): 0.20.4, to republish with the English package page (#1849)
- chore(scoop): canonical key order and checksums-based autoupdate (#1814)
- chore(sync): drop the export helper the push path stopped using in #215 (#1892)
- chore: Search Console verification file (#1882)
- chore: name the homepage, repository and licence in the plugin manifest (#1555)
- chore: open a discussion with every release (#1559)
- chore: pin scoop, winget and the codex plugin to 0.18.0 (#1547)
- chore: the dsh plugin lives in its own repository now (#1558)
- ci: give every package a coverage floor (#1956)
- ci: give peers and stats a coverage floor, and say which floors are tight (#1944)
- ci: publish the harness packages from the release, at the release version (#1574)
- ci: run the HOL plugin scanner on the Codex bundle (#1715)
- ci: tell the plugin scanner which files are redaction fixtures (#1717)
- compare: the tools that read the same session files (#2008)
- grant discussions:write to the release job (#2016)
- mcp: answer ping and resources/templates/list (#1746)
- mcp: refuse a session uri that carries no id (#1750)
- release: open the page with what changed, not with the install command (#1581)
- stats: make the impact screen agree with a count of one (#1675)
- test(atomicfile): cover WriteStream, which had none (#1957)
- test(docs): end a doc section at the heading that ends it (#1952)
- test(doctor): pin what a peer row with neither timestamp means (#1860)
- test(hook): measure the task-git budget against a repo that has changes (#1996)
- test(hooks): hold the session start to one project across worktrees (#1959)
- test(hooks): pin the receiver both injection paths record (#1953)
- test(index): measure the early exit against a log worth scanning (#2004)
- test(index): pin that the index keeps what the transcript brought (#1992)
- test(index): stand where the swap-window wait is instead of racing a goroutine against it (#1783)
- test(json): catch a struct that embeds a type writing its own JSON (#1897)
- test(json): pin the keys deja show and deja last emit to the document (#1911)
- test(kimi): compare the two manifests whole instead of five keys of them (#1778)
- test(mcp): pin the budgets the conclusions gate was measured at (#1802)
- test(nfcfold): tie the composition table to the block the scan looks for (#1836)
- test(notes): pin what re-promoting an old record does, and stop a flaky cap test (#1816)
- test(peers): hold the shape that keeps the lock's fallback out of reach (#1905)
- test(search): record what the close tier's length bucket still reaches (#1945)
- test(search): record which tier joins a word to its marked form (#1942)
- test(stats): hold the card and the page to the week the report carries (#1948)
- test(usage): measure the shape that costs three bytes a character (#1987)
- test(usage): measure what a record weighs instead of guessing (#1981)
- test(usage): pin that a blank line in the log is read past (#1910)
- test(usage): pin the record size this log can hold (#1974)
- test(usage): pin why the environment block stays out of the impact ratio (#1964)
- test(usage): state the room one event has, and hold the writers to it (#1986)
- test(view): pin that a note's tag cannot close the page's script block (#1818)
- test(windows): fix the two tests that fail on every Windows checkout (#1677)
- warmup: name the phase before it is written (#1748)
Install
curl -fsSL https://raw.githubusercontent.com/vshulcz/deja-vu/main/install.sh | sh
go install github.com/vshulcz/deja-vu/cmd/deja@v0.19.0
brew install deja-vu