diff options
Diffstat (limited to 'makima/src/daemon')
| -rw-r--r-- | makima/src/daemon/api/directive.rs | 200 | ||||
| -rw-r--r-- | makima/src/daemon/cli/directive.rs | 48 | ||||
| -rw-r--r-- | makima/src/daemon/cli/mod.rs | 18 | ||||
| -rw-r--r-- | makima/src/daemon/skills/directive.md | 80 |
4 files changed, 346 insertions, 0 deletions
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs index 5886766..fcc2ca5 100644 --- a/makima/src/daemon/api/directive.rs +++ b/makima/src/daemon/api/directive.rs @@ -30,6 +30,54 @@ pub struct UpdateStepDepsRequest { pub depends_on: Vec<Uuid>, } +/// Percent-encode a string for use as a URL path segment. +/// +/// Encodes all characters except unreserved characters (alphanumeric, `-`, `.`, `_`, `~`). +fn percent_encode_path(s: &str) -> String { + let mut encoded = String::with_capacity(s.len()); + for byte in s.bytes() { + match byte { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => { + encoded.push(byte as char); + } + _ => { + encoded.push_str(&format!("%{:02X}", byte)); + } + } + } + encoded +} + +/// Request body for setting a single memory entry. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SetMemoryRequest { + pub key: String, + pub value: String, +} + +/// A single entry within a batch set request. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchMemoryEntry { + pub key: String, + pub value: String, +} + +/// Request body for setting multiple memory entries at once. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchSetMemoryRequest { + pub entries: Vec<BatchMemoryEntry>, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MemorySetRequest { + pub value: String, +} + + impl ApiClient { /// List all directives. pub async fn list_directives(&self) -> Result<JsonValue, ApiError> { @@ -145,6 +193,158 @@ impl ApiClient { let req = UpdateDirectiveMetadataRequest { pr_url, pr_branch }; self.put(&format!("/api/v1/directives/{}", directive_id), &req).await } + + // ── Directive Memory ────────────────────────────────────────────── + + /// List all memory entries for a directive. + pub async fn list_memories(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/directives/{}/memory", directive_id)) + .await + } + + /// Get a single memory entry by key. + pub async fn get_memory( + &self, + directive_id: Uuid, + key: &str, + ) -> Result<JsonValue, ApiError> { + self.get(&format!( + "/api/v1/directives/{}/memory/{}", + directive_id, + percent_encode_path(key) + )) + .await + } + + /// Set (create or update) a single memory entry. + pub async fn set_memory( + &self, + directive_id: Uuid, + key: &str, + value: &str, + ) -> Result<JsonValue, ApiError> { + let req = SetMemoryRequest { + key: key.to_string(), + value: value.to_string(), + }; + self.put(&format!("/api/v1/directives/{}/memory", directive_id), &req) + .await + } + + /// Set multiple memory entries in a single request. + pub async fn batch_set_memories( + &self, + directive_id: Uuid, + entries: Vec<(String, String)>, + ) -> Result<JsonValue, ApiError> { + let req = BatchSetMemoryRequest { + entries: entries + .into_iter() + .map(|(key, value)| BatchMemoryEntry { key, value }) + .collect(), + }; + self.post( + &format!("/api/v1/directives/{}/memory/batch", directive_id), + &req, + ) + .await + } + + /// Delete a single memory entry by key. + pub async fn delete_memory( + &self, + directive_id: Uuid, + key: &str, + ) -> Result<(), ApiError> { + self.delete(&format!( + "/api/v1/directives/{}/memory/{}", + directive_id, + percent_encode_path(key) + )) + .await + } + + /// Clear all memory entries for a directive. + pub async fn clear_memories(&self, directive_id: Uuid) -> Result<(), ApiError> { + self.delete(&format!("/api/v1/directives/{}/memory", directive_id)) + .await + } + + // ── CLI-facing Directive Memory aliases ────────────────────────── + + /// Set a memory key-value pair for a directive (CLI-facing). + pub async fn directive_memory_set( + &self, + directive_id: Uuid, + key: &str, + value: &str, + ) -> Result<JsonValue, ApiError> { + let req = MemorySetRequest { + value: value.to_string(), + }; + self.put( + &format!("/api/v1/directives/{}/memory/{}", directive_id, key), + &req, + ) + .await + } + + /// Get a memory value by key for a directive (CLI-facing). + pub async fn directive_memory_get( + &self, + directive_id: Uuid, + key: &str, + ) -> Result<JsonValue, ApiError> { + self.get(&format!( + "/api/v1/directives/{}/memory/{}", + directive_id, key + )) + .await + } + + /// List all memory key-value pairs for a directive (CLI-facing). + pub async fn directive_memory_list( + &self, + directive_id: Uuid, + ) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/directives/{}/memory", directive_id)) + .await + } + + /// Delete a memory key for a directive (CLI-facing). + pub async fn directive_memory_delete( + &self, + directive_id: Uuid, + key: &str, + ) -> Result<(), ApiError> { + self.delete(&format!( + "/api/v1/directives/{}/memory/{}", + directive_id, key + )) + .await + } + + /// Clear all memory for a directive (CLI-facing). + pub async fn directive_memory_clear( + &self, + directive_id: Uuid, + ) -> Result<(), ApiError> { + self.delete(&format!("/api/v1/directives/{}/memory", directive_id)) + .await + } + + /// Batch set multiple memory key-value pairs for a directive (CLI-facing). + pub async fn directive_memory_batch_set( + &self, + directive_id: Uuid, + entries: serde_json::Value, + ) -> Result<JsonValue, ApiError> { + self.post( + &format!("/api/v1/directives/{}/memory/batch", directive_id), + &entries, + ) + .await + } } #[derive(Serialize)] diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs index 2e6ac1d..8eded77 100644 --- a/makima/src/daemon/cli/directive.rs +++ b/makima/src/daemon/cli/directive.rs @@ -125,3 +125,51 @@ pub struct UpdateArgs { #[arg(long)] pub pr_branch: Option<String>, } + +/// Arguments for memory-set command. +#[derive(Args, Debug)] +pub struct MemorySetArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Memory key + pub key: String, + + /// Memory value + pub value: String, +} + +/// Arguments for memory-get command. +#[derive(Args, Debug)] +pub struct MemoryGetArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Memory key + pub key: String, +} + +/// Arguments for memory-list command (uses DirectiveArgs directly). + +/// Arguments for memory-delete command. +#[derive(Args, Debug)] +pub struct MemoryDeleteArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Memory key to delete + pub key: String, +} + +/// Arguments for memory-clear command (uses DirectiveArgs directly). + +/// Arguments for memory-batch-set command. +#[derive(Args, Debug)] +pub struct MemoryBatchSetArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// JSON object of key-value pairs: {"key1":"value1","key2":"value2"} + #[arg(long)] + pub json: String, +} diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs index bcaaa70..a78e5f8 100644 --- a/makima/src/daemon/cli/mod.rs +++ b/makima/src/daemon/cli/mod.rs @@ -249,6 +249,24 @@ pub enum DirectiveCommand { /// Update directive metadata (PR URL, etc.) Update(directive::UpdateArgs), + + /// Set a memory key-value pair for the directive + MemorySet(directive::MemorySetArgs), + + /// Get a memory value by key + MemoryGet(directive::MemoryGetArgs), + + /// List all memory key-value pairs + MemoryList(DirectiveArgs), + + /// Delete a memory key + MemoryDelete(directive::MemoryDeleteArgs), + + /// Clear all memory for the directive + MemoryClear(DirectiveArgs), + + /// Batch set multiple memory key-value pairs from JSON + MemoryBatchSet(directive::MemoryBatchSetArgs), } impl Cli { diff --git a/makima/src/daemon/skills/directive.md b/makima/src/daemon/skills/directive.md index 7c55cf8..68d9277 100644 --- a/makima/src/daemon/skills/directive.md +++ b/makima/src/daemon/skills/directive.md @@ -76,6 +76,86 @@ Updates the goal and bumps `goalUpdatedAt`. If the directive is `idle`, it react makima directive pause ``` +## Memory Commands + +Directives have an optional key-value memory system that persists across steps and planning cycles. Use memory to share context, decisions, and learned information between steps — so downstream tasks don't need to re-discover what earlier steps already figured out. + +### Set a Memory Entry +```bash +makima directive memory-set <key> <value> +``` +Stores a key-value pair in the directive's memory. If the key already exists, the value is overwritten. Keys are strings; values are strings (use JSON encoding for structured data). + +**Example:** +```bash +makima directive memory-set "db_schema_version" "3" +makima directive memory-set "auth_pattern" "JWT with refresh tokens stored in httpOnly cookies" +makima directive memory-set "api_base_path" "/api/v2" +``` + +### Get a Memory Entry +```bash +makima directive memory-get <key> +``` +Retrieves the value for a specific key. Returns the value if found, or an error if the key does not exist. + +**Example:** +```bash +makima directive memory-get "db_schema_version" +``` + +### List All Memory Entries +```bash +makima directive memory-list +``` +Returns all key-value pairs stored in the directive's memory. Useful for understanding what context is available before starting work on a step. + +### Delete a Memory Entry +```bash +makima directive memory-delete <key> +``` +Removes a single key-value pair from memory. + +**Example:** +```bash +makima directive memory-delete "deprecated_config_key" +``` + +### Clear All Memory +```bash +makima directive memory-clear +``` +Removes **all** key-value pairs from the directive's memory. Use with caution — this is irreversible. + +### Batch Set Memory Entries +```bash +makima directive memory-batch-set --json '{"key1": "value1", "key2": "value2"}' +``` +Sets multiple key-value pairs in a single operation. Existing keys are overwritten; keys not mentioned are left unchanged. + +**Example:** +```bash +makima directive memory-batch-set --json '{"framework": "axum", "orm": "sqlx", "test_runner": "cargo test"}' +``` + +## Using Memory Effectively + +### When to Write Memory +- **During planning**: Record architectural decisions, technology choices, and file layout patterns +- **After step completion**: Save discovered information (e.g., generated IDs, API endpoints, schema details) +- **When context matters**: Store anything a downstream step would need to avoid re-exploring the codebase + +### When to Read Memory +- **At step start**: Check `memory-list` to see what context previous steps have provided +- **Before making decisions**: Check if an earlier step already made a relevant architectural choice +- **During re-planning**: Read memory to understand what was learned in previous iterations + +### Best Practices +- Use descriptive, namespaced keys (e.g., `auth.strategy`, `db.migration_count`, `api.base_url`) +- Store concise but complete values — enough for another task to act on without guessing +- Clean up stale entries when the directive's goal changes significantly +- Use `memory-batch-set` when recording multiple related decisions at once + ## Orchestration Workflow ### Initial Setup |
