Reach two more harnesses, and stop a database pin from drifting - #7
Conversation
Steelbore Standard §5.7 makes AGENTS.md the single harness-neutral source of truth and reduces CLAUDE.md to an @AGENTS.md import plus Claude-only content. Rendering the managed block into both files therefore delivers it to Claude twice, and — worse — creates two copies that can disagree once anything edits one of them. §5.7 puts the obligation on the tooling: rendered blocks target AGENTS.md, and every harness reading CLAUDE.md picks them up through the import. DEFAULT_TARGETS drops to ["AGENTS.md"]; --file still overrides. User-facing strings updated across the CLI, MCP, and HTTP surfaces, plus the manual and README. rule_sync_dry_run_writes_no_files now asserts one target and names it. cargo test: 245 passed. clippy -D warnings and fmt --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Companion to the rule-sync change on this branch: CLAUDE.md's project
content moves into AGENTS.md, and CLAUDE.md becomes the @AGENTS.md
import. The absolute `/home/mj/.gemini/engram.db` path in the {{DB}}
note is genericised, since AGENTS.md is a published artifact.
Steelbore Standard v1.46 §5.7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported: engram's slash commands never appear in Antigravity CLI or
OpenClaude. Two unrelated causes, plus a defect found on the way that matters
more than either.
OpenClaude was simply unknown. It is a Claude Code fork with its own config
root, so it is now the eighth harness: commands go to
`~/.openclaude/commands/`, the MCP registration is read from
`~/.openclaude.json` (the `~/.claude.json` analogue, not the settings file,
which carries no servers block), and its transcripts are read by the existing
Claude Code reader. Verified against a real session: two turns captured and
`unknown_record: 0` — the fork-only record types were already in the
non-message allowlist, and they must stay there, because a fork tripping the
drift alarm on every run would train a reader to ignore it.
Antigravity was correctly refused but dishonestly explained. It has no
slash-command directory at all; its extension surface is skills, packaged in
plugins. `agy plugin validate` reports a plugin's own `commands/` as
"2 processed (converted to skills)", so a command there becomes a skill either
way — engram now writes the plugin directly, and `validate` accepts it. This
reverses the "engram never ships a skill" rule, which was written for the
Nix-locked `~/.claude/skills` and does not generalise: the rule is now that
engram installs into whatever a harness makes writable and never into the Nix
store. Antigravity's own skills path is store-managed; its plugins path is not.
`HarnessSpec` models this as `CommandSurface::{Markdown, Plugin, None}`. The
old `command_frontmatter: bool` could not express it — the artifact's *shape*
differs, not just whether a header is read — and `None` now carries a
per-harness reason, since one shared sentence described none of the three
harnesses it was applied to.
The defect: every harness on this machine had moved to
`~/.local/share/engram/engram.db`, while the installed commands still pinned
`~/.gemini/engram.db`. Both databases are real and actively written, with
disjoint scopes, so `/engram-*` and engram's MCP tools had been reading
different stores for weeks with nothing to say so. `registered_db` was never
wrong; the registrations changed after `install` ran, and nothing checked. It
checks now, reporting the drift on the file and the harness before correcting
it, and every response carries `db_origin` so the relative-`engram.db` fallback
is visible rather than silent.
Also fixed, found because the test suite started failing on it: `find_git_root`
treated any `.git` entry as a repository. An empty `/tmp/.git` therefore made
`save-chat` resolve its project root to `/tmp`, create `/tmp/chat/`, and add
`chat/` to `/tmp/.gitignore` — which is exactly what the suite had been doing.
A directory now needs `.git/HEAD`; a `.git` file (worktree, submodule) still
counts. Tests that resolve a project root pin their own marker rather than
depending on whether the developer's `/tmp` looks like a repository.
Gates: fmt, clippy -D warnings, 259 tests (both feature sets), REUSE 3.3,
makeinfo clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016i16R4GhdSffsboRYq97Fs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8b3580d1b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// `CLAUDE.md` picks them up through the import. | ||
| /// | ||
| /// Callers that genuinely need another destination still pass `--file`. | ||
| pub const DEFAULT_TARGETS: [&str; 1] = ["AGENTS.md"]; |
There was a problem hiding this comment.
Keep syncing the Claude policy target by default
In projects whose CLAUDE.md does not already import @AGENTS.md—including existing Engram users created before this convention—changing the sole default to AGENTS.md means rule sync no longer updates any file Claude Code loads, so newly added or retired rules silently fail to take effect there. The CLI, MCP, and HTTP paths all consume this default; either continue syncing CLAUDE.md or ensure the import exists before relying on it.
AGENTS.md reference: AGENTS.md:L88-L90
Useful? React with 👍 / 👎.
| let written = managed_file::write_managed( | ||
| &manifest, | ||
| PLUGIN_MANIFEST, | ||
| WritePolicy::Owned, | ||
| dry_run, |
There was a problem hiding this comment.
Refuse to overwrite a foreign plugin manifest
When an Antigravity user already has ~/.gemini/config/plugins/engram/plugin.json, this unconditional owned-file write replaces that manifest even without --force; unlike each SKILL.md, the manifest has no banner and is never passed through is_ours. This can destroy metadata belonging to an existing plugin that happens to use the engram name, despite install promising to skip files it cannot identify as generated by Engram.
AGENTS.md reference: AGENTS.md:L179-L180
Useful? React with 👍 / 👎.
| written.reason = Some(format!( | ||
| "was pinned to {old}; re-pinned to {db}. Commands and MCP tools were reading \ | ||
| different databases." |
There was a problem hiding this comment.
Describe dry-run repinning as a preview
When a stale pin is found under install --dry-run, write_managed deliberately leaves the file untouched, but this reason still says it "re-pinned" the command. A user can therefore finish the dry run believing the database split was corrected even though the old pin remains active; make the wording conditional on dry_run (for example, "would re-pin").
Useful? React with 👍 / 👎.
| let mut w = format!( | ||
| "generated commands were pinned to {old} but this harness registers {db}; \ | ||
| re-pinned. Until now, /engram-* commands and engram's MCP tools were reading \ | ||
| different databases." |
There was a problem hiding this comment.
Do not label non-registered database choices as registered
If the target database came from --db-path, ENGRAM_DB, or the default rather than the harness configuration, this warning falsely says the harness "registers" that value and claims the command/MCP split was repaired. For example, install --db-path /override.db with MCP still registered to /registered.db produces this success message while deliberately leaving the two surfaces divergent; use db_origin to reserve the MCP-alignment claim for Registered.
AGENTS.md reference: AGENTS.md:L278-L278
Useful? React with 👍 / 👎.
CI's clippy is newer than the local toolchain and rejects `chunks_exact(4)`
under `chunks_exact_to_as_chunks`. The lint is right: `as_chunks::<4>()` yields
`[u8; 4]` arrays directly, so the `try_into().expect("chunks_exact yields 4
bytes")` that justified itself in a comment simply disappears.
Both call sites decoded the same blob the same way, so the logic is now one
`decode_vector` helper rather than two copies of a fallible conversion that
could never fail.
Behaviour is unchanged, including for a corrupt row: trailing bytes that do not
fill an f32 are still dropped, and a short vector still cosines to 0.0 rather
than panicking mid-query.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016i16R4GhdSffsboRYq97Fs
Reported: engram's slash commands never appear in Antigravity CLI (
agy) or OpenClaude. Two unrelated causes — plus a defect found on the way that matters more than either.1. OpenClaude was simply unknown
A Claude Code fork (
@gitlawb/openclaudev0.24.0) with its own config root. Now the eighth harness:~/.openclaude/commands/~/.openclaude.json— the~/.claude.jsonanalogue, not~/.openclaude/settings.json, which carries no servers block~/.openclaude/projects/<mangled-cwd>/*.jsonl, read by the existing Claude Code readerVerified against a real session: 2 turns captured,
unknown_record: 0. The fork-only record types (mode,file-history-snapshot,last-prompt) were already in the non-message allowlist — and must stay there, because a fork tripping the drift alarm on every run would train a reader to ignore it.2. Antigravity was correctly refused, but dishonestly explained
It has no slash-command directory at all. Its extension surface is skills, packaged in plugins. Decisively:
A command there becomes a skill regardless, so engram writes the plugin directly — and
agy plugin validateaccepts it:This reverses the documented "engram never ships a skill" rule. That rule was written for the Nix-locked
~/.claude/skillsand does not generalise; the rule is now that engram installs into whatever a harness makes writable and never into the Nix store. Antigravity's own skills path is store-managed — its plugins path is not.HarnessSpecmodels this asCommandSurface::{Markdown, Plugin, None}. The oldcommand_frontmatter: boolcould not express it: the artifact's shape differs, not just whether a header is read.Nonenow carries a per-harness reason, since one shared sentence described none of the three harnesses it was applied to.3. The defect worth more than both
Every harness on this machine had moved to
~/.local/share/engram/engram.db. The installed commands still pinned~/.gemini/engram.db. Both databases are real and actively written, with disjoint scopes:~/.local/share/…(2.5M)~/.gemini/engram.db(1.6M)So
/engram-*and engram's MCP tools had been reading different stores for weeks, with nothing to say so.registered_dbwas never wrong — the registrations changed afterinstallran, and nothing checked. It checks now, reporting the drift on the file and on the harness before correcting it:Every response also carries
db_origin(override/registered/env/default) so the relative-engram.dbfallback is visible rather than silent.4.
find_git_rootaccepted an empty.gitFound because the test suite started failing on it. Any
.gitentry counted as a repository, so an empty/tmp/.gitmadesave-chatresolve its project root to/tmp— creating/tmp/chat/and addingchat/to/tmp/.gitignore. Which is exactly what the test suite had been doing.A directory now needs
.git/HEAD; a.gitfile (worktree, submodule) still counts. Tests that resolve a project root pin their own marker viapinned_projectrather than depending on whether the developer's/tmplooks like a repository.Gates
fmt·clippy -D warnings· 259 tests across both feature sets · REUSE 3.3 ·makeinfoclean🤖 Generated with Claude Code
https://claude.ai/code/session_016i16R4GhdSffsboRYq97Fs