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
13 changes: 11 additions & 2 deletions Sources/engram/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func parseOptions(_ args: [String]) -> (positional: [String], options: [String:
var positional: [String] = []
var options: [String: String] = [:]
var flags: Set<String> = []
let valued: Set<String> = ["--title", "--tags", "--source", "--limit", "--content", "--verifiability", "--check-anchor", "--confidence", "--reason", "--since"]
let valued: Set<String> = ["--title", "--tags", "--source", "--limit", "--content", "--text", "--verifiability", "--check-anchor", "--confidence", "--reason", "--since"]
var index = 0
while index < args.count {
let arg = args[index]
Expand Down Expand Up @@ -174,7 +174,16 @@ do {

switch command {
case "store":
guard let content = positional.first else { fail("store: missing content") }
// Accept content positionally or via --content/--text. The flag forms are
// common (and natural) model guesses — `update` uses --content, so the
// model often reaches for `engram store --content "…"`. Honoring them here
// turns a silent "missing content" failure into a successful store.
let storeContent = positional.first ?? options["--content"] ?? options["--text"]
guard let content = storeContent,
!content.trimmingCharacters(in: .whitespaces).isEmpty else {
fail("store: missing content. Pass it positionally — engram store \"<text>\" — "
+ "or with --content/--text \"<text>\".")
}
let tags = options["--tags"]?.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) } ?? []
let source = options["--source"]
let verifiability = options["--verifiability"].flatMap(Verifiability.init(rawValue:))
Expand Down
36 changes: 36 additions & 0 deletions Tests/EngramCoreTests/CLISmokeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,42 @@ private func tempDB() -> URL {
#expect(fetchOut.contains("Paris"), "fetch output must include the stored content")
}

/// The store argument forms the model actually reaches for must all succeed:
/// positional content, `--content`, and `--text`. Historically only positional
/// worked, so `engram store --content "…"` failed silently with "missing
/// content" — a real cause of un-saved memories (see the store-robustness work).
@Test func cliStoreAcceptsContentAndTextFlags() throws {
for (label, args) in [
("positional", ["store", "alpha fact about Paris"]),
("--content", ["store", "--content", "beta fact about Paris"]),
("--text", ["store", "--text", "gamma fact about Paris"]),
] {
let db = tempDB()
defer { try? FileManager.default.removeItem(at: db) }

let (out, exit) = try engram(args, db: db)
guard exit != -1 else { return } // binary not built — skip
#expect(exit == 0, "engram \(label) store must exit 0 (got: \(out))")
#expect(out.hasPrefix("stored "), "\(label) store must report success")

let (fetchOut, _) = try engram(["fetch", "Paris", "--limit", "3"], db: db)
#expect(fetchOut.contains("Paris"), "\(label): stored content must be fetchable")
}
}

/// A store with no content (any form) fails non-zero with an actionable message
/// that points at the correct invocation — not a bare "missing content".
@Test func cliStoreWithoutContentFailsWithHelpfulMessage() throws {
let db = tempDB()
defer { try? FileManager.default.removeItem(at: db) }

let (out, exit) = try engram(["store"], db: db)
guard exit != -1 else { return } // binary not built — skip
#expect(exit != 0, "a content-less store must fail")
#expect(out.contains("--content") && out.contains("engram store"),
"the error must show the correct invocation forms (got: \(out))")
}

/// `engram stats` exits 0 and emits JSON with a numeric totalActive field.
@Test func cliStatsJSON() throws {
let db = tempDB()
Expand Down
Loading