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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ research/
*.db-journal
*.db-wal
*.db-shm

# Generated by `make -C doc` from engram.texi.
doc/engram.info
doc/engram.html
doc/engram.pdf
171 changes: 171 additions & 0 deletions AGENTS.md

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,45 @@ follows [Keep a Changelog](https://keepachangelog.com/); versions follow

### Added

- **OpenClaude** is a supported harness (the eighth). It is a Claude Code fork
with its own config root: commands go to `~/.openclaude/commands/`, the MCP
registration is read from `~/.openclaude.json`, and its transcripts are read
by the existing Claude Code reader — the record types the fork adds are
recognised rather than counted as format drift.
- **Antigravity** now gets a plugin at `~/.gemini/config/plugins/engram/`
(`plugin.json` plus one `skills/engram-<name>/SKILL.md` per command). It has
no slash-command directory at all; `agy plugin validate` reports a plugin's
own `commands/` as "converted to skills", so engram writes skills directly.
- `install` reports `db_origin` (`override` / `registered` / `env` / `default`)
alongside the database it pins, so the relative-`engram.db` fallback is
visible rather than silent.

### Fixed

- **`install` now detects a stale database pin.** When a generated command
points at a different database than the harness currently registers, the
drift is reported on both the file and the harness before being corrected.
Previously the two could diverge indefinitely: if a harness's registration
moved after `install` ran, its `/engram-*` commands and its engram MCP tools
read different stores with nothing to say so.
- **`find_git_root` requires a working tree, not merely a `.git` entry.** A
directory must contain `.git/HEAD`; a `.git` file (worktree or submodule
pointer) also counts. An empty `.git` directory in a shared location — e.g.
`/tmp/.git` — previously captured every path beneath it, so `save-chat` would
resolve its project root there, create `chat/`, and edit that directory's
`.gitignore`.
- Harnesses with no writable surface each state their own reason instead of
sharing one sentence that described none of them precisely.

### Changed

- `HarnessSpec` models its command surface as an enum —
`CommandSurface::{Markdown, Plugin, None}` — replacing `commands_dir`,
`command_file`, and the `command_frontmatter` bool. Antigravity's surface
differs in artifact *shape*, not just in whether a header is read.

### Added

- **`flake.nix`** — Engram is now consumable as a Nix flake input
(`github:Spacecraft-Software/Engram`), exposing `packages.default`,
`packages.engram`, `apps.default`, `checks.default`, and `default`/`docs`
Expand Down
260 changes: 3 additions & 257 deletions CLAUDE.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ engram rule add --id skill-description-1000 \
# Read what's in effect.
engram rule list

# Render into AGENTS.md and CLAUDE.md at the project root.
# Render into AGENTS.md at the project root.
engram rule sync

# Withdraw one when it stops applying, then re-sync.
Expand Down Expand Up @@ -134,7 +134,7 @@ engram rule purge --id old-policy --yes # delete a retired
```

Everything outside them is preserved verbatim, so the block can sit inside a
hand-written `CLAUDE.md` indefinitely. The rendered block is a pure function of
hand-written `AGENTS.md` indefinitely. The rendered block is a pure function of
the rules — no generation timestamp — so re-running `sync` with unchanged rules
writes nothing at all. That makes it safe in a `SessionStart` hook, a pre-commit
gate, or a CI check (`engram rule sync --dry-run` reports `updated` if someone
Expand Down Expand Up @@ -214,7 +214,7 @@ storage failure. New routes should keep following that pattern.
> touches anything outside the database. Target paths come from the server
> process's own working directory and never from caller input, so there is no
> path-traversal surface — but combined with the no-auth posture it means any
> local process can rewrite that project's `AGENTS.md` and `CLAUDE.md`. The
> local process can rewrite that project's `AGENTS.md`. The
> CLI's `--file` override is deliberately not exposed over HTTP. Weigh this
> before binding the server anywhere but `127.0.0.1`.

Expand Down
47 changes: 36 additions & 11 deletions doc/engram.texi
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ explicit). @code{--dry-run} previews the deletion.
engram rule sync [--scope @var{id}] [--file @var{path}]@dots{} [--dry-run]
@end example

Render the scope's rules into @file{AGENTS.md} and @file{CLAUDE.md}
Render the scope's rules into @file{AGENTS.md}
(or the given @code{--file} targets, repeatable). Only the region
between the engram sentinels is rewritten. With @code{--dry-run},
reports what would be written without touching any file.
Expand Down Expand Up @@ -992,7 +992,7 @@ deletes.

@item rule_sync
@cindex rule_sync tool (MCP)
Render a scope's rules into @file{AGENTS.md} and @file{CLAUDE.md} at
Render a scope's rules into @file{AGENTS.md} at
the project root.

@item save_chat
Expand Down Expand Up @@ -1099,7 +1099,7 @@ unknown rule, @code{200} with the outcome on success.

@item POST /v1/rules/sync
@cindex POST /v1/rules/sync
Render the scope's rules into @file{AGENTS.md} and @file{CLAUDE.md}.
Render the scope's rules into @file{AGENTS.md}.
This is the only route that writes outside the database; targets
derive from the server process's working directory, never from caller
input.
Expand Down Expand Up @@ -1175,9 +1175,10 @@ Four invariants worth not breaking:
@item
@cindex sync, as delivery
@emph{Sync is the delivery mechanism, not an export.} A row in SQLite
never reaches a model's context. Rendering into @file{AGENTS.md} and
@file{CLAUDE.md} --- files harnesses auto-load --- is what makes a rule
take effect.
never reaches a model's context. Rendering into @file{AGENTS.md} ---
the file harnesses auto-load, and that Claude Code reaches through the
@code{@@AGENTS.md} import in its @file{CLAUDE.md} (Steelbore Standard
§5.7) --- is what makes a rule take effect.

@item
@emph{The rendered block is a pure function of the rules.} No
Expand Down Expand Up @@ -1264,24 +1265,48 @@ installed, and never creates a directory for one that is not.
Support is uneven, and the manual would rather say so than imply
otherwise:

@multitable @columnfractions .22 .26 .22 .30
@multitable @columnfractions .22 .26 .26 .26
@headitem Harness @tab Transcripts @tab Commands @tab Hooks
@item claude-code @tab read @tab written @tab written (opt-in)
@item openclaude @tab read @tab written @tab ---
@item codex @tab read @tab written @tab ---
@item opencode @tab --- @tab written @tab ---
@item antigravity @tab --- @tab plugin (skills) @tab ---
@item qwen @tab --- @tab --- @tab ---
@item goose @tab --- @tab --- @tab ---
@item antigravity @tab --- @tab --- @tab ---
@item copilot-cli @tab --- @tab --- @tab ---
@end multitable

@cindex openclaude
@cindex forks, harness
OpenClaude is a fork of Claude Code with its own configuration root. Its
transcripts match Claude Code's format down to the record keys, so one
reader serves both; the records the fork adds are recognized rather than
counted as drift. Its MCP registration lives in @file{~/.openclaude.json},
the analogue of @file{~/.claude.json} --- not in
@file{~/.openclaude/settings.json}, which holds unrelated settings.

@cindex antigravity, plugin
@cindex skills
Antigravity is the one harness with no slash-command directory at all.
Its extension surface is @dfn{skills}, packaged in @dfn{plugins}, and a
plugin's own @file{commands/} directory is reported by
@command{agy plugin validate} as ``converted to skills'' --- so a command
there would become a skill regardless. Engram therefore writes a plugin
directly: a manifest plus one skill per command. The skill frontmatter is
a different contract from a command file's, but its description is lifted
from the same template, so the two surfaces cannot disagree about what a
command does.

Every gap in that table is reported at runtime with its reason attached,
rather than omitted. @command{engram install --list} names the harnesses
with no command surface; @command{engram ingest} fails with exit
status@tie{}2 and an explanation when asked to read a harness it cannot.
A feature that quietly does nothing on four of seven harnesses would read
as broken; one that says which four, and why, is merely honest about its
reach.
A feature that quietly does nothing on three of eight harnesses would read
as broken; one that says which three, and why, is merely honest about its
reach. Each of those three states its own reason: unsurveyed session
storage and an absent command directory are different problems, and one
sentence shared between them described neither.

@section Formats Engram does not control

Expand Down
6 changes: 3 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,14 +460,14 @@ EXAMPLES:
#[arg(long)]
scope: Option<String>,
},
/// Render the scope's rules into AGENTS.md and CLAUDE.md.
/// Render the scope's rules into AGENTS.md.
///
/// Rewrites only the region between the engram sentinels, leaving the rest
/// of each file untouched. Running it twice with unchanged rules is a
/// no-op, so it is safe to wire into a hook or a commit gate.
#[command(after_help = "\
EXAMPLES:
# Render into AGENTS.md and CLAUDE.md at the project root.
# Render into AGENTS.md at the project root.
engram rule sync

# Read-only check: reports 'updated' if the block is stale or hand-edited.
Expand All @@ -481,7 +481,7 @@ EXAMPLES:
#[arg(long)]
scope: Option<String>,
/// Target file, repeatable. Relative paths resolve against the project
/// root. Defaults to AGENTS.md and CLAUDE.md.
/// root. Defaults to AGENTS.md.
#[arg(long = "file")]
files: Vec<std::path::PathBuf>,
/// Report what would be written without touching any file.
Expand Down
Loading
Loading