summaryrefslogtreecommitdiff
path: root/makima/src/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/daemon')
-rw-r--r--makima/src/daemon/api/directive.rs200
-rw-r--r--makima/src/daemon/cli/directive.rs48
-rw-r--r--makima/src/daemon/cli/mod.rs18
-rw-r--r--makima/src/daemon/skills/directive.md80
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