From 90ff99b4c62ed95565ce47518b0c7575e3bb6ec8 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 11:49:05 -0400 Subject: [PATCH 1/8] feat: read/write btw memory --- .gitignore | 3 ++ R/tool-memory.R | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ R/utils.R | 26 +++++++++ 3 files changed, 166 insertions(+) create mode 100644 R/tool-memory.R diff --git a/.gitignore b/.gitignore index 457525ee..4308aeac 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ .DS_Store .quarto docs +btw-memory.yaml +/_* +/.* diff --git a/R/tool-memory.R b/R/tool-memory.R new file mode 100644 index 00000000..cdbee33f --- /dev/null +++ b/R/tool-memory.R @@ -0,0 +1,137 @@ +#' Read btw memory YAML file +#' +#' Reads the btw-memory.yaml file and returns a btw_memory object. +#' If the file doesn't exist, returns an empty btw_memory structure. +#' +#' @param path Path to the YAML file. Defaults to "btw-memory.yaml" +#' +#' @return A list with class "btw_memory" containing `project_context` and +#' `data_sources` +#' +#' @noRd +read_btw_memory_yaml <- function(path = NULL) { + path <- path_find_btw_memory(path) + + as_btw_memory(yaml::read_yaml(path)) +} + +as_btw_memory <- function(x) { + structure(x, class = "btw_memory") +} + +#' Write btw memory YAML file +#' +#' Writes a btw_memory object to a YAML file. +#' +#' @param x A btw_memory object (list with project_context and data_sources) +#' @param path Path to the YAML file. Defaults to "btw-memory.yaml" +#' @noRd +write_btw_memory_yaml <- function(x, path = NULL) { + if (!inherits(x, "btw_memory")) { + stop("Object must have class 'btw_memory'") + } + + data <- unclass(x) + path <- path_find_btw_memory(path, must_exist = FALSE) + + if (identical(compact(data), list())) { + cli::cli_inform( + "No memory to write, creating an empty file at {.path {path}}." + ) + fs::file_touch(path) + return(invisible(x)) + } + + # Project Context ---- + if (!is.null(data$project_context)) { + # problem_description should be a single string + if (!is.null(data$project_context$problem_description)) { + data$project_context$problem_description <- paste( + data$project_context$problem_description, + collapse = " " + ) + } + } + + # Data Sources ---- + if (!is.null(data$data_sources) && length(data$data_sources) > 0) { + for (i in seq_along(data$data_sources)) { + ds <- data$data_sources[[i]] + + # These should be single strings + for (field in c("name", "description", "source", "code")) { + if (!is.null(ds[[field]])) { + ds[[field]] <- paste(ds[[field]], collapse = "\n") + } + } + + # notes should be an array of strings + if (!is.null(ds$notes)) { + ds$notes <- as.character(ds$notes) + } + + # Variables ---- + if (!is.null(ds$variables) && length(ds$variables) > 0) { + for (j in seq_along(ds$variables)) { + var <- ds$variables[[j]] + + # name should be a single string + if (!is.null(var$name)) { + var$name <- paste(var$name, collapse = "") + } + + # notes should be an array of strings + if (!is.null(var$notes)) { + var$notes <- as.character(var$notes) + } + + ds$variables[[j]] <- var + } + } + + data$data_sources[[i]] <- ds + } + } + + yaml::write_yaml(data, path) + + invisible(x) +} + + +path_find_btw_memory <- function(path = NULL, must_exist = TRUE) { + check_string(path, allow_null = TRUE, call = caller_env()) + check_bool(must_exist, call = caller_env()) + + if (!is.null(path)) { + if (must_exist && !file.exists(path)) { + cli::cli_abort("File '{file}' does not exist.") + } + return(path) + } + + root <- path_find_project_root() + + if (is.null(root)) { + cli::cli_abort(c( + "Could not find the project root. Are you in a project directory?", + "i" = "The project root is typically the directory containing a {.file DESCRIPTION} file, a {.file .git} directory, or a {.field VS Code} or {.field RStudio} project.", + "i" = "Create an empty {.file btw-memory.yaml} file in the project root to store your memory." + )) + } + + paths <- fs::path(root, c("btw-memory.yaml", "btw-memory.yml")) + paths <- paths[file.exists(paths)] + + if (length(paths) > 0) { + return(paths[1]) + } + + if (must_exist) { + cli::cli_abort( + "No {.pkg btw} memory file found in the project directory {.path {root}}." + ) + } + + fs::path(root, "btw-memory.yaml") +} diff --git a/R/utils.R b/R/utils.R index 374dfb90..84abd7fa 100644 --- a/R/utils.R +++ b/R/utils.R @@ -77,6 +77,32 @@ path_find_in_project <- function(filename, dir = getwd()) { path_find_in_project(filename, dirname(dir)) } +path_find_project_root <- function(dir = getwd()) { + root_files <- c( + "DESCRIPTION", + ".git", + ".vscode", + ".here", + "btw.md", + "btw-memory.yaml", + "btw-memory.yml" + ) + + if (any(file.exists(file.path(dir, root_files)))) { + return(normalizePath(dir)) + } + + if (length(dir(pattern = ".[.]Rproj$")) > 0) { + return(normalizePath(dir)) + } + + if (dirname(dir) == dir) { + return(NULL) + } + + path_find_project_root(dirname(dir)) +} + local_reproducible_output <- function( width = 80L, max.print = 100, From 2d4d581ecaf422bbfa848748f3c2a94349bd9a8b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 12:42:50 -0400 Subject: [PATCH 2/8] feat: project context memory --- DESCRIPTION | 4 +- NAMESPACE | 3 + R/tool-memory.R | 267 +++++++++++++++++- man/btw_tool_memory_project_context_add.Rd | 36 +++ man/btw_tool_memory_project_context_read.Rd | 32 +++ ...btw_tool_memory_project_context_replace.Rd | 34 +++ man/btw_tools.Rd | 61 ++++ 7 files changed, 434 insertions(+), 3 deletions(-) create mode 100644 man/btw_tool_memory_project_context_add.Rd create mode 100644 man/btw_tool_memory_project_context_read.Rd create mode 100644 man/btw_tool_memory_project_context_replace.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 87a0d21f..b2f58726 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -36,7 +36,8 @@ Imports: tibble, utils, withr, - xml2 + xml2, + yaml Suggests: bslib (>= 0.7.0), gh, @@ -66,6 +67,7 @@ Collate: 'tool-docs.R' 'tool-environment.R' 'tool-files.R' + 'tool-memory.R' 'tool-rstudioapi.R' 'tool-search-packages.R' 'tool-session-package-installed.R' diff --git a/NAMESPACE b/NAMESPACE index 9c921c2d..a120749c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -33,6 +33,9 @@ export(btw_tool_env_describe_environment) export(btw_tool_files_list_files) export(btw_tool_files_read_text_file) export(btw_tool_ide_read_current_editor) +export(btw_tool_memory_project_context_add) +export(btw_tool_memory_project_context_read) +export(btw_tool_memory_project_context_replace) export(btw_tool_search_package_info) export(btw_tool_search_packages) export(btw_tool_session_check_package_installed) diff --git a/R/tool-memory.R b/R/tool-memory.R index cdbee33f..65104ba9 100644 --- a/R/tool-memory.R +++ b/R/tool-memory.R @@ -1,3 +1,266 @@ +#' Tool: Project Context Memory - Add context +#' +#' Appends content to a specific key in the project context section of the +#' btw-memory.yaml file. If the key doesn't exist, it creates it. If it exists +#' and contains an array, the content is appended to the array. +#' +#' @param key Character string specifying which project context key to update. +#' Must be one of: "problem_description", "objectives", "constraints", or +#' "business_context". +#' @param content Character vector of content to add. For "problem_description", +#' only the first element is used and replaces existing content. For other keys, +#' all elements are appended to the existing array. +#' @param ... Ignored, used for future feature expansion and compatibility. +#' @param path Character string specifying the path to the memory file. If +#' `NULL`, uses the default (`btw-memory.yaml` in the project root). +#' +#' @return Invisibly returns the updated memory data +#' +#' @family Memory tools +#' @export +btw_tool_memory_project_context_add <- function( + key, + content, + ..., + path = NULL +) { + key <- arg_match(key, btw_memory_keys_project_context(), ) + check_character(content) + path <- path_find_btw_memory(path, must_exist = FALSE) + + mem <- read_btw_memory_yaml(path) + + if (is.null(mem$project_context)) { + mem$project_context <- list() + } + + if (key == "problem_description") { + mem$project_context[[key]] <- paste( + c(mem$project_context[[key]], content), + collapse = "\n\n" + ) + } else { + mem$project_context[[key]] <- c(mem$project_context[[key]], content) + } + + write_btw_memory_yaml(mem, path) + invisible(mem) +} + +btw_memory_keys_project_context <- function() { + c("problem_description", "objectives", "constraints", "business_context") +} + +.btw_add_to_tools( + "btw_tool_memory_project_context_add", + group = "memory", + tool = function() { + ellmer::tool( + function(key, content, ...) { + btw_tool_memory_project_context_add(key, content) + BtwToolResult("Success.") + }, + .name = "btw_tool_memory_project_context_add", + .description = "Store or append information about the project. + +Use this when the user provides new information that won't change during the project lifecycle about: + +- **problem_description**: The main business problem or research question (replaces existing) +- **objectives**: Specific analysis goals or questions to answer (appends to list) +- **constraints**: Limitations, requirements, or restrictions (appends to list) +- **business_context**: Domain knowledge, organizational background (appends to list) + +**When to use:** User mentions project goals, business requirements, analysis objectives, domain constraints, or background context that should be remembered across sessions. + +**Examples:** +- User says \"I need to predict customer churn\" -> store in `problem_description` as \"Identify customers at risk of churning\" +- User explains \"We need three risk levels\" -> add to `objectives` as \"Segment customers into low, medium, high risk\" +- User states \"We can't use personal data\" -> add to `constraints` as \"No personal data allowed in analysis\" +- User provides background on company goals -> add to `business_context` as \"Recent product changes have affected customer retention\"", + key = ellmer::type_enum( + description = "The project context key to update", + values = btw_memory_keys_project_context() + ), + content = ellmer::type_array( + description = "Content to append to the memory.", + items = ellmer::type_string() + ) + ) + } +) + + +#' Tool: Project Context Memory - Read Context +#' +#' Reads the project context section from the btw memory. Can return the entire +#' project context or a specific set of keys within it. +#' +#' @inheritParams btw_tool_memory_project_context_add +#' +#' @return The requested project context data, or `NULL` if no memory exists +#' for the project context or selected keys yet. +#' +#' @family Memory tools +#' @export +btw_tool_memory_project_context_read <- function(key = NULL, ..., path = NULL) { + path <- path_find_btw_memory(path) + if (!is.null(key)) { + key <- arg_match(key, btw_memory_keys_project_context(), multiple = TRUE) + } + + memory_data <- read_btw_memory_yaml(path) + + if (is.null(memory_data$project_context)) { + return(NULL) + } + + keys <- key %||% names(memory_data$project_context) + memory <- memory_data$project_context[keys] + + res <- c() + for (key in names(memory)) { + title <- switch( + key, + problem_description = "Problem Description", + objectives = "Objectives", + constraints = "Constraints", + business_context = "Business Context" + ) + + value <- memory[[key]] + if (length(value) > 1) { + value <- paste(sprintf("* %s", value), collapse = "\n") + } + + res <- c(res, if (length(res)) "", paste0("### ", title), "", value) + } + + btw_tool_result( + value = paste(res, collapse = "\n"), + data = memory + ) +} + +.btw_add_to_tools( + "btw_tool_memory_project_context_read", + group = "memory", + tool = function() { + ellmer::tool( + function(key, ...) { + if (!is.null(key) && "all" %in% key) { + key <- NULL + } + btw_tool_memory_project_context_read(key) + }, + .name = "btw_tool_memory_project_context_read", + .description = "Read project memory. + +Retrieves stored project context to understand the analysis requirements and provide contextually appropriate responses. + +**When to use:** +- At the start of conversations to understand the project scope +- When making analysis recommendations to align with stated objectives +- When the user asks about project goals or requirements +- Before suggesting approaches to ensure they fit within constraints +- When you need to recall business context to interpret results appropriately + +**Key scenarios:** +- User asks \"What are we trying to accomplish?\" -> read `objectives` +- User wants analysis suggestions -> read constraints and `success_criteria` +- User asks about project background -> read `business_context` +- You need full context for recommendations -> read without specifying key + +**Best practice:** Check project context early in conversations to provide more relevant and targeted assistance.", + key = ellmer::type_array( + description = "The project context keys to read. If empty, reads the entire project context.", + items = ellmer::type_enum( + values = c("all", btw_memory_keys_project_context()), + description = " +* all: All project context keys +* problem_description: High-level description of the business problem to solve +* objectives: Specific analysis goals and questions to answer +* success_criteria: How to measure if the analysis was successful +* constraints: Limitations, requirements, or restrictions for the analysis +* business_context: Domain knowledge, organizational context, or background information" + ), + required = FALSE + ) + ) + } +) + + +#' Tool: Project Context Memory - Replace Context +#' +#' Replaces content in the project context section of the btw memory. +#' +#' @inheritParams btw_tool_memory_project_context_add +#' +#' @return Invisibly returns the updated memory data +#' +#' @family Memory tools +#' @export +btw_tool_memory_project_context_replace <- function( + key, + content, + ..., + path = NULL +) { + path <- path_find_btw_memory(path) + key <- arg_match(key, btw_memory_keys_project_context()) + check_character(content) + + memory_data <- read_btw_memory_yaml(path) + + if (key == "problem_description") { + memory_data$project_context[[key]] <- paste(contents, collapse = "\n\n") + } else { + memory_data$project_context[[key]] <- as.character(contents) + } + + write_btw_memory_yaml(memory_data, path) + invisible(memory_data) +} + +.btw_add_to_tools( + "btw_tool_memory_project_context_replace", + group = "memory", + tool = function() { + ellmer::tool( + function(key, contents, ...) { + btw_tool_memory_project_context_replace(key, contents) + BtwToolResult("Success.") + }, + .name = "btw_tool_memory_project_context_replace", + .description = "Replace or correct existing project memory. + +**When to use:** +- User corrects previously stated objectives or requirements +- Project scope changes significantly +- User provides more accurate problem description +- Need to reorganize or consolidate scattered information +- User explicitly asks to \"update\" or \"change\" stored project information + +**Key scenarios:** +- \"Actually, the goal is prediction, not classification\" -> replace objectives +- \"I was wrong about the constraints\" -> replace constraints +- User provides detailed problem statement to replace vague initial description +- Consolidating multiple business_context entries into organized list + +**Caution:** Use sparingly - prefer `add` for new information. Only use `replace` when existing information is wrong, outdated, or needs reorganization. +You may need to use `btw_tool_memory_project_context_read()` to check current content before replacing.", + key = ellmer::type_enum( + description = "The project context key to update", + values = btw_memory_keys_project_context() + ), + content = ellmer::type_array( + description = "Content to replace the memory.", + items = ellmer::type_string() + ) + ) + } +) + #' Read btw memory YAML file #' #' Reads the btw-memory.yaml file and returns a btw_memory object. @@ -12,7 +275,7 @@ read_btw_memory_yaml <- function(path = NULL) { path <- path_find_btw_memory(path) - as_btw_memory(yaml::read_yaml(path)) + as_btw_memory(yaml::read_yaml(path) %||% list()) } as_btw_memory <- function(x) { @@ -93,7 +356,7 @@ write_btw_memory_yaml <- function(x, path = NULL) { } } - yaml::write_yaml(data, path) + yaml::write_yaml(data, path, indent.mapping.sequence = TRUE, indent = 2) invisible(x) } diff --git a/man/btw_tool_memory_project_context_add.Rd b/man/btw_tool_memory_project_context_add.Rd new file mode 100644 index 00000000..c7a76b3b --- /dev/null +++ b/man/btw_tool_memory_project_context_add.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tool-memory.R +\name{btw_tool_memory_project_context_add} +\alias{btw_tool_memory_project_context_add} +\title{Tool: Project Context Memory - Add context} +\usage{ +btw_tool_memory_project_context_add(key, content, ..., path = NULL) +} +\arguments{ +\item{key}{Character string specifying which project context key to update. +Must be one of: "problem_description", "objectives", "constraints", or +"business_context".} + +\item{content}{Character vector of content to add. For "problem_description", +only the first element is used and replaces existing content. For other keys, +all elements are appended to the existing array.} + +\item{...}{Ignored, used for future feature expansion and compatibility.} + +\item{path}{Character string specifying the path to the memory file. If +\code{NULL}, uses the default (\code{btw-memory.yaml} in the project root).} +} +\value{ +Invisibly returns the updated memory data +} +\description{ +Appends content to a specific key in the project context section of the +btw-memory.yaml file. If the key doesn't exist, it creates it. If it exists +and contains an array, the content is appended to the array. +} +\seealso{ +Other Memory tools: +\code{\link{btw_tool_memory_project_context_read}()}, +\code{\link{btw_tool_memory_project_context_replace}()} +} +\concept{Memory tools} diff --git a/man/btw_tool_memory_project_context_read.Rd b/man/btw_tool_memory_project_context_read.Rd new file mode 100644 index 00000000..412be5a2 --- /dev/null +++ b/man/btw_tool_memory_project_context_read.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tool-memory.R +\name{btw_tool_memory_project_context_read} +\alias{btw_tool_memory_project_context_read} +\title{Tool: Project Context Memory - Read Context} +\usage{ +btw_tool_memory_project_context_read(key = NULL, ..., path = NULL) +} +\arguments{ +\item{key}{Character string specifying which project context key to update. +Must be one of: "problem_description", "objectives", "constraints", or +"business_context".} + +\item{...}{Ignored, used for future feature expansion and compatibility.} + +\item{path}{Character string specifying the path to the memory file. If +\code{NULL}, uses the default (\code{btw-memory.yaml} in the project root).} +} +\value{ +The requested project context data, or \code{NULL} if no memory exists +for the project context or selected keys yet. +} +\description{ +Reads the project context section from the btw memory. Can return the entire +project context or a specific set of keys within it. +} +\seealso{ +Other Memory tools: +\code{\link{btw_tool_memory_project_context_add}()}, +\code{\link{btw_tool_memory_project_context_replace}()} +} +\concept{Memory tools} diff --git a/man/btw_tool_memory_project_context_replace.Rd b/man/btw_tool_memory_project_context_replace.Rd new file mode 100644 index 00000000..42a74430 --- /dev/null +++ b/man/btw_tool_memory_project_context_replace.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tool-memory.R +\name{btw_tool_memory_project_context_replace} +\alias{btw_tool_memory_project_context_replace} +\title{Tool: Project Context Memory - Replace Context} +\usage{ +btw_tool_memory_project_context_replace(key, content, ..., path = NULL) +} +\arguments{ +\item{key}{Character string specifying which project context key to update. +Must be one of: "problem_description", "objectives", "constraints", or +"business_context".} + +\item{content}{Character vector of content to add. For "problem_description", +only the first element is used and replaces existing content. For other keys, +all elements are appended to the existing array.} + +\item{...}{Ignored, used for future feature expansion and compatibility.} + +\item{path}{Character string specifying the path to the memory file. If +\code{NULL}, uses the default (\code{btw-memory.yaml} in the project root).} +} +\value{ +Invisibly returns the updated memory data +} +\description{ +Replaces content in the project context section of the btw memory. +} +\seealso{ +Other Memory tools: +\code{\link{btw_tool_memory_project_context_add}()}, +\code{\link{btw_tool_memory_project_context_read}()} +} +\concept{Memory tools} diff --git a/man/btw_tools.Rd b/man/btw_tools.Rd index 904de948..7d134443 100644 --- a/man/btw_tools.Rd +++ b/man/btw_tools.Rd @@ -36,6 +36,67 @@ Use this tool when you need to learn what changed in a package release, i.e. | | btw_tool_files_list_files | files | List files in the current working directory or in subfolders in the current project directory. | | btw_tool_files_read_text_file | files | Read an entire text file. | | btw_tool_ide_read_current_editor | ide | Read the contents of the editor that is currently open in the user's IDE. | +| btw_tool_memory_project_context_add | memory | Store or append information about the project. + +Use this when the user provides new information that won't change during the project lifecycle about: +\itemize{ +\item \strong{problem_description}: The main business problem or research question (replaces existing) +\item \strong{objectives}: Specific analysis goals or questions to answer (appends to list) +\item \strong{constraints}: Limitations, requirements, or restrictions (appends to list) +\item \strong{business_context}: Domain knowledge, organizational background (appends to list) +} + +\strong{When to use:} User mentions project goals, business requirements, analysis objectives, domain constraints, or background context that should be remembered across sessions. + +\strong{Examples:} +\itemize{ +\item User says "I need to predict customer churn" -> store in \code{problem_description} as "Identify customers at risk of churning" +\item User explains "We need three risk levels" -> add to \code{objectives} as "Segment customers into low, medium, high risk" +\item User states "We can't use personal data" -> add to \code{constraints} as "No personal data allowed in analysis" +\item User provides background on company goals -> add to \code{business_context} as "Recent product changes have affected customer retention". | +| btw_tool_memory_project_context_read | memory | Read project memory. +} + +Retrieves stored project context to understand the analysis requirements and provide contextually appropriate responses. + +\strong{When to use:} +\itemize{ +\item At the start of conversations to understand the project scope +\item When making analysis recommendations to align with stated objectives +\item When the user asks about project goals or requirements +\item Before suggesting approaches to ensure they fit within constraints +\item When you need to recall business context to interpret results appropriately +} + +\strong{Key scenarios:} +\itemize{ +\item User asks "What are we trying to accomplish?" -> read \code{objectives} +\item User wants analysis suggestions -> read constraints and \code{success_criteria} +\item User asks about project background -> read \code{business_context} +\item You need full context for recommendations -> read without specifying key +} + +\strong{Best practice:} Check project context early in conversations to provide more relevant and targeted assistance. | +| btw_tool_memory_project_context_replace | memory | Replace or correct existing project memory. + +\strong{When to use:} +\itemize{ +\item User corrects previously stated objectives or requirements +\item Project scope changes significantly +\item User provides more accurate problem description +\item Need to reorganize or consolidate scattered information +\item User explicitly asks to "update" or "change" stored project information +} + +\strong{Key scenarios:} +\itemize{ +\item "Actually, the goal is prediction, not classification" -> replace objectives +\item "I was wrong about the constraints" -> replace constraints +\item User provides detailed problem statement to replace vague initial description +\item Consolidating multiple business_context entries into organized list +} + +\strong{Caution:} Use sparingly - prefer \code{add} for new information. | | btw_tool_search_package_info | search | Describe a CRAN package. | | btw_tool_search_packages | search | Search for an R package on CRAN. \subsection{Search Behavior}{ From 33efe7aada154ad51262f03e3ff44b3416646b29 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 13:06:57 -0400 Subject: [PATCH 3/8] docs: fix tool description in docs --- R/tools.R | 2 +- man/btw_tools.Rd | 111 ++++++----------------------------------------- 2 files changed, 15 insertions(+), 98 deletions(-) diff --git a/R/tools.R b/R/tools.R index a4c3d57a..ff497d79 100644 --- a/R/tools.R +++ b/R/tools.R @@ -103,7 +103,7 @@ wrap_with_intent <- function(tool) { # nocov start .docs_list_tools <- function() { x <- map(.btw_tools, function(tool) { - desc <- strsplit(S7::prop(tool$tool(), "description"), ". ", fixed = TRUE) + desc <- strsplit(S7::prop(tool$tool(), "description"), "[.]( |\n)") desc <- desc[[1]][1] if (!grepl("[.]$", desc)) { diff --git a/man/btw_tools.Rd b/man/btw_tools.Rd index 7d134443..27ef5707 100644 --- a/man/btw_tools.Rd +++ b/man/btw_tools.Rd @@ -26,103 +26,20 @@ this function have access to the tools:\tabular{lll}{ btw_tool_docs_help_page \tab docs \tab Get help page from package. \cr btw_tool_docs_package_help_topics \tab docs \tab Get available help topics for an R package. \cr btw_tool_docs_package_news \tab docs \tab Read the release notes (NEWS) for a package. \cr -} - - -Use this tool when you need to learn what changed in a package release, i.e. | -| btw_tool_docs_vignette | docs | Get a package vignette in plain text. | -| btw_tool_env_describe_data_frame | env | Show the data frame or table or get information about the structure of a data frame or table. | -| btw_tool_env_describe_environment | env | List and describe items in an environment. | -| btw_tool_files_list_files | files | List files in the current working directory or in subfolders in the current project directory. | -| btw_tool_files_read_text_file | files | Read an entire text file. | -| btw_tool_ide_read_current_editor | ide | Read the contents of the editor that is currently open in the user's IDE. | -| btw_tool_memory_project_context_add | memory | Store or append information about the project. - -Use this when the user provides new information that won't change during the project lifecycle about: -\itemize{ -\item \strong{problem_description}: The main business problem or research question (replaces existing) -\item \strong{objectives}: Specific analysis goals or questions to answer (appends to list) -\item \strong{constraints}: Limitations, requirements, or restrictions (appends to list) -\item \strong{business_context}: Domain knowledge, organizational background (appends to list) -} - -\strong{When to use:} User mentions project goals, business requirements, analysis objectives, domain constraints, or background context that should be remembered across sessions. - -\strong{Examples:} -\itemize{ -\item User says "I need to predict customer churn" -> store in \code{problem_description} as "Identify customers at risk of churning" -\item User explains "We need three risk levels" -> add to \code{objectives} as "Segment customers into low, medium, high risk" -\item User states "We can't use personal data" -> add to \code{constraints} as "No personal data allowed in analysis" -\item User provides background on company goals -> add to \code{business_context} as "Recent product changes have affected customer retention". | -| btw_tool_memory_project_context_read | memory | Read project memory. -} - -Retrieves stored project context to understand the analysis requirements and provide contextually appropriate responses. - -\strong{When to use:} -\itemize{ -\item At the start of conversations to understand the project scope -\item When making analysis recommendations to align with stated objectives -\item When the user asks about project goals or requirements -\item Before suggesting approaches to ensure they fit within constraints -\item When you need to recall business context to interpret results appropriately -} - -\strong{Key scenarios:} -\itemize{ -\item User asks "What are we trying to accomplish?" -> read \code{objectives} -\item User wants analysis suggestions -> read constraints and \code{success_criteria} -\item User asks about project background -> read \code{business_context} -\item You need full context for recommendations -> read without specifying key -} - -\strong{Best practice:} Check project context early in conversations to provide more relevant and targeted assistance. | -| btw_tool_memory_project_context_replace | memory | Replace or correct existing project memory. - -\strong{When to use:} -\itemize{ -\item User corrects previously stated objectives or requirements -\item Project scope changes significantly -\item User provides more accurate problem description -\item Need to reorganize or consolidate scattered information -\item User explicitly asks to "update" or "change" stored project information -} - -\strong{Key scenarios:} -\itemize{ -\item "Actually, the goal is prediction, not classification" -> replace objectives -\item "I was wrong about the constraints" -> replace constraints -\item User provides detailed problem statement to replace vague initial description -\item Consolidating multiple business_context entries into organized list -} - -\strong{Caution:} Use sparingly - prefer \code{add} for new information. | -| btw_tool_search_package_info | search | Describe a CRAN package. | -| btw_tool_search_packages | search | Search for an R package on CRAN. -\subsection{Search Behavior}{ -\itemize{ -\item Prioritizes exact phrase matches over individual words -\item Falls back to word matching only when phrase matching fails -} -} - -\subsection{Query Strategy}{ -\itemize{ -\item Submit separate searches for distinct concepts (e.g., \code{flights}, \code{airlines}) -\item Break multi-concept queries (e.g., \verb{flights airlines data API}) into multiple searches and synthesize results -\item Search for single, specific technical terms that package authors would use -\item If the search result includes more than a 1000 results, refine your query and try again. -} -} - -\subsection{Examples}{ - -Good: Search for \code{"permutation test"} or just \code{"permutation"} -Bad: Search for \code{"statistical analysis tools for permutation test"} -. | -| btw_tool_session_check_package_installed | session | Check if a package is installed in the current session. | -| btw_tool_session_package_info | session | Verify that a specific package is installed, or find out which packages are in use in the current session. | -| btw_tool_session_platform_info | session | Describes the R version, operating system, language and locale settings for the user's system. | + btw_tool_docs_vignette \tab docs \tab Get a package vignette in plain text. \cr + btw_tool_env_describe_data_frame \tab env \tab Show the data frame or table or get information about the structure of a data frame or table. \cr + btw_tool_env_describe_environment \tab env \tab List and describe items in an environment. \cr + btw_tool_files_list_files \tab files \tab List files in the current working directory or in subfolders in the current project directory. \cr + btw_tool_files_read_text_file \tab files \tab Read an entire text file. \cr + btw_tool_ide_read_current_editor \tab ide \tab Read the contents of the editor that is currently open in the user's IDE. \cr + btw_tool_memory_project_context_add \tab memory \tab Store or append information about the project. \cr + btw_tool_memory_project_context_read \tab memory \tab Read project memory. \cr + btw_tool_memory_project_context_replace \tab memory \tab Replace or correct existing project memory. \cr + btw_tool_search_package_info \tab search \tab Describe a CRAN package. \cr + btw_tool_search_packages \tab search \tab Search for an R package on CRAN. \cr + btw_tool_session_check_package_installed \tab session \tab Check if a package is installed in the current session. \cr + btw_tool_session_package_info \tab session \tab Verify that a specific package is installed, or find out which packages are in use in the current session. \cr + btw_tool_session_platform_info \tab session \tab Describes the R version, operating system, language and locale settings for the user's system. \cr } } \examples{ From 6c2ed4497ce233d1223d45180ad7d7156c5fe075 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 13:13:44 -0400 Subject: [PATCH 4/8] chore: add annotations --- R/tool-memory.R | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/R/tool-memory.R b/R/tool-memory.R index 65104ba9..f4d2c9bd 100644 --- a/R/tool-memory.R +++ b/R/tool-memory.R @@ -61,6 +61,12 @@ btw_memory_keys_project_context <- function() { BtwToolResult("Success.") }, .name = "btw_tool_memory_project_context_add", + .annotations = ellmer::tool_annotations( + title = "Add project context to memory", + read_only_hint = FALSE, + open_world_hint = FALSE, + destructive_hint = FALSE + ), .description = "Store or append information about the project. Use this when the user provides new information that won't change during the project lifecycle about: @@ -153,6 +159,11 @@ btw_tool_memory_project_context_read <- function(key = NULL, ..., path = NULL) { btw_tool_memory_project_context_read(key) }, .name = "btw_tool_memory_project_context_read", + .annotations = ellmer::tool_annotations( + title = "Read project context from memory", + read_only_hint = TRUE, + open_world_hint = FALSE + ), .description = "Read project memory. Retrieves stored project context to understand the analysis requirements and provide contextually appropriate responses. @@ -232,6 +243,12 @@ btw_tool_memory_project_context_replace <- function( BtwToolResult("Success.") }, .name = "btw_tool_memory_project_context_replace", + .annotations = ellmer::tool_annotations( + title = "Replace project context in memory", + read_only_hint = FALSE, + open_world_hint = FALSE, + destructive_hint = TRUE + ), .description = "Replace or correct existing project memory. **When to use:** From 056edfe3285343fb2ba0874ae8e7d5d0a41938f4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 17:24:15 -0400 Subject: [PATCH 5/8] fix: writing to memory when the memory file doesn't exist yet --- R/tool-memory.R | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/R/tool-memory.R b/R/tool-memory.R index f4d2c9bd..844722ef 100644 --- a/R/tool-memory.R +++ b/R/tool-memory.R @@ -28,7 +28,11 @@ btw_tool_memory_project_context_add <- function( check_character(content) path <- path_find_btw_memory(path, must_exist = FALSE) - mem <- read_btw_memory_yaml(path) + if (fs::file_exists(path)) { + mem <- read_btw_memory_yaml(path) + } else { + mem <- as_btw_memory(list()) + } if (is.null(mem$project_context)) { mem$project_context <- list() @@ -217,11 +221,15 @@ btw_tool_memory_project_context_replace <- function( ..., path = NULL ) { - path <- path_find_btw_memory(path) + path <- path_find_btw_memory(path, must_exist = FALSE) key <- arg_match(key, btw_memory_keys_project_context()) check_character(content) - memory_data <- read_btw_memory_yaml(path) + if (fs::file_exists(path)) { + memory_data <- read_btw_memory_yaml(path) + } else { + memory_data <- as_btw_memory(list()) + } if (key == "problem_description") { memory_data$project_context[[key]] <- paste(contents, collapse = "\n\n") @@ -385,7 +393,7 @@ path_find_btw_memory <- function(path = NULL, must_exist = TRUE) { if (!is.null(path)) { if (must_exist && !file.exists(path)) { - cli::cli_abort("File '{file}' does not exist.") + cli::cli_abort("File {.path {path}} does not exist.") } return(path) } From fe36db49b28743cf63f193c443ed81b9aebb0005 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 17:25:03 -0400 Subject: [PATCH 6/8] refactor: use full name `memory_data` --- R/tool-memory.R | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/R/tool-memory.R b/R/tool-memory.R index 844722ef..016bbc0d 100644 --- a/R/tool-memory.R +++ b/R/tool-memory.R @@ -29,26 +29,29 @@ btw_tool_memory_project_context_add <- function( path <- path_find_btw_memory(path, must_exist = FALSE) if (fs::file_exists(path)) { - mem <- read_btw_memory_yaml(path) + memory_data <- read_btw_memory_yaml(path) } else { - mem <- as_btw_memory(list()) + memory_data <- as_btw_memory(list()) } - if (is.null(mem$project_context)) { - mem$project_context <- list() + if (is.null(memory_data$project_context)) { + memory_data$project_context <- list() } if (key == "problem_description") { - mem$project_context[[key]] <- paste( - c(mem$project_context[[key]], content), + memory_data$project_context[[key]] <- paste( + c(memory_data$project_context[[key]], content), collapse = "\n\n" ) } else { - mem$project_context[[key]] <- c(mem$project_context[[key]], content) + memory_data$project_context[[key]] <- c( + memory_data$project_context[[key]], + content + ) } - write_btw_memory_yaml(mem, path) - invisible(mem) + write_btw_memory_yaml(memory_data, path) + invisible(memory_data) } btw_memory_keys_project_context <- function() { From f3f43ed06ac59c925144d9e120a332f5a75f9162 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 2 Jun 2025 17:25:38 -0400 Subject: [PATCH 7/8] chore: don't ignore dot files --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4308aeac..a61557a5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ docs btw-memory.yaml /_* -/.* From 1621333e2695c4c0300d34bad26c506db3751c3f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Jun 2025 10:00:33 -0400 Subject: [PATCH 8/8] chore: fixes from code review --- R/tool-memory.R | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/R/tool-memory.R b/R/tool-memory.R index 016bbc0d..1608ba8f 100644 --- a/R/tool-memory.R +++ b/R/tool-memory.R @@ -194,7 +194,7 @@ Retrieves stored project context to understand the analysis requirements and pro items = ellmer::type_enum( values = c("all", btw_memory_keys_project_context()), description = " -* all: All project context keys +* all: All project context keys * problem_description: High-level description of the business problem to solve * objectives: Specific analysis goals and questions to answer * success_criteria: How to measure if the analysis was successful @@ -235,9 +235,9 @@ btw_tool_memory_project_context_replace <- function( } if (key == "problem_description") { - memory_data$project_context[[key]] <- paste(contents, collapse = "\n\n") + memory_data$project_context[[key]] <- paste(content, collapse = "\n\n") } else { - memory_data$project_context[[key]] <- as.character(contents) + memory_data$project_context[[key]] <- as.character(content) } write_btw_memory_yaml(memory_data, path) @@ -318,9 +318,7 @@ as_btw_memory <- function(x) { #' @param path Path to the YAML file. Defaults to "btw-memory.yaml" #' @noRd write_btw_memory_yaml <- function(x, path = NULL) { - if (!inherits(x, "btw_memory")) { - stop("Object must have class 'btw_memory'") - } + check_inherits(x, "btw_memory") data <- unclass(x) path <- path_find_btw_memory(path, must_exist = FALSE)