Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9fd16f6
feat: recall quality & project inspection (issue #56)
ashu17706 May 3, 2026
dbb2eeb
refactor: move memory.ts + ollama.ts out of QMD submodule
ashu17706 May 3, 2026
91effef
fix(ci): add picomatch@4 as explicit root dep after QMD upstream sync
ashu17706 May 3, 2026
fa2604a
feat: knowledge density scoring (#62) and smriti digest (#63)
ashu17706 May 3, 2026
cfe15ed
refactor: SDK migration Phase 1+2 — createStore() init + kill llm.js …
ashu17706 May 3, 2026
c6b20e1
feat: query expansion + reranking in recall pipeline (#58)
ashu17706 May 3, 2026
e5e4dfb
feat: smriti enrich --queries — retroactive query labeling (#60)
ashu17706 May 3, 2026
991ab13
feat: smriti ask — RAG question-answering command (#61)
ashu17706 May 3, 2026
e8f6ab4
feat: --wide flag for cross-project knowledge routing (#64)
ashu17706 May 3, 2026
d9928c2
feat: smriti drift — temporal topic evolution command (#65)
ashu17706 May 3, 2026
968aa25
feat: --check-conflicts flag for contradiction detection in recall (#67)
ashu17706 May 3, 2026
81af217
feat: QMD SDK Migration Phase 4 — index sessions as QMD documents (#59)
ashu17706 May 4, 2026
6eb91dc
feat: smriti clusters — semantic session clustering (#66)
ashu17706 May 4, 2026
cc00cff
fix: sync tag roundtrip + team config.json with custom categories (#1…
ashu17706 May 4, 2026
3ecfd16
chore(qmd): bump submodule to upstream main (ddbd6bd)
ashu17706 May 18, 2026
e74d6a0
feat(daemon): scaffold server with PID-file single-instance + IPC socket
ashu17706 May 19, 2026
fe75694
feat(daemon): recursive watcher with macOS-native + Linux walk-and-watch
ashu17706 May 19, 2026
2c13e34
feat(daemon): per-project debounce queue
ashu17706 May 19, 2026
9f5c0cc
feat(daemon): agent-root routing helpers
ashu17706 May 19, 2026
514ea2a
feat(daemon): lifecycle client for stop / status
ashu17706 May 19, 2026
260eefa
feat(daemon): runDaemon() entry point wiring all five modules
ashu17706 May 19, 2026
a5c56c3
feat(daemon): LaunchAgent + systemd-user installer (macOS + Linux)
ashu17706 May 19, 2026
6d8600c
feat(cli): wire smriti daemon subcommands
ashu17706 May 19, 2026
adab97f
chore(release): bump to v0.8.0; document daemon commands in CLAUDE.md
ashu17706 May 19, 2026
6ece68f
docs(release): add release-flow + v0.8.0 release notes
ashu17706 May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,34 @@ smriti embed # Build vector embeddings
smriti categorize # Auto-categorize sessions
smriti share --project myapp # Export to .smriti/ for git
smriti sync # Import team knowledge

# Daemon (v0.8+) — cross-agent capture in the background
smriti daemon install # Install LaunchAgent/systemd unit; auto-start at login
smriti daemon status # PID, uptime, watched agents
smriti daemon stop # Graceful shutdown
smriti daemon logs # Tail the daemon log
smriti daemon uninstall # Reverse install
smriti daemon # Run in foreground (debugging)
```

### The Claude Stop hook with the daemon

When the daemon is installed, the Claude Stop hook becomes a 5ms socket
poke instead of a full ingest invocation. Recommended template:

```bash
#!/bin/bash
SOCK="$HOME/.cache/smriti/daemon.sock"
if [ -S "$SOCK" ]; then
: | nc -U "$SOCK" 2>/dev/null
else
/usr/bin/lockf -t 0 /tmp/smriti-ingest.lock smriti ingest claude 2>/dev/null
fi
exit 0
```

The `lockf` fallback keeps the system working when the daemon isn't running.

## Project Structure

```
Expand Down
817 changes: 8 additions & 809 deletions bun.lock

Large diffs are not rendered by default.

162 changes: 162 additions & 0 deletions docs/internal/daemon-prd.md

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions docs/internal/release-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Release flow

How a Smriti version goes from "code on a feature branch" to "tagged release that downstream users pick up via `smriti upgrade`." Written after the v0.8.0 work; intended to be reused for every future release.

## Versioning

Standard semver. The general rule: features bump minor (0.7.0 → 0.8.0), bug fixes and polish bump patch (0.8.0 → 0.8.1), and we will reach 1.0.0 once the daemon has been running on real teams for a month without anyone hitting a "this is broken in a load-bearing way" issue.

A note about local drift: `package.json` has occasionally lagged the git tag (v0.7.0 was tagged without a corresponding `"version"` bump). Try to keep them in sync; if they drift, fix it during the next release rather than rewriting history.

## The four phases

### Phase 1 — Feature branch development

All work for a release lives on one feature branch, named `feat/<headline-shape>` (e.g. `feat/daemon-core` for v0.8.0). One feature branch per release, even if the release contains several modules.

The branch accumulates commits as we go. We don't try to keep the branch always-rebased-to-main during development — that creates more friction than it solves for a solo developer. Instead we squash-or-merge when we're ready to ship.

Each commit on the branch should be independently testable: `bun test` passes after each commit. This makes bisecting later much cheaper.

### Phase 2 — Staging on real hardware

When the branch is feature-complete and unit-tested, we install it on real hardware and exercise it. There's no separate "build artifact" — the source IS the build, and switching to staging is `git checkout <feature-branch> && bun install`.

For the developer (running from `/Users/zero8/zero8.dev/smriti/`):
```bash
cd /Users/zero8/zero8.dev/smriti
git fetch origin
git checkout feat/<branch-name>
bun install --frozen-lockfile
bun test # sanity
```

For downstream users (running from `~/.smriti` as a git clone):
```bash
cd ~/.smriti
git fetch origin
git checkout feat/<branch-name>
bun install --frozen-lockfile
```

If the release adds a long-running process or service-file install, the staging step includes those too — e.g. for v0.8.0:
```bash
bun src/index.ts daemon install
bun src/index.ts daemon status # verify it's running
```

To leave staging, `git checkout main && bun install --frozen-lockfile` (and remove any service-file installs).

### Phase 3 — Release-readiness checklist

Every release has a tracking issue with a checklist of real-hardware verifications. The checklist is release-specific (a daemon release tests reboot + soak; a search-quality release tests recall against a fixture set; etc.) but the shape is consistent:

- Per-OS verification rows that have to be done on actual machines
- Soak / endurance rows where time itself is the test
- Idempotency rows (running install twice, etc.) that catch state-corruption bugs
- A "previous CLI still works" row to catch regressions

When all rows are ✅, we tag. When a row fails, we fix it on the feature branch with a small commit and re-run the row — same branch, just more commits.

The tracking issue for v0.8.0 is #75. Future releases should clone its structure.

### Phase 4 — Promotion to release

```bash
# 1. Final sanity check
cd /Users/zero8/zero8.dev/smriti
git checkout feat/<branch-name>
bun test # all green
bun src/index.ts <whatever-needs-spot-check> # smoke-test the headline feature

# 2. Merge the PR
gh pr merge <PR-#> --squash --delete-branch # squash if many commits and you don't need the history
# --merge if you want the commit-by-commit story preserved

# 3. Tag
git checkout main && git pull
git tag -a v<x.y.z> -m "v<x.y.z> — <one-line headline>"
git push origin v<x.y.z>

# 4. GitHub release with notes
gh release create v<x.y.z> \
--title "v<x.y.z> — <one-line headline>" \
--notes-file docs/internal/release-notes-v<x.y.z>.md \
--latest
```

The release notes file lives in the repo (`docs/internal/release-notes-v<x.y.z>.md`) as a draft from Phase 1, gets polished during Phase 3, and is the canonical source for the GitHub release body in Phase 4. After tagging, the file can stay in the repo as historical record — it's small and useful when someone asks "what landed in 0.8?"

### Optional Phase 5 — Daemon / long-running-process restart

For releases that ship changes to a long-running process (the daemon, future MCP server, etc.), users who upgrade need to restart that process to pick up the new code. Today this is manual:

```bash
smriti upgrade # git pull + bun install
smriti daemon stop
launchctl kickstart -k gui/$UID/dev.zero8.smriti # macOS; KeepAlive=true will respawn it
# or: systemctl --user restart smriti # Linux
```

A v0.8.1 polish release should teach `smriti upgrade` to detect a running daemon and restart it automatically. Tracked separately — not load-bearing for v0.8.0 itself.

## What lives where

| Artifact | Location | When updated |
|---|---|---|
| Release-tracking issue | GitHub issue (one per release) | Created at start of Phase 3; closed when tagged |
| Release notes | `docs/internal/release-notes-v<x.y.z>.md` | Drafted Phase 1, polished Phase 3, used in Phase 4 |
| Version | `package.json` `"version"` | Bumped in the same commit as the release notes finalisation |
| CHANGELOG | We don't maintain one. The set of GitHub Releases is the changelog. | — |
| Reference doc per major change | `docs/internal/*-prd.md` | Drafted alongside the feature; stays in the repo as historical record |
| Postmortems / reflections | `docs/papers/` | When something is worth telling as a story |

## What we deliberately don't do

- **No CI release pipeline.** Releases are small enough and rare enough that automating them past `gh release create` adds more failure modes than it removes. If we ever release multiple times a week, revisit.
- **No release candidates or beta channels.** Staging on the feature branch IS the RC. If a release needs longer soak time before tagging, just leave it in Phase 3 longer.
- **No release branches.** `main` is always the latest stable; feature branches are everything else. Branching off a tag for a hotfix is fine, but we don't keep a `release/0.8.x` branch alive after tagging.
- **No version-skipping for ceremony.** If v0.7.0 was tagged without a `package.json` bump, the next release just skips ahead in `package.json` — we don't go back and re-tag 0.7.1 to fix the drift.

## When something goes wrong post-release

If a release ships with a regression bad enough to revert:

1. `git revert <merge-commit>` on main (creates a clean revert commit)
2. Tag v<x.y.z+1> from the revert
3. Push tag, create release marked as a regression revert
4. Users on `smriti upgrade` pick up the revert via the normal flow

For less severe issues, a regular patch release (v<x.y.z+1> with the fix) is preferred over a revert.

## Source of truth for the current release

Always the GitHub release for the highest tag. If `package.json` disagrees with the tag, the tag wins. If a doc disagrees with the code, the code wins. We are explicit about this so future-us doesn't get confused by stale documentation that says we shipped something we didn't.
86 changes: 86 additions & 0 deletions docs/internal/release-notes-v0.8.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Smriti v0.8.0 — Cross-agent capture, finally

The headline: a long-running `smriti daemon` that captures your sessions across every coding agent in the background. Open Cursor, Codex, or Claude — the daemon watches the filesystem, debounces, and ingests automatically. You stop having to remember which agent you used yesterday, or whether you remembered to `smriti ingest`.

This is the release the postmortem [`docs/papers/stop-hook-never-stopped.md`](../papers/stop-hook-never-stopped.md) gestured at — the daemon shape the lockf mitigation pointed toward. It also reuses everything Smriti was already doing for Claude (the Stop hook continues to work, just as a 5ms socket poke now instead of a full ingest).

## What you get

- **Cross-agent capture.** Sessions from Claude, Codex, Cline, Copilot, and Cursor are picked up automatically as they're written. No `smriti ingest <agent>` to remember.
- **Auto-start at login.** `smriti daemon install` writes a LaunchAgent on macOS or a systemd-user unit on Linux. Daemon comes back after every reboot; restarts itself on crash.
- **Per-project debouncing.** A busy session in project A doesn't delay project B. Each project gets its own 30s settle window.
- **Six new commands** (`smriti daemon install / uninstall / status / stop / logs`, plus the bare `smriti daemon` for foreground debugging).
- **`smriti share` continues to work** with its existing sanitization — unchanged.

## Recommended Claude Stop-hook update

When the daemon is running, the Stop hook becomes a 5ms poke. Update your `~/.claude/hooks/save-memory.sh` to:

```bash
#!/bin/bash
SOCK="$HOME/.cache/smriti/daemon.sock"
if [ -S "$SOCK" ]; then
: | nc -U "$SOCK" 2>/dev/null
else
/usr/bin/lockf -t 0 /tmp/smriti-ingest.lock smriti ingest claude 2>/dev/null
fi
exit 0
```

The `lockf` fallback keeps capture working if the daemon isn't running. No flag day; the old hook continues to work too.

## Quick start

```bash
smriti upgrade # pull the new code
smriti daemon install # register the LaunchAgent / systemd unit
smriti daemon status # PID, uptime, watched agents
```

That's it. Open your agent of choice, work for a while, then `smriti search` for whatever you did. The session will be there.

## What's in the box

- 6 daemon modules (~1500 LOC of code, ~1200 LOC of tests)
- 57 daemon tests passing (full suite: 1174+ tests)
- 9 commits on `feat/daemon-core`, all individually testable
- 1 dedicated PRD (`docs/internal/daemon-prd.md`) documenting both the design and the three pre-impl smoke-test findings that shaped it

## Design discipline (the boring details that matter)

Three constraints came out of pre-impl smoke tests against Bun 1.3.6, and each one shaped a design decision that would have silently bitten us in production:

- **Single-instance enforcement uses a PID file + `kill(pid, 0)` liveness probe, not Unix-socket bind contention.** Bun's `net.createServer().listen(path)` silently succeeds on duplicate binds and steals connections from the original server. We use the PID-file pattern QMD already uses for `qmd mcp --daemon`.
- **FS watching uses native `fs.watch({ recursive: true })`, not chokidar.** Chokidar 5.0.0 under Bun fires zero events; native `fs.watch` works correctly on macOS (recursive native) and Linux (walk-and-watch).
- **DB connections are per-flush, not per-daemon-lifetime.** Repeatedly calling `ingest()` against a single long-lived SQLite handle inside one Bun process climbed to 6.8 GB peak RSS and segfaulted Bun. Opening a fresh connection per flush sidesteps this entirely; the ~30ms cost is invisible inside the 30s debounce window.

All three findings are documented in [`docs/internal/daemon-prd.md`](daemon-prd.md).

## Platforms

- ✅ macOS 14+ (Apple Silicon and Intel)
- ✅ Linux (systemd-user supported)
- ⏸️ Windows — deferred to a later release. Bun's Windows daemon support is rough and named-pipe semantics differ enough from Unix sockets that we want to ship them separately rather than half-build them now.

## Upgrading

If you're coming from v0.6.0 / v0.7.0:

1. `cd ~/.smriti && smriti upgrade` (or your equivalent — wherever your smriti install lives)
2. `smriti daemon install` if you want the daemon. Optional — if you skip this, Smriti continues to work exactly as it did before via the existing Claude Stop hook.

If you do install the daemon and later decide to roll back, `smriti daemon uninstall` removes the service file and stops the daemon. The PID file and IPC socket are cleaned up automatically. There is no other state to migrate.

## What's not in this release

A few things explicitly deferred to keep this release tight:

- **No real redaction pipeline.** `smriti share` still does the basic sanitization it always has. Real redaction comes in v0.8.1 / v0.9.0.
- **No read-side routing through the daemon.** `smriti search` and `smriti recall` are still one-shot CLI invocations. The daemon doesn't speed them up.
- **No auto-restart on `smriti upgrade`.** After upgrading, you'll want to `smriti daemon stop` followed by `launchctl kickstart -k gui/$UID/dev.zero8.smriti` (or `systemctl --user restart smriti` on Linux) so the daemon picks up the new code. v0.8.1 will teach `smriti upgrade` to do this automatically.

## Thanks

This release came out of a debugging session that found 42 stuck `smriti ingest` processes consuming 9 CPU-days. The lockf mitigation that stopped the pile-up is still in place as the fallback path; the daemon makes it usually-unnecessary. Both stories live in `docs/papers/`.

Refs: #71, #72, #73, #74, #75. PR #76.
Loading
Loading