Skip to content
Closed
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
239 changes: 195 additions & 44 deletions internal/components/engram/inject.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package engram

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -129,17 +131,11 @@ func engramOverlayJSON(agentID model.AgentID, cmd string) []byte {
},
}
} else {
args := []string{"mcp", "--tools=agent"}
if agentID == model.AgentAntigravity {
// Antigravity should launch the default Engram MCP server without
// narrowing the exposed tool set.
args = []string{"mcp"}
}
cfg = map[string]any{
"mcpServers": map[string]any{
"engram": map[string]any{
"command": cmd,
"args": args,
"args": []string{"mcp", "--tools=agent"},
},
},
}
Expand Down Expand Up @@ -262,36 +258,198 @@ func ensureJSONFileIfMissing(path string) (filemerge.WriteResult, error) {
return filemerge.WriteFileAtomic(path, []byte("{}\n"), 0o644)
}

func installAntigravityEngramPlugin(homeDir, engramCommand string) (bool, []string, error) {
pluginDir := filepath.Join(homeDir, ".gemini", "antigravity-cli", "plugins", "gentle-ai-engram")
files := make([]string, 0, 3)
changed := false
type fileImage struct {
data []byte
exists bool
}

func isActiveAntigravityManifest(image fileImage) bool {
if !image.exists {
return false
}
var manifest map[string]json.RawMessage
if err := json.Unmarshal(image.data, &manifest); err != nil || manifest == nil {
return false
}
var name string
return json.Unmarshal(manifest["name"], &name) == nil && name == "gentle-ai-engram"
}

var antigravityWriteFile = filemerge.WriteFileAtomic
var antigravityReadFile = os.ReadFile

func readImage(path string) (fileImage, error) {
b, err := antigravityReadFile(path)
if os.IsNotExist(err) {
return fileImage{}, nil
}
return fileImage{data: b, exists: err == nil}, err
}

func sameImage(a, b fileImage) bool { return a.exists == b.exists && bytes.Equal(a.data, b.data) }

func writeReconciled(path string, before fileImage, desired []byte) (bool, string, error) {
r, err := antigravityWriteFile(path, desired, 0o644)
if err == nil {
return r.Changed, "post-replacement", nil
}
after, readErr := readImage(path)
if readErr != nil {
return false, "unknown", errors.Join(err, fmt.Errorf("reread %q after atomic-write error: %w", path, readErr))
}
state := "unknown"
if sameImage(after, before) {
state = "pre-replacement"
} else if after.exists && bytes.Equal(after.data, desired) {
state = "post-replacement"
}
return !sameImage(before, after), state, fmt.Errorf("atomic write %q failed; observed %s: %w", path, state, err)
}

pluginPath := filepath.Join(pluginDir, "plugin.json")
pluginWrite, err := filemerge.WriteFileAtomic(pluginPath, []byte(antigravityEngramPluginJSON), 0o644)
func antigravityConfigs(globalPath, pluginPath string, manifestActive bool) (fileImage, []byte, string, bool, error) {
global, err := readImage(globalPath)
if err != nil {
return false, nil, fmt.Errorf("write Antigravity Engram plugin manifest: %w", err)
return global, nil, "", false, fmt.Errorf("read Antigravity global config %q: %w", globalPath, err)
}
var root map[string]json.RawMessage
if global.exists && json.Unmarshal(global.data, &root) != nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity global config %q", globalPath)
}
if global.exists && root == nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity global config %q: expected object", globalPath)
}
servers := map[string]json.RawMessage{}
if raw, ok := root["mcpServers"]; ok && json.Unmarshal(raw, &servers) != nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity global mcpServers %q", globalPath)
}
if servers == nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity global mcpServers %q: expected object", globalPath)
}
changed = changed || pluginWrite.Changed
files = append(files, pluginPath)
_, migrate := servers["engram"]
legacy, _ := existingMergedEngramCommand(global.data, model.AgentAntigravity)
delete(servers, "engram")
if migrate {
root["mcpServers"], _ = json.Marshal(servers)
}
globalDesired, _ := json.MarshalIndent(root, "", " ")
globalDesired = append(globalDesired, '\n')

pluginMCPPath := filepath.Join(pluginDir, "mcp_config.json")
mcpWrite, err := filemerge.WriteFileAtomic(pluginMCPPath, engramOverlayJSON(model.AgentAntigravity, engramCommand), 0o644)
plugin, err := readImage(pluginPath)
if err != nil {
return false, nil, fmt.Errorf("write Antigravity Engram plugin MCP config: %w", err)
return global, nil, "", false, fmt.Errorf("read Antigravity plugin config %q: %w", pluginPath, err)
}
root, servers = map[string]json.RawMessage{}, map[string]json.RawMessage{}
if plugin.exists && json.Unmarshal(plugin.data, &root) != nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity plugin config %q", pluginPath)
}
if plugin.exists && root == nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity plugin config %q: expected object", pluginPath)
}
if raw, ok := root["mcpServers"]; ok && json.Unmarshal(raw, &servers) != nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity plugin mcpServers %q", pluginPath)
}
if servers == nil {
return global, nil, "", false, fmt.Errorf("parse Antigravity plugin mcpServers %q: expected object", pluginPath)
}
changed = changed || mcpWrite.Changed
files = append(files, pluginMCPPath)
cmd, ok := existingMergedEngramCommand(plugin.data, model.AgentAntigravity)
if !manifestActive && legacy != "" {
cmd = legacy
} else if !ok {
cmd = legacy
}
if cmd == "" {
cmd = preferredStableEngramCommand()
}
cmd = stableEngramCommandForExisting(cmd, model.AgentAntigravity)
servers["engram"], _ = json.Marshal(map[string]any{"command": cmd, "args": []string{"mcp", "--tools=agent"}})
root["mcpServers"], _ = json.Marshal(servers)
pluginDesired, _ := json.MarshalIndent(root, "", " ")
return global, append(pluginDesired, '\n'), string(globalDesired), migrate, nil
}

hooksPath := filepath.Join(pluginDir, "hooks.json")
hooksWrite, err := filemerge.WriteFileAtomic(hooksPath, antigravityEngramHooksJSON(), 0o644)
func installAntigravityEngramPlugin(homeDir string, adapter agents.Adapter) (bool, []string, error) {
globalPath := adapter.MCPConfigPath(homeDir, "engram")
pluginDir := filepath.Join(filepath.Dir(globalPath), "plugins", "gentle-ai-engram")
manifestPath, mcpPath := filepath.Join(pluginDir, "plugin.json"), filepath.Join(pluginDir, "mcp_config.json")
manifestBefore, err := readImage(manifestPath)
if err != nil {
return false, nil, fmt.Errorf("write Antigravity Engram hooks: %w", err)
return false, nil, fmt.Errorf("read Antigravity manifest %q: %w", manifestPath, err)
}
manifestBeforeActive := isActiveAntigravityManifest(manifestBefore)
globalBefore, pluginMCP, globalDesired, migrate, err := antigravityConfigs(globalPath, mcpPath, manifestBeforeActive)
if err != nil {
return false, nil, err
}
files := []string{mcpPath, filepath.Join(pluginDir, "hooks.json"), adapter.SettingsPath(homeDir), manifestPath}
changed := false
for i, content := range [][]byte{pluginMCP, antigravityEngramHooksJSON(), []byte("{}\n")} {
before, readErr := readImage(files[i])
if readErr != nil {
return false, nil, fmt.Errorf("read staging target %q: %w", files[i], readErr)
}
if i == 2 && before.exists {
continue
}
Comment on lines +383 to +392

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all call sites of ensureAntigravitySettings (excluding its definition)
rg -nP '\bensureAntigravitySettings\s*\(' --type=go -C2

# Confirm whether the ~/.gemini/settings.json bootstrap source is still referenced anywhere
rg -nP '\.gemini/settings\.json|filepath\.Join\([^)]*\.gemini' --type=go -C2

Repository: Gentleman-Programming/gentle-ai

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and surrounding symbols first
ast-grep outline internal/components/engram/inject.go --view expanded || true

# Show the relevant region around the staging loop and helper definition
sed -n '340,780p' internal/components/engram/inject.go | cat -n

# Find all references to the helper and the gemini bootstrap source
rg -n --hidden -S 'ensureAntigravitySettings|\.gemini/settings\.json|filepath\.Join\([^)]*\.gemini' internal/components/engram -C 2 || true

# Check whether the helper is called anywhere else in the repo
rg -n --hidden -S '\bensureAntigravitySettings\s*\(' . -g '*.go' -C 2 || true

Repository: Gentleman-Programming/gentle-ai

Length of output: 32556


Keep the Antigravity settings bootstrap

settings.json now falls back to {} when missing, and ensureAntigravitySettings has no production call sites, so Antigravity no longer inherits ~/.gemini/settings.json defaults on first run. Restore that bootstrap before this staging path.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/engram/inject.go` around lines 381 - 390, Restore the
Antigravity settings bootstrap before the staging flow in the relevant injection
function, ensuring a missing settings.json inherits the user’s
~/.gemini/settings.json defaults rather than only falling back to "{}". Reuse
the existing ensureAntigravitySettings helper and preserve the current
files/readImage staging behavior.

wrote, _, writeErr := writeReconciled(files[i], before, content)
changed = changed || wrote
if writeErr != nil {
observed := fmt.Errorf("observed global active=%v at %q, manifest active=%v at %q", migrate, globalPath, manifestBeforeActive, manifestPath)
if !migrate && !manifestBeforeActive {
observed = errors.Join(observed, fmt.Errorf("no active registration; manual recovery required"))
}
return false, nil, errors.Join(writeErr, observed)
}
}

var primary error
if migrate {
wrote, state, writeErr := writeReconciled(globalPath, globalBefore, []byte(globalDesired))
changed = changed || wrote
if writeErr != nil {
if state == "pre-replacement" {
return false, nil, errors.Join(writeErr, fmt.Errorf("manifest %q active=%v", manifestPath, manifestBeforeActive))
}
primary = writeErr
}
files = append(files, globalPath)
}
changed = changed || hooksWrite.Changed
files = append(files, hooksPath)

return changed, files, nil
manifestDesired := []byte(antigravityEngramPluginJSON)
wrote, _, activateErr := writeReconciled(manifestPath, manifestBefore, manifestDesired)
changed = changed || wrote
if activateErr == nil {
return changed, files, primary
}
primary = errors.Join(primary, activateErr)
manifestAfter, observeErr := readImage(manifestPath)
if observeErr != nil {
primary = errors.Join(primary, fmt.Errorf("observe manifest %q after activation failure: %w", manifestPath, observeErr))
} else if isActiveAntigravityManifest(manifestAfter) {
return false, nil, errors.Join(primary, fmt.Errorf("plugin-only registration observed at %q", manifestPath))
}
if !migrate {
return false, nil, errors.Join(primary, fmt.Errorf("manifest inactive and no prior global registration; manual recovery required"))
}
currentGlobal, readErr := readImage(globalPath)
if readErr != nil {
return false, nil, errors.Join(primary, fmt.Errorf("observe global %q before rollback: %w; manual recovery required", globalPath, readErr))
}
_, rollbackState, rollbackErr := writeReconciled(globalPath, currentGlobal, globalBefore.data)
if rollbackErr == nil || rollbackState == "post-replacement" {
return false, nil, errors.Join(primary, rollbackErr, fmt.Errorf("global registration restored exactly at %q", globalPath))
}
if rollbackState == "pre-replacement" {
currentManifest, readErr := readImage(manifestPath)
if readErr != nil {
return false, nil, errors.Join(primary, rollbackErr, fmt.Errorf("observe manifest %q before roll-forward: %w; manual recovery required", manifestPath, readErr))
}
_, rollState, rollErr := writeReconciled(manifestPath, currentManifest, manifestDesired)
if rollErr == nil || rollState == "post-replacement" {
return false, nil, errors.Join(primary, rollbackErr, rollErr, fmt.Errorf("rollback failed; converged plugin-only at %q", manifestPath))
}
rollbackErr = errors.Join(rollbackErr, rollErr)
}
return false, nil, errors.Join(primary, rollbackErr, fmt.Errorf("unknown state at global %q and manifest %q; manual recovery required", globalPath, manifestPath))
}

func injectWithOptions(configHomeDir, promptDir string, adapter agents.Adapter, opts InjectOptions) (InjectionResult, error) {
Expand Down Expand Up @@ -349,6 +507,15 @@ func injectWithOptions(configHomeDir, promptDir string, adapter agents.Adapter,
if mcpPath == "" {
break
}
if adapter.Agent() == model.AgentAntigravity {
pluginChanged, pluginFiles, pluginErr := installAntigravityEngramPlugin(configHomeDir, adapter)
if pluginErr != nil {
return InjectionResult{}, pluginErr
}
changed = changed || pluginChanged
files = append(files, pluginFiles...)
break
}
engramCommand := stableEngramCommandForMergedConfig(mcpPath, adapter.Agent())
var overlay []byte
if adapter.Agent() == model.AgentVSCodeCopilot {
Expand All @@ -364,22 +531,6 @@ func injectWithOptions(configHomeDir, promptDir string, adapter agents.Adapter,
changed = changed || mcpWrite.Changed
files = append(files, mcpPath)

if adapter.Agent() == model.AgentAntigravity {
settingsWrite, settingsErr := ensureJSONFileIfMissing(adapter.SettingsPath(configHomeDir))
if settingsErr != nil {
return InjectionResult{}, fmt.Errorf("ensure Antigravity settings: %w", settingsErr)
}
changed = changed || settingsWrite.Changed
files = append(files, adapter.SettingsPath(configHomeDir))

pluginChanged, pluginFiles, pluginErr := installAntigravityEngramPlugin(configHomeDir, engramCommand)
if pluginErr != nil {
return InjectionResult{}, pluginErr
}
changed = changed || pluginChanged
files = append(files, pluginFiles...)
}

case model.StrategyMergeIntoYAML:
// Hermes: upsert the engram MCP server block under mcp_servers: in
// ~/.hermes/config.yaml via the comment-preserving YAML string helpers.
Expand Down Expand Up @@ -854,7 +1005,7 @@ func isVersionedHomebrewCellarPath(path string) bool {

func isStableHomebrewEngramPath(path string) bool {
clean := filepath.ToSlash(filepath.Clean(path))
return (clean == "/opt/homebrew/bin/engram" || clean == "/usr/local/bin/engram") && isEngramCommand(clean)
return (clean == "/opt/homebrew/bin/engram" || clean == "/usr/local/bin/engram" || clean == "/home/linuxbrew/.linuxbrew/bin/engram") && isEngramCommand(clean)
}

// resolveProfileAssignments builds the []codex.ProfileAssignment slice used
Expand Down
Loading
Loading