Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
47 changes: 47 additions & 0 deletions plugin/scripts/README-uds-daemon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# UDS Daemon Pipeline (Sprint 1 + 2)

Optional bundled performance-and-reliability layer for claude-mem hook events.
Replaces the per-hook Bun cold-start with a long-lived UNIX-socket singleton —
hook latency drops from ~467 ms p50 to ~60 ms p50 (≈7.8×).

## Files

| File | Purpose |
|------|---------|
| `daemon-server.mjs` | Persistent Bun process listening on UNIX socket, receives NDJSON hook events, writes to SQLite. |
| `hook-client.mjs` | Thin per-hook client: fast-skip filter for non-interesting tools + auto-spawn of daemon. |
| `plugin-hook-perf-patch.v2.mjs` | Idempotent patcher for `hooks/hooks.json` and `hooks/codex-hooks.json` (rewrites to use the new client, with `.uds-bak` rollback). |
| `setup-tree-sitter.mjs` | Installs tree-sitter parsers so `smart_search` / `smart_outline` / `smart_unfold` work. |
| `settings-doctor.mjs` | Audits `~/.claude-mem/settings.json` for security / noise / dead-config issues. |
| `install.sh` | One-shot installer applying the patcher + setup-tree-sitter. Includes `--rollback`. |
| `lib/constants.mjs` | Shared `INTERESTING_TOOLS`, regex, prefix constants. |
| `lib/paths.mjs` | Shared `DATA_DIR`, `DEFAULT_SOCK`, `DEFAULT_LOCK`, `DB_PATH`, `SETTINGS_PATH`. |
| `lib/importance.mjs` | Heuristic observation-importance scorer (0..1) + ADR auto-pin matcher. |
| `cli/memory-bank-export.mjs` | Exports observations to Cline 4-file Markdown convention. |
| `mcp-sidecar/` | Tiny MCP server exposing 4 Resources + 3 Prompts (requires `bun install` inside the directory). |

## Activation

```bash
bash plugin/scripts/install.sh # applies patcher to live hooks.json + sets up tree-sitter
bash plugin/scripts/install.sh --rollback # restores .uds-bak files
```

## Reliability invariants

- **Drain-await:** `hook-client` uses callback-based `socket.write` so the kernel flush completes before exit (no fire-and-forget frame loss).
- **RPC ack:** daemon replies `{ok, queued}` per insert; client awaits the reply (200 ms timeout) before closing — protects against socket-FIN-before-data races.
- **UTF-8:** daemon uses `node:string_decoder` instead of `chunk.toString()` so multi-byte codepoints split across reads don't corrupt JSON.
- **FK-safe inserts:** daemon resolves or inserts the `sdk_sessions` row before writing `pending_messages` (`session_db_id=0` sentinel would 100% silent-fail under `PRAGMA foreign_keys=ON`).
- **O_EXCL lock:** prevents concurrent hook-clients from spawning multiple daemons; stale-takeover via PID-aliveness check.

## Tests

```bash
bun test tests/uds-daemon/ # 38/38 green
```

## Rollback

`install.sh --rollback` plus the `.uds-bak` files next to the live `hooks.json`
restore the original behavior. The patcher never edits the bundled source.
134 changes: 134 additions & 0 deletions plugin/scripts/cli/memory-bank-export.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env bun
// claude-mem memory-bank-export — Cline-Memory-Bank-compatible markdown export.
// Spec: docs/sprint2/07-tdd-plan-v2.md Phase 7.
//
// Cline Memory Bank convention (https://docs.cline.bot/prompting/cline-memory-bank):
// projectbrief.md — what we're building, why, top-level scope
// activeContext.md — what we're focused on right now
// systemPatterns.md — architectural decisions, design patterns
// progress.md — what's done, what's next
//
// Usage:
// bun memory-bank-export.mjs --project <name> --out <dir> [--db <path>]

import { parseArgs } from 'util';
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { Database } from 'bun:sqlite';
import { DB_PATH } from '../lib/paths.mjs';

const { values: args } = parseArgs({
options: {
project: { type: 'string' },
out: { type: 'string' },
db: { type: 'string' },
limit: { type: 'string', default: '50' },
},
strict: true,
});

if (!args.project || !args.out) {
process.stderr.write('Usage: --project <name> --out <dir> [--db <path>] [--limit N]\n');
process.exit(2);
}
const dbPath = args.db || DB_PATH;
if (!existsSync(dbPath)) {
process.stderr.write(`[mb-export] no DB at ${dbPath}\n`);
process.exit(2);
}
mkdirSync(args.out, { recursive: true });

const db = new Database(dbPath, { readonly: true });
const limit = parseInt(args.limit, 10);

function fetch(type, lim = limit) {
return db.prepare(
`SELECT id, type, title, subtitle, narrative, text, created_at
FROM observations
WHERE project = ? AND type = ?
ORDER BY created_at_epoch DESC
LIMIT ?`
).all(args.project, type, lim);
}

function fmt(obs) {
if (!obs.length) return '_No observations yet._';
return obs.map(o => {
const title = o.title || o.subtitle || (o.text || o.narrative || '').slice(0, 80);
const body = o.narrative || o.text || '';
const date = (o.created_at || '').slice(0, 10);
return `### ${title}\n_${date} · obs:${o.id}_\n\n${body}`;
}).join('\n\n---\n\n');
}

const decisions = fetch('decision');
const features = fetch('feature');
const changes = fetch('change', Math.min(30, limit));
const bugfixes = fetch('bugfix', Math.min(30, limit));
const discoveries = fetch('discovery', Math.min(30, limit));
const refactors = fetch('refactor', Math.min(30, limit));

const projectbrief = `# Project Brief — ${args.project}

> Exported from claude-mem on ${new Date().toISOString()}

## Top features captured
${fmt(features.slice(0, 10))}

## Key decisions
${fmt(decisions.slice(0, 10))}
`;

const activeContext = `# Active Context — ${args.project}

> Most recent ${Math.min(30, limit)} changes + bugfixes + discoveries.

## Recent changes
${fmt(changes)}

## Recent bugfixes
${fmt(bugfixes)}

## Recent discoveries
${fmt(discoveries)}
`;

const systemPatterns = `# System Patterns — ${args.project}

> Decisions + refactors that shape the codebase.

## Architectural decisions
${fmt(decisions)}

## Refactors
${fmt(refactors)}
`;

const progress = `# Progress — ${args.project}

> Snapshot of completed and in-flight work.

## Features
${fmt(features)}

## Changes
${fmt(changes)}
`;

writeFileSync(join(args.out, 'projectbrief.md'), projectbrief);
writeFileSync(join(args.out, 'activeContext.md'), activeContext);
writeFileSync(join(args.out, 'systemPatterns.md'), systemPatterns);
writeFileSync(join(args.out, 'progress.md'), progress);

const stats = {
project: args.project,
decisions: decisions.length,
features: features.length,
changes: changes.length,
bugfixes: bugfixes.length,
discoveries: discoveries.length,
refactors: refactors.length,
out: args.out,
};
process.stdout.write(JSON.stringify(stats, null, 2) + '\n');
db.close();
Loading