Skip to content
Merged
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
12 changes: 7 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Recall — Persistent Memory for AI Coding Agents

> **Canonical agent guide.** This is the single source of truth for developing Recall, written to the cross-agent [`AGENTS.md`](https://agents.md) standard. `CLAUDE.md` is a thin shim that `@`-imports this file so Claude Code loads it automatically — do not duplicate content there. See **Agent Context Files** at the bottom.
> **Canonical agent guide.** This is the single source of truth for developing Recall, written to the cross-agent [`AGENTS.md`](https://agents.md) standard. `CLAUDE.md` is a symlink to this file so Claude Code loads it automatically — do not duplicate content there. See **Agent Context Files** at the bottom.

## Project Overview

Expand Down Expand Up @@ -34,7 +34,7 @@ Top-level directories, by purpose (one line each — not a file enumeration):
- `templates/` — install templates (`CLAUDE.md.template`, `mcp.json.template`)
- `assets/` — README banner + VHS demo tapes / gifs

Key root files: `AGENTS.md` (canonical guide), `CLAUDE.md` (shim that `@`-imports it), `CHANGELOG.md`, `FOR_CLAUDE.md` / `FOR_OPENCODE.md` / `FOR_PI.md` (host usage guides), `CONTEXT.md`, `install.sh` / `update.sh` / `uninstall.sh`, `package.json`, `tsconfig.json`.
Key root files: `AGENTS.md` (canonical guide), `CLAUDE.md` (symlink to it), `CHANGELOG.md`, `FOR_CLAUDE.md` / `FOR_OPENCODE.md` / `FOR_PI.md` (host usage guides), `CONTEXT.md`, `install.sh` / `update.sh` / `uninstall.sh`, `package.json`, `tsconfig.json`.

For the **live source tree** (every file, current symbols, callers), use the **codegraph** index (`codegraph_*` MCP tools / `codegraph_explore`) or `fd` — never an enumerated tree that drifts. `docs/architecture.md` is complementary: it documents the installed/runtime layout under `~/.agents/Recall/`, not the source tree.

Expand Down Expand Up @@ -102,7 +102,7 @@ Workers run in an isolated worktree (`/ce-worktree`): verify `pwd` is the worktr
- **Lifecycle scripts**: shared behavior lives in `lib/install-lib.sh` only — never re-implemented across `install.sh` / `update.sh` / `uninstall.sh`.
- **Prompt / guide / workflow text**: when the same workflow must reach multiple guides (`FOR_CLAUDE.md`, `FOR_PI.md`, `FOR_OPENCODE.md`, `opencode/recall-memory.md`), author the workflow body **once** as a canonical block; each platform guide carries only its platform-specific tool-name mapping plus a reference to that canonical block. Do not hand-copy a full workflow into four files and hope they stay aligned.
- **Host memory bootstraps**: generated `## MEMORY` sections point to the installed `Recall_GUIDE.md` and live MCP schemas; they never copy tool call syntax and carry `<!-- RECALL_MANAGED_MEMORY -->`. `recall_append_memory_section` is the shared separator-safe append path. `recall_memory_section_mutate` is the single Claude/Pi ownership classifier used by install, update, and uninstall: install/update refresh marked sections and migrate only normalized exact matches of complete legacy-generated bodies; uninstall removes those same owned sections. Unmarked customized/external sections survive. The marker explicitly retains Recall ownership even when the body is edited; remove it before taking external ownership. When `~/.claude/rules/memory.md` contains a Recall-specific marker (`Recall_GUIDE.md` or `recall-memory`), that external rule owns the Claude contract and install/update leave `~/.claude/CLAUDE.md` unchanged. An unrelated memory rule does not suppress Recall configuration.
- **Agent context files**: this `AGENTS.md` is canonical; `CLAUDE.md` is a shim that imports it. Never copy content into `CLAUDE.md`.
- **Agent context files**: this `AGENTS.md` is canonical; `CLAUDE.md` is a symlink to it. Never replace that symlink with a copy.
- **Tests / docs**: assert or document a fact in one place; cross-reference rather than restate it.

Before adding code or content, search for an existing definition and extend it. Code review **must** reject diffs that introduce copy-paste duplication.
Expand Down Expand Up @@ -138,10 +138,12 @@ Before adding code or content, search for an existing definition and extend it.

`AGENTS.md` (this file) is the **single source of truth** for agent guidance. To load it, each host points at it its own way:

- **Claude Code** does not read `AGENTS.md` natively — it auto-loads `CLAUDE.md`. So `CLAUDE.md` is a one-line shim containing `@AGENTS.md`, which Claude Code expands into context at launch (the same way an inlined CLAUDE.md would load). All real content lives here.
- **Claude Code** does not read `AGENTS.md` natively — it auto-loads `CLAUDE.md`. So `CLAUDE.md` is a **symlink** to `AGENTS.md` (git mode `120000`): Claude Code opens `CLAUDE.md`, git hands it this file, and only one copy ever exists on disk.
- Other hosts that honor the `AGENTS.md` standard read this file directly.

**Do not run `/init` in this repo.** Claude Code's `/init` regenerates `CLAUDE.md` from scratch, which would overwrite the `@AGENTS.md` shim with a full duplicate copy and silently reintroduce the drift this layout exists to prevent. If `CLAUDE.md` ever ends up with content other than the import line, restore it to the shim and move any new guidance into this file.
**Do not run `/init` in this repo.** Claude Code's `/init` regenerates `CLAUDE.md` from scratch, which would replace the symlink with a full duplicate copy and silently reintroduce the drift this layout exists to prevent. If `CLAUDE.md` ever becomes a regular file, restore it with `ln -sf AGENTS.md CLAUDE.md` and move any new guidance into this file.

**Known limitation — checkouts without symlink support.** With `core.symlinks=false` (a common Windows default) git materializes `CLAUDE.md` as a plain file whose entire content is the literal string `AGENTS.md` — and Claude Code would load *that* as the guide, silently and with no error. `tests/commands/scout-workflow.test.ts` asserts both that `CLAUDE.md` is a symlink and that it has not degraded into a literal-path file, so this fails loudly in CI instead of shipping a broken guide. It affects only contributors working on Recall; the `CLAUDE.md` that Recall *installs* for users comes from `templates/CLAUDE.md.template` and is unaffected.


# DOX framework
Expand Down
14 changes: 7 additions & 7 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ these steps.

## Pre-flight: agent context files

Before tagging, confirm `CLAUDE.md` is still the one-line `@AGENTS.md` shim and
has not been regenerated into a full duplicate:
Before tagging, confirm `CLAUDE.md` is still a symlink to `AGENTS.md` and has
not been regenerated into a full duplicate:

```bash
# Should print the @AGENTS.md import (and the HTML comment) — nothing else.
cat CLAUDE.md
# Should print: CLAUDE.md -> AGENTS.md
ls -l CLAUDE.md
```

`AGENTS.md` is the canonical agent guide; `CLAUDE.md` only `@`-imports it so
`AGENTS.md` is the canonical agent guide; `CLAUDE.md` is a symlink to it so
Claude Code auto-loads it. **Never run `/init` in this repo** — it rewrites
`CLAUDE.md` from scratch and reintroduces the duplication. If `CLAUDE.md` has
drifted, restore the shim and fold any new content back into `AGENTS.md` before
releasing.
drifted into a regular file, restore it with `ln -sf AGENTS.md CLAUDE.md` and
fold any new content back into `AGENTS.md` before releasing.

## Source of truth: `CHANGELOG.md`

Expand Down
35 changes: 27 additions & 8 deletions tests/commands/scout-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
// 3. DRY: the four platform guides reference the canonical block and do NOT
// hand-copy its body (per the MANDATORY DRY rule in CLAUDE.md/AGENTS.md).
// 4. The artifacts policy is recorded in canonical AGENTS.md; CLAUDE.md is a
// shim that @-imports it.
// symlink to it.
// 5. recall-scout is listed in the user-facing docs (agent-skills, README).

import { describe, test, expect } from 'bun:test';
import { readFileSync } from 'fs';
import { readFileSync, lstatSync, readlinkSync } from 'fs';
import { join } from 'path';

const repoRoot = join(import.meta.dir, '..', '..');
Expand Down Expand Up @@ -147,20 +147,39 @@ describe('DRY — guides reference the canonical block, never copy it', () => {
});

describe('artifacts policy is recorded in the canonical dev guide', () => {
// AGENTS.md is the single source of truth; CLAUDE.md is a thin shim that
// @-imports it so Claude Code loads it. The policy lives in AGENTS.md only.
// AGENTS.md is the single source of truth; CLAUDE.md is a symlink to it so
// Claude Code loads it. The policy lives in AGENTS.md only.
test('AGENTS.md documents the .agents/atlas/artifacts/ policy', () => {
const body = read('AGENTS.md');
expect(body).toContain('.agents/atlas/artifacts/');
// handoffs/ remains reserved for handoffs.
expect(body).toMatch(/\.agents\/atlas\/handoffs\/.*reserved|reserved for session handoff/i);
});

test('CLAUDE.md is a shim that @-imports AGENTS.md (no duplicated content)', () => {
test('CLAUDE.md is a symlink to AGENTS.md (no duplicated content)', () => {
// CLAUDE.md is a SYMLINK to AGENTS.md, not a file containing `@AGENTS.md`.
// The contract this test defends is unchanged — "CLAUDE.md must never carry
// a second copy of the guide" — but a symlink satisfies it by construction:
// there is literally one file, so content can't drift. Assert the LINK, not
// the content; reading CLAUDE.md now yields all of AGENTS.md, which is the
// point, so any content assertion here would be asserting AGENTS.md twice.
const link = lstatSync(join(repoRoot, 'CLAUDE.md'));
expect(link.isSymbolicLink()).toBe(true);
expect(readlinkSync(join(repoRoot, 'CLAUDE.md'))).toBe('AGENTS.md');

// Resolving it must land on the canonical guide, not a stale copy.
expect(read('CLAUDE.md')).toBe(read('AGENTS.md'));
});

// Guards the known cross-platform failure mode of the symlink approach: on a
// checkout with core.symlinks=false (common on Windows), git materializes
// CLAUDE.md as a PLAIN FILE whose whole content is the string "AGENTS.md" —
// and Claude Code would silently load that as the entire guide. The assertion
// above fails loudly in that case rather than shipping a broken guide.
test('CLAUDE.md did not degrade into a literal-path text file', () => {
const body = read('CLAUDE.md');
expect(body).toMatch(/^@AGENTS\.md\s*$/m);
// It must NOT hand-copy the policy — that would reintroduce drift.
expect(body).not.toContain('.agents/atlas/artifacts/');
expect(body.trim()).not.toBe('AGENTS.md');
expect(body).toContain('.agents/atlas/artifacts/');
});
});

Expand Down