R/configure_ai.R

Defines functions .update_frameworkrc .parse_assistant_selection .prompt_ai_support_init .prompt_ai_support_install .load_ai_template .create_ai_instructions configure_ai_agents

Documented in configure_ai_agents .create_ai_instructions .load_ai_template .parse_assistant_selection .prompt_ai_support_init .prompt_ai_support_install .update_frameworkrc

#' Configure AI Assistant Support
#'
#' Non-interactive function to configure AI assistant support.
#' Should be called from bash CLI with parameters, not directly from R.
#'
#' @param support "yes", "never", or NULL (show current status)
#' @param assistants Character vector of assistants: "claude", "copilot", "agents"
#'
#' @details
#' Supported AI assistants:
#' - **Claude Code**: Creates `CLAUDE.md` in project root
#' - **GitHub Copilot**: Creates `.github/copilot-instructions.md`
#' - **AGENTS.md**: Creates `AGENTS.md` (cross-platform, industry standard)
#'
#' @keywords internal
configure_ai_agents <- function(support = NULL, assistants = NULL) {
  frameworkrc <- path.expand("~/.frameworkrc")

  # Read current settings
  current_support <- Sys.getenv("FW_AI_SUPPORT", "")
  current_assistants <- Sys.getenv("FW_AI_ASSISTANTS", "")

  # If no parameters, show current status
  if (is.null(support)) {
    if (current_support == "never") {
      status_msg <- "disabled"
    } else if (current_support == "yes") {
      status_msg <- sprintf("enabled (assistants: %s)", current_assistants)
    } else {
      status_msg <- "not configured"
    }
    message("AI assistant support: ", status_msg)
    return(invisible(list(support = current_support, assistants = current_assistants)))
  }

  # Update configuration
  .update_frameworkrc(frameworkrc, support = support, assistants = assistants %||% character(0))

  if (support == "yes" && length(assistants) > 0) {
    message("[ok] AI assistant support enabled")
    message("  Assistants: ", paste(assistants, collapse = ", "))
  } else if (support == "never") {
    message("[ok] AI assistant support disabled")
  }

  invisible(TRUE)
}


#' Create AI Assistant Instruction Files
#'
#' Internal function called during project_create() to create AI assistant instruction
#' files based on user preferences.
#'
#' @param assistants Character vector of assistants: "claude", "copilot", "agents"
#' @param target_dir Target directory (default: current directory)
#' @param project_name Project name for template substitution
#' @param project_type Project type for template selection ("project", "project_sensitive", "course", "presentation")
#' @keywords internal
.create_ai_instructions <- function(assistants, target_dir = ".", project_name = NULL, project_type = "project") {
  if (length(assistants) == 0) {
    return(invisible(NULL))
  }

  # Get project name from directory if not provided
  if (is.null(project_name)) {
    project_name <- basename(normalizePath(target_dir))
  }

  # Generate AI context content dynamically
  # Try to use ai_generate_context() if config exists, otherwise use template
  config <- tryCatch(
    settings_read(file.path(target_dir, "settings.yml")),
    error = function(e) NULL
  )

  if (!is.null(config)) {
    # Use dynamic generation
    content <- ai_generate_context(
      project_path = target_dir,
      project_name = project_name,
      project_type = project_type,
      config = config
    )
  } else {
    # Fall back to template
    content <- .load_ai_template(project_type, project_name)
  }

  # Define file paths for each assistant
  ai_files <- list(
    claude = "CLAUDE.md",
    agents = "AGENTS.md",
    copilot = ".github/copilot-instructions.md"
  )

  for (assistant in assistants) {
    if (assistant %in% names(ai_files)) {
      target_file <- file.path(target_dir, ai_files[[assistant]])

      # Create directory if needed (for copilot)
      file_dir <- dirname(target_file)
      if (!dir.exists(file_dir)) {
        dir.create(file_dir, recursive = TRUE, showWarnings = FALSE)
      }

      # Write content
      writeLines(content, target_file)
      message("  \u2713 Created ", ai_files[[assistant]])
    }
  }

  invisible(NULL)
}


#' Load AI context template for a project type
#'
#' @param project_type Project type
#' @param project_name Project name for placeholder substitution
#' @return Character string with template content
#' @keywords internal
.load_ai_template <- function(project_type, project_name = "My Project") {
  template_dir <- system.file("templates", package = "framework")

  # Naming convention: ai-context.{type}.md
  template_name <- sprintf("ai-context.%s.md", project_type)
  template_file <- file.path(template_dir, template_name)

  # Fall back to generic project template
  if (!file.exists(template_file)) {
    template_file <- file.path(template_dir, "ai-context.project.md")
  }

  if (!file.exists(template_file)) {
    return(sprintf("# %s\n\nFramework project.\n", project_name))
  }

  content <- paste(readLines(template_file, warn = FALSE), collapse = "\n")

  # Replace placeholders
  content <- gsub("\\{ProjectName\\}", project_name, content)

  content
}


#' Set AI Support Preferences (Non-interactive)
#'
#' Called from bash CLI to set AI preferences.
#' NO prompting - bash handles all user interaction.
#'
#' @param support "yes" or "never"
#' @param assistants Character vector like c("claude", "copilot")
#' @keywords internal
.prompt_ai_support_install <- function(support = "never", assistants = character(0)) {
  return(list(support = support, assistants = assistants))
}


#' Get AI Support Preferences (Non-interactive)
#'
#' Called during project_create() to check if AI instructions should be created.
#' NO prompting - just returns saved preferences.
#'
#' @keywords internal
.prompt_ai_support_init <- function() {
  # Check saved preferences
  fw_support <- Sys.getenv("FW_AI_SUPPORT", "")

  # If user disabled, don't create files
  if (fw_support == "never") {
    return(character(0))
  }

  # If user enabled, return their assistants
  if (fw_support == "yes") {
    assistants <- Sys.getenv("FW_AI_ASSISTANTS", "")
    if (assistants != "") {
      return(strsplit(assistants, ",")[[1]])
    }
  }

  # Not configured - don't create files
  return(character(0))
}


#' Parse Assistant Selection from User Input
#'
#' Helper to convert user input like "1,3" or "4" into assistant names.
#'
#' @param selection User input string
#' @return Character vector of assistant names
#' @keywords internal
.parse_assistant_selection <- function(selection) {
  selection <- trimws(selection)

  if (selection == "4") {
    return(c("claude", "copilot", "agents"))
  }

  # Parse comma-separated numbers (suppress NA warning for non-numeric input)
  numbers <- suppressWarnings(as.integer(strsplit(selection, ",")[[1]]))
  numbers <- numbers[!is.na(numbers)]

  assistants <- character(0)

  if (1 %in% numbers) assistants <- c(assistants, "claude")
  if (2 %in% numbers) assistants <- c(assistants, "copilot")
  if (3 %in% numbers) assistants <- c(assistants, "agents")

  return(unique(assistants))
}


#' Update ~/.frameworkrc with AI Preferences
#'
#' Helper to update the frameworkrc file with AI support settings.
#'
#' @param frameworkrc_path Path to .frameworkrc file
#' @param support "yes", "never", or ""
#' @param assistants Character vector of assistant names
#' @keywords internal
.update_frameworkrc <- function(frameworkrc_path, support, assistants) {
  # Read existing content
  if (file.exists(frameworkrc_path)) {
    lines <- readLines(frameworkrc_path, warn = FALSE)
  } else {
    lines <- character(0)
  }

  # Remove existing AI config lines
  lines <- lines[!grepl("^FW_AI_SUPPORT=", lines)]
  lines <- lines[!grepl("^FW_AI_ASSISTANTS=", lines)]

  # Add new config
  if (support != "") {
    lines <- c(
      lines,
      sprintf('FW_AI_SUPPORT="%s"', support)
    )
  }

  if (length(assistants) > 0) {
    lines <- c(
      lines,
      sprintf('FW_AI_ASSISTANTS="%s"', paste(assistants, collapse = ","))
    )
  }

  # Write back
  writeLines(lines, frameworkrc_path)

  # Update current environment
  Sys.setenv(FW_AI_SUPPORT = support)
  if (length(assistants) > 0) {
    Sys.setenv(FW_AI_ASSISTANTS = paste(assistants, collapse = ","))
  }

  invisible(NULL)
}

Try the framework package in your browser

Any scripts or data that you put into this service are public.

framework documentation built on Feb. 18, 2026, 1:07 a.m.