Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/cli/connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { adapter as hermes } from "./hermes.js";
import { adapter as openclaw } from "./openclaw.js";
import { adapter as openhuman } from "./openhuman.js";
import { adapter as pi } from "./pi.js";
import { adapter as kilo } from "./kilo.js";

export const ADAPTERS: readonly ConnectAdapter[] = [
claudeCode,
Expand All @@ -19,6 +20,7 @@ export const ADAPTERS: readonly ConnectAdapter[] = [
hermes,
pi,
openhuman,
kilo,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the test expectations for adapter count and list
rg -n "ships exactly the 8 agents" test/cli-connect.test.ts
rg -n 'ADAPTERS\.length.*toBe' test/cli-connect.test.ts
rg -A10 'knownAgents.*toEqual' test/cli-connect.test.ts

Repository: rohitg00/agentmemory

Length of output: 391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the ADAPTERS definition around the referenced line in source
echo "---- src/cli/connect/index.ts (around line 1-80) ----"
nl -ba src/cli/connect/index.ts | sed -n '1,120p'

echo
echo "---- test/cli-connect.test.ts (around key expectations) ----"
nl -ba test/cli-connect.test.ts | sed -n '1,120p'

# Also show any "kilo" mentions
echo
echo "---- occurrences of 'kilo' ----"
rg -n "\"kilo\"|kilo" src/cli/connect/index.ts test/cli-connect.test.ts || true

Repository: rohitg00/agentmemory

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/cli/connect/index.ts (around line 1-120) ----"
nl -ba src/cli/connect/index.ts | sed -n '1,120p'

echo
echo "---- test/cli-connect.test.ts (around line 1-120) ----"
nl -ba test/cli-connect.test.ts | sed -n '1,140p'

echo
echo "---- occurrences of 'kilo' ----"
rg -n "\"kilo\"|kilo" src/cli/connect/index.ts test/cli-connect.test.ts || true

Repository: rohitg00/agentmemory

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the ADAPTERS definition around the referenced line in source
echo "---- src/cli/connect/index.ts (around line 1-120) ----"
nl -ba src/cli/connect/index.ts | sed -n '1,120p'

echo
echo "---- test/cli-connect.test.ts (around line 1-140) ----"
nl -ba test/cli-connect.test.ts | sed -n '1,140p'

echo
echo "---- occurrences of 'kilo' ----"
rg -n "\"kilo\"|kilo" src/cli/connect/index.ts test/cli-connect.test.ts || true

Repository: rohitg00/agentmemory

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/cli/connect/index.ts (lines 1-160) ----"
awk '{printf "%5d\t%s\n", NR, $0}' src/cli/connect/index.ts | sed -n '1,160p'

echo
echo "---- test/cli-connect.test.ts (lines 1-200) ----"
awk '{printf "%5d\t%s\n", NR, $0}' test/cli-connect.test.ts | sed -n '1,200p'

echo
echo "---- occurrences of 'kilo' ----"
rg -n "\"kilo\"|kilo" src/cli/connect/index.ts test/cli-connect.test.ts || true

Repository: rohitg00/agentmemory

Length of output: 14748


Update test/cli-connect.test.ts to include the new kilo adapter (expect 9 adapters)
src/cli/connect/index.ts now registers kilo in ADAPTERS, but the test still expects exactly 8 adapters and a knownAgents() list that excludes "kilo".

🧪 Proposed test fix
-  it("ships exactly the 8 agents specified by the spec", () => {
+  it("ships exactly the 9 agents specified by the spec", () => {
     expect(knownAgents().sort()).toEqual(
       [
         "claude-code",
         "codex",
         "cursor",
         "gemini-cli",
         "hermes",
+        "kilo",
         "openclaw",
         "openhuman",
         "pi",
       ].sort(),
     );
-    expect(ADAPTERS.length).toBe(8);
+    expect(ADAPTERS.length).toBe(9);
   });
🤖 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 `@src/cli/connect/index.ts` at line 23, Tests expect 8 adapters but
src/cli/connect/index.ts added "kilo" to ADAPTERS; update
test/cli-connect.test.ts to assert 9 adapters and include "kilo" in the
knownAgents() expected list (or adjust the assertion that checks for exclusion
of "kilo"), ensuring the test references the ADAPTERS count and knownAgents()
output accordingly.

];

export function resolveAdapter(name: string): ConnectAdapter | null {
Expand Down
112 changes: 112 additions & 0 deletions src/cli/connect/kilo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { ConnectAdapter, ConnectOptions, ConnectResult } from "./types.js";
import {
backupFile,
logAlreadyWired,
logBackup,
logInstalled,
readJsonSafe,
writeJsonAtomic,
} from "./util.js";

type McpEntry = {
type: string;
command: string[];
environment?: Record<string, string>;
enabled?: boolean;
};

type KiloConfig = {
mcp?: Record<string, McpEntry>;
[key: string]: unknown;
};

// Env values use ${VAR} expansion so the wired MCP entry inherits
// AGENTMEMORY_URL / AGENTMEMORY_SECRET from the user's shell.
const AGENTMEMORY_MCP_BLOCK: McpEntry = {
type: "local",
command: ["npx", "-y", "@agentmemory/mcp"],
environment: {
AGENTMEMORY_URL: "${AGENTMEMORY_URL}",
AGENTMEMORY_SECRET: "${AGENTMEMORY_SECRET}",
},
enabled: true,
};

function entryMatches(entry: unknown): boolean {
if (!entry || typeof entry !== "object") return false;
const e = entry as Record<string, unknown>;
const cmd = Array.isArray(e["command"]) ? (e["command"] as string[]) : [];
return cmd.some((c) => c.includes("@agentmemory/mcp"));
}

const configDir = join(homedir(), ".config", "kilo");
const configFiles = ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if a JSONC parser is available in package.json dependencies
cat package.json | jq '.dependencies, .devDependencies' | grep -i 'jsonc\|strip-json'

Repository: rohitg00/agentmemory

Length of output: 46


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the referenced file and relevant helpers
sed -n '1,140p' src/cli/connect/kilo.ts | cat -n

# 2) Locate the implementations of readJsonSafe / writeJsonAtomic in the repo
echo "---- rg readJsonSafe ----"
rg -n "readJsonSafe" -S src

echo "---- rg writeJsonAtomic ----"
rg -n "writeJsonAtomic" -S src

# 3) Re-check package.json for JSONC-related deps
echo "---- package.json deps matching jsonc/strip-json-comments ----"
cat package.json | jq -r '
  ([.dependencies, .devDependencies] | add | to_entries | map(.key) | .[])
  ' 2>/dev/null | rg -i 'jsonc|strip-json'

Repository: rohitg00/agentmemory

Length of output: 6083


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect JSON helpers
sed -n '1,120p' src/cli/connect/util.ts | cat -n

# Search package.json for jsonc/strip-json-comments mentions
echo "---- rg package.json for jsonc/strip-json-comments ----"
rg -n "jsonc|strip-json-comments" package.json || true

Repository: rohitg00/agentmemory

Length of output: 3032


Critical: kilo.jsonc/opencode.jsonc configs are parsed as strict JSON, so existing config can be silently overwritten

  • src/cli/connect/kilo.ts searches ["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"], but it reads both with readJsonSafe, which uses JSON.parse (src/cli/connect/util.ts), so JSONC (comments/trailing commas) returns null.
  • When parsing returns null, kilo.ts treats the config as empty and overwrites it via writeJsonAtomic(configPath, next), discarding existing fields (backup is created, but the live config is clobbered).

Fix options

  • Make readJsonSafe JSONC-aware for .jsonc (recommended: use jsonc-parser or json5 so comments + trailing commas are handled), or
  • Remove .jsonc from configFiles to avoid clobbering unsupported formats.
🤖 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 `@src/cli/connect/kilo.ts` at line 46, The config discovery uses configFiles
(["kilo.jsonc", "kilo.json", "opencode.jsonc", "opencode.json"]) and reads them
via readJsonSafe, but readJsonSafe uses strict JSON.parse so .jsonc files with
comments/trailing commas return null and are treated as empty, then overwritten
by writeJsonAtomic; fix by either making readJsonSafe JSONC-aware (preferable) —
update readJsonSafe to detect .jsonc (or use jsonc-parser/json5) and parse
accordingly so it returns the actual object for kilo.jsonc/opencode.jsonc — or
remove ".jsonc" entries from configFiles to avoid parsing clobbering; ensure
references to configFiles, readJsonSafe and writeJsonAtomic are updated
consistently and preserve backup behavior when migrating formats.


function findConfigFile(): string | null {
for (const file of configFiles) {
const fullPath = join(configDir, file);
if (existsSync(fullPath)) return fullPath;
}
return null;
}

export const adapter: ConnectAdapter = {
name: "kilo",
displayName: "Kilo",
docs: "https://github.com/rohitg00/agentmemory/tree/main/integrations/kilo",
protocolNote: "→ Using MCP. Kilo implements memory hooks natively in its codebase.",

detect(): boolean {
return existsSync(configDir) || findConfigFile() !== null;
},

async install(opts: ConnectOptions): Promise<ConnectResult> {
const configPath = findConfigFile() ?? join(configDir, "kilo.json");
const existing = readJsonSafe<KiloConfig>(configPath);
const next: KiloConfig = existing ? { ...existing } : {};
const mcp: Record<string, McpEntry> = {
...((next.mcp as Record<string, McpEntry>) ?? {}),
};

const alreadyHas = entryMatches(mcp["agentmemory"]);
if (alreadyHas && !opts.force) {
logAlreadyWired("Kilo", configPath);
return { kind: "already-wired", mutatedPath: configPath };
}

if (opts.dryRun) {
console.log(
`[dry-run] Would ${alreadyHas ? "overwrite" : "add"} mcp.agentmemory in ${configPath}`,
);
return { kind: "installed", mutatedPath: configPath };
}

let backupPath: string | undefined;
if (existsSync(configPath)) {
backupPath = backupFile(configPath, "kilo");
logBackup(backupPath);
}

mcp["agentmemory"] = AGENTMEMORY_MCP_BLOCK;
next.mcp = mcp;
writeJsonAtomic(configPath, next);

const verify = readJsonSafe<KiloConfig>(configPath);
if (!entryMatches(verify?.mcp?.["agentmemory"])) {
console.error(
`Verification failed: ${configPath} did not contain mcp.agentmemory after write.`,
);
return { kind: "skipped", reason: "verification-failed" };
}

logInstalled("Kilo", configPath);
return {
kind: "installed",
mutatedPath: configPath,
...(backupPath !== undefined && { backupPath }),
};
},
};