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
6 changes: 6 additions & 0 deletions .changeset/kilo-memory-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@kilocode/cli": minor
"@kilocode/sdk": minor
---

Add opt-in project memory commands, tools, automatic capture, and public API support.
5 changes: 5 additions & 0 deletions .changeset/kilo-memory-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-memory": minor
---

Add the project memory storage, indexing, recall, and capture safety foundation.
17 changes: 17 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions packages/kilo-memory/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-memory",
"version": "7.3.45",
"type": "module",
"license": "MIT",
"description": "Project memory storage, indexing, recall, and command helpers for Kilo Code",
"keywords": [
"kilo",
"kilocode",
"memory",
"agent"
],
"exports": {
".": "./src/index.ts",
"./capture": "./src/capture/capture.ts",
"./commands": "./src/commands.ts",
"./digest": "./src/capture/digest.ts",
"./memory": "./src/memory.ts",
"./ops": "./src/capture/ops.ts",
"./paths": "./src/storage/paths.ts",
"./recall": "./src/recall/recall.ts",
"./redact": "./src/capture/redact.ts",
"./schema": "./src/schema.ts",
"./shared": "./src/recall/shared.ts",
"./store": "./src/storage/store.ts"
},
"files": [
"dist",
"src"
],
"scripts": {
"typecheck": "tsgo --noEmit",
"build": "tsc",
"test": "bun test --timeout 30000",
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml"
},
"dependencies": {
"zod": "catalog:"
},
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
},
"peerDependencies": {}
}
7 changes: 7 additions & 0 deletions packages/kilo-memory/src/capture/capture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Public entry point (`@kilocode/kilo-memory/capture`). Implementation lives in focused siblings;
// this barrel keeps the import surface stable.
export * from "./parse"
export * from "./diff"
export * from "./digest-text"
export * from "./plan"
export * from "./outcome"
29 changes: 29 additions & 0 deletions packages/kilo-memory/src/capture/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type CaptureDiff = {
file?: string
status?: string
additions: number
deletions: number
}

const durable =
/(^|\/)(AGENTS\.md|README(?:\.[^/]*)?|docs?\/.+|package\.json|bun\.lock|pnpm-lock\.yaml|package-lock\.json|turbo\.json|tsconfig[^/]*\.json|vite\.config|eslint|biome|prettier|kilo\.json|\.kilo\/.+|[^/]*(test|spec|config|command|agent|workflow)[^/]*\.(ts|tsx|js|json|md|yml|yaml))$/i

export function hasDurableDiff(diffs: Pick<CaptureDiff, "file" | "additions" | "deletions">[]) {
return diffs.some((item) => {
const file = item.file ?? ""
if (!file) return false
if (durable.test(file)) return true
return item.additions + item.deletions >= 20 && /\.(md|json|ya?ml|toml|ts|tsx|js)$/.test(file)
})
}

export function summarizeDiffs(diffs: Pick<CaptureDiff, "file" | "status" | "additions" | "deletions">[]) {
return diffs
.filter((item) => item.file)
.slice(0, 20)
.map((item) => {
const status = item.status ?? "modified"
return `${status} ${item.file} +${item.additions} -${item.deletions}`
})
.join("\n")
}
51 changes: 51 additions & 0 deletions packages/kilo-memory/src/capture/digest-text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { MemoryDigest } from "./digest"
import { MemoryRedact } from "./redact"
import { MemoryShared } from "../recall/shared"
import type { CaptureDigest } from "./parse"

export function cap(input: string, max: number) {
if (Buffer.byteLength(input) <= max) return input
const chars: string[] = []
let bytes = 0
for (const char of input) {
const size = Buffer.byteLength(char)
if (bytes + size > max) break
chars.push(char)
bytes += size
}
return chars.join("")
}

function body(input: string | undefined, fallback = "(empty)") {
const text = MemoryRedact.text(input?.trim().replaceAll("```", "'''") ?? "")
return text || fallback
}

export function evidence(sections: { title: string; body?: string }[]) {
return [
"```kilo-memory-evidence-v1",
...sections.flatMap((section) => [`## ${section.title}`, body(section.body)]),
"```",
].join("\n")
}

export function summarize(input: { user: string; assistant: string; max: number }) {
const user = MemoryShared.brief(MemoryRedact.text(input.user), Math.max(24, Math.floor(input.max * 0.45)))
const assistant = MemoryShared.brief(MemoryRedact.text(input.assistant), Math.max(24, Math.floor(input.max * 0.45)))
const text = [user ? `User: ${user}` : "", assistant ? `Result: ${assistant}` : ""].filter(Boolean).join(" ")
return MemoryShared.brief(text, input.max)
}

export function fallbackDigest(input: { prior?: string; summary: string; max: number }) {
if (!input.prior?.trim()) return MemoryShared.brief(input.summary, input.max)
const prior = MemoryShared.brief(input.prior ?? "", Math.max(0, Math.floor(input.max * 0.55)))
const latest = MemoryShared.brief(input.summary, Math.max(0, input.max - prior.length - 9))
return MemoryShared.brief([prior, latest ? `Latest: ${latest}` : ""].filter(Boolean).join(" "), input.max)
}

export function parseDigest(input: CaptureDigest, fallback: string, max: number) {
const summary = MemoryShared.brief(input.summary.trim() || fallback, max)
const topic = MemoryShared.brief(input.topic.trim() || summary.split(/[.;:]/)[0] || summary, 80)
if (MemoryDigest.empty({ topic, summary })) return { topic: "", summary: "" }
return { topic, summary }
}
15 changes: 15 additions & 0 deletions packages/kilo-memory/src/capture/digest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export namespace MemoryDigest {
export type Summary = {
topic?: string
summary: string
}

function blank(input: string | undefined) {
return !input?.trim()
}

export function empty(input: string | Summary) {
if (typeof input === "string") return blank(input)
return blank(input.topic) && blank(input.summary)
}
}
Loading
Loading