# v0.30.1 Patch Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Fix 4 UX bugs found by `/review` + smoke test on v0.30.0 so the GitHub Release can ship a clean headline. No new features.

**Architecture:** All four fixes live in `src/cli.ts`. Each fix is a narrow change guarded by a failing test. Schema and storage untouched.

**Tech Stack:** TypeScript, Node 22.5+, vitest, node:sqlite. No new deps.

---

## Context (for the executor)

Four real issues confirmed by independent reviewer + smoke test against npm artifact:

1. `hippo recall --layer <L>` is accepted but silently dropped. Results leak other layers.
2. `examples/rsi-demo/agent.mjs:126` passes `--layer trace` — works only because every entry happens to be a trace. Fixed transitively by #1.
3. `hippo status` has no `Trace:` line even though `byLayer` already tracks it (`src/cli.ts:1828`).
4. `hippo --version` errors with "Unknown command".

Current code anchors:
- `parseArgs`: `src/cli.ts:161-202`
- `cmdRecall`: `src/cli.ts:551-640` (`--outcome` filter is at `:613-624` — add `--layer` filter next to it)
- `cmdStatus`: `src/cli.ts:1818-1900` (byLayer at `:1825-1830`, print block at `:1857-1859`)
- entry-point dispatch: `src/cli.ts:3982-4010`

No schema changes. No migration. No config changes.

---

## Task 1: Strict `--layer` filter on recall

**Files:**
- Modify: `src/cli.ts:613-624` (add `--layer` filter block alongside `--outcome`)
- Test: `tests/cli-recall-filters.test.ts` (new file)

**Step 1: Write the failing test**

Create `tests/cli-recall-filters.test.ts`:

```ts
import { describe, it, expect, beforeEach } from 'vitest';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const HIPPO_BIN = join(process.cwd(), 'bin', 'hippo.js');

function hippo(env: Record<string, string>, ...args: string[]): string {
  return execFileSync('node', [HIPPO_BIN, ...args], {
    env: { ...process.env, ...env },
    encoding: 'utf-8',
  });
}

describe('recall --layer filter (v0.30.1)', () => {
  let home: string;
  let env: Record<string, string>;

  beforeEach(() => {
    home = mkdtempSync(join(tmpdir(), 'hippo-layer-'));
    env = { HIPPO_HOME: join(home, '.hippo') };
    hippo(env, 'init');
  });

  it('--layer trace excludes non-trace entries', () => {
    hippo(env, 'remember', 'red herring episodic about deploys');
    hippo(env, 'trace', 'record', '--task', 'deploy', '--steps', '[{"action":"x","observation":"y"}]', '--outcome', 'success');

    const out = hippo(env, 'recall', 'deploy', '--layer', 'trace', '--limit', '5');

    expect(out).toContain('[trace]');
    expect(out).not.toContain('[episodic]');
    rmSync(home, { recursive: true, force: true });
  });

  it('--layer rejects invalid value', () => {
    let err = '';
    try {
      hippo(env, 'recall', 'anything', '--layer', 'bogus');
    } catch (e) {
      err = String((e as { stderr?: Buffer }).stderr ?? '');
    }
    expect(err).toMatch(/Invalid --layer/);
    rmSync(home, { recursive: true, force: true });
  });
});
```

**Step 2: Run test to verify it fails**

```bash
npx vitest run tests/cli-recall-filters.test.ts
```

Expected: FAIL — first assertion fails because `[episodic]` leaks through.

**Step 3: Implement the filter**

In `src/cli.ts` `cmdRecall`, immediately after the `--outcome` filter block (around line 624), add:

```ts
// --layer filter: strict, drops entries whose layer does not match.
const layerFilter = flags['layer'] !== undefined ? String(flags['layer']).trim() : '';
if (layerFilter) {
  const validLayers = Object.values(Layer) as string[];
  if (!validLayers.includes(layerFilter)) {
    console.error(`Invalid --layer: "${layerFilter}". Must be one of: ${validLayers.join(', ')}.`);
    process.exit(1);
  }
  results = results.filter((r) => r.entry.layer === layerFilter);
}
```

**Step 4: Run test to verify it passes**

```bash
npx vitest run tests/cli-recall-filters.test.ts
```

Expected: PASS both cases.

**Step 5: Run full suite for regressions**

```bash
npm run build && npx vitest run
```

Expected: 601/601 (599 existing + 2 new) PASS.

**Step 6: Commit**

```bash
git add src/cli.ts tests/cli-recall-filters.test.ts
git commit -m "fix(recall): --layer is a strict filter, not a soft rank"
```

---

## Task 2: Add `Trace:` counter to `hippo status`

**Files:**
- Modify: `src/cli.ts:1857-1859` (add one line after Semantic)
- Test: `tests/cli-status.test.ts` (new file)

**Step 1: Write the failing test**

```ts
import { describe, it, expect } from 'vitest';
import { execFileSync } from 'node:child_process';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const HIPPO_BIN = join(process.cwd(), 'bin', 'hippo.js');

describe('hippo status (v0.30.1)', () => {
  it('shows trace layer count after recording a trace', () => {
    const home = mkdtempSync(join(tmpdir(), 'hippo-status-'));
    const env = { ...process.env, HIPPO_HOME: join(home, '.hippo') };
    execFileSync('node', [HIPPO_BIN, 'init'], { env, encoding: 'utf-8' });
    execFileSync('node', [HIPPO_BIN, 'trace', 'record',
      '--task', 't', '--steps', '[{"action":"a","observation":"o"}]',
      '--outcome', 'success'], { env, encoding: 'utf-8' });
    const out = execFileSync('node', [HIPPO_BIN, 'status'], { env, encoding: 'utf-8' });
    expect(out).toMatch(/Trace:\s+1/);
  });
});
```

**Step 2: Run test to verify it fails**

```bash
npx vitest run tests/cli-status.test.ts
```

Expected: FAIL — no "Trace:" line in output.

**Step 3: Implement**

In `src/cli.ts` `cmdStatus`, after the Semantic print line (around line 1859), add:

```ts
console.log(`  Trace:           ${byLayer[Layer.Trace]}`);
```

Match existing spacing: 2 spaces indent, label padded to col 19, colon, count.

**Step 4: Run test**

```bash
npx vitest run tests/cli-status.test.ts
```

Expected: PASS.

**Step 5: Commit**

```bash
git add src/cli.ts tests/cli-status.test.ts
git commit -m "fix(status): surface trace layer count"
```

---

## Task 3: Add `hippo --version` / `-v`

**Files:**
- Modify: `src/cli.ts` main dispatch (around line 3988, inside `async function main()` before the switch)
- Test: `tests/cli-version.test.ts` (new file)

**Step 1: Write the failing test**

```ts
import { describe, it, expect } from 'vitest';
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

const HIPPO_BIN = join(process.cwd(), 'bin', 'hippo.js');
const pkg = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8')) as { version: string };

describe('hippo --version (v0.30.1)', () => {
  it('--version prints the package version', () => {
    const out = execFileSync('node', [HIPPO_BIN, '--version'], { encoding: 'utf-8' });
    expect(out.trim()).toBe(pkg.version);
  });

  it('-v prints the package version', () => {
    const out = execFileSync('node', [HIPPO_BIN, '-v'], { encoding: 'utf-8' });
    expect(out.trim()).toBe(pkg.version);
  });
});
```

**Step 2: Run test to verify it fails**

```bash
npx vitest run tests/cli-version.test.ts
```

Expected: FAIL — "Unknown command: --version".

**Step 3: Implement**

In `src/cli.ts`, after `const { command, args, flags } = parseArgs(process.argv);` (line 3982) and before `const hippoRoot = getHippoRoot(...)`, add:

```ts
if (command === '--version' || command === '-v' || flags['version']) {
  const pkgPath = path.join(__dirname, '..', 'package.json');
  // dist/ lives next to package.json when installed; fall back to two levels up for src/
  let pkgJson: string;
  try {
    pkgJson = fs.readFileSync(pkgPath, 'utf-8');
  } catch {
    pkgJson = fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8');
  }
  const { version } = JSON.parse(pkgJson) as { version: string };
  console.log(version);
  process.exit(0);
}
```

Note: `__dirname` is available because `tsconfig.json` compiles to CommonJS for the CLI entry (verify by reading `tsconfig.json` first). If ESM-only, substitute `import.meta.url`-derived path.

**Step 4: Run test to verify it passes**

```bash
npx vitest run tests/cli-version.test.ts
```

Expected: PASS both cases.

**Step 5: Commit**

```bash
git add src/cli.ts tests/cli-version.test.ts
git commit -m "feat(cli): --version / -v flag"
```

---

## Task 4: Verify RSI demo works with strict `--layer`

**Files:**
- Read only: `examples/rsi-demo/agent.mjs`
- Test: extend an existing smoke file or add `tests/rsi-demo-smoke.test.ts`

**Step 1: Confirm demo's `--layer trace` now produces strict results**

No code change needed — Task 1 already makes `--layer trace` strict. Verify manually:

```bash
node examples/rsi-demo/agent.mjs | tail -20
```

Expected: "lateRate - earlyRate >= 0.20" threshold still passes. No exit 1.

**Step 2: Add a one-line smoke assertion (optional, time-boxed 5min)**

If time permits, add to an existing integration test file a line that confirms the demo doesn't regress. Skip if it adds >30 min.

**Step 3: No commit needed if no code change**

---

## Task 5: Full suite + build

```bash
npm run build
npx vitest run
```

Expected: all green, 3 new test files, ~601-604 tests total.

---

## Task 6: Publish v0.30.1 (handled by /publish-repo)

Flagged for the publish skill, not the executor subagent:
- Bump manifests from 0.30.0 → 0.30.1 (package.json, openclaw.plugin.json, extensions/openclaw-plugin/openclaw.plugin.json, extensions/openclaw-plugin/package.json)
- CHANGELOG: "### Fixed" section listing the 4 items
- README: "What's new in v0.30.1" (keep short: RSI demo headline now works as documented)
- npm publish, tag v0.30.1, push
- npm install -g hippo-memory@0.30.1
- openclaw plugins install hippo-memory@0.30.1 --force --pin

---

## Done criteria

- [ ] 3 new test files, all pass
- [ ] `git log --oneline v0.30.0..HEAD` shows exactly 3 `fix/feat` commits (one per task)
- [ ] `hippo recall --layer trace` returns only trace entries (manual verify)
- [ ] `hippo status` prints "Trace:    N" (manual verify)
- [ ] `hippo --version` prints 0.30.1 (manual verify post-publish)
- [ ] Full suite green
- [ ] `examples/rsi-demo/agent.mjs` still passes the 0.20 gap bar

## Do NOT

- Refactor cmdRecall or cmdStatus beyond the lines specified.
- Add new flags beyond `--layer` and `--version` / `-v`.
- Touch schema, store, migrations, consolidate.
- Change default behavior when `--layer` is absent.
- Update pinned-inject or replay.
