AGENTS.md

AGENTS.md

Introduction

This document defines a suite of specialized LLM-based agents designed to streamline and enhance the development, testing, documentation, and maintenance of Jamovi modules, specifically:

Each agent encapsulates a focused set of responsibilities and expertise, enabling efficient collaboration with Large Language Models (LLMs) for code generation, debugging, documentation, quality assurance, refactoring, and domain-specific analysis.

How to Use This Document

  1. Identify Task: Determine which aspect of module development you need assistance with (e.g., writing R functions, drafting YAML, creating unit tests, designing a plot).
  2. Select Agent: Choose the agent from the list below whose responsibilities align with your task.
  3. Launch Prompt: Use the example prompt templates to invoke the chosen agent. Copy and paste the template into ChatGPT or your preferred LLM interface, replacing placeholders (e.g., <module_name>, <function_name>, <description>) with context-specific details.
  4. Iterate: Agents are designed for iterative workflows; if the output needs refinement, provide additional context or clarifications.
  5. Integrate Output: Incorporate the generated code, documentation, tests, or analyses into your Jamovi module project.

Agents Overview

| Agent Name | Primary Responsibilities | | ------------------------ | --------------------------------------------------------------------------------------------------------- | | ModuleArchitectAgent | Scaffolding module structure, YAML configuration, metadata, dependencies, import/export logic | | RFunctionGenAgent | Generating robust, idiomatic R functions for statistical analyses and data manipulation | | DocumentationAgent | Creating or updating .R, .u.yaml, .r.yaml, .a.yaml files with clear comments and user-facing help | | PlotDesignAgent | Designing and coding statistical plots (ggplot2, Jamovi plotting conventions, bslib themes) | | TestQAAgent | Writing unit tests (testthat) and integration tests; validating existing functions for edge cases | | YAMLConfigAgent | Drafting and validating YAML specification files for Jamovi modules (.u.yaml, .r.yaml, .a.yaml) | | RefactorAgent | Refactoring and optimizing existing code for readability, performance, and style compliance | | BugTrackerAgent | Identifying, reproducing, and proposing fixes for bugs based on user error messages or failing tests | | CIIntegrationAgent | Configuring GitHub Actions, CI pipelines, and automated checks for module builds, linting, and testing | | DomainExpertAgent | Providing domain-specific insights (e.g., clinical decision thresholds, interpretation of survival plots) | | VisualizationUXAgent | Enhancing UI/UX: table layouts (DT), tooltips, theme toggles, and accessibility | | ReleaseManagerAgent | Drafting release notes, version bump logic, changelog generation, packaging (tarball, zip) |

Each agent is described in detail below.

Implementation Patterns (Informed by Repositories and Jamovi Dev Docs)

Overview

ClinicoPath statistical modules (meddecide, jjstatsplot, ClinicoPathDescriptives, jsurvival) follow a consistent structure, treating each analysis as an agent with defined inputs, processing logic, and outputs. These agents are implemented as R6 classes (in .b.R files) and configured by YAML files (.a.yaml, .u.yaml, .r.yaml). This design cleanly separates the user interface, analysis options, and result definitions.

Core Patterns Extracted

  1. Function Calls Using private$: Agents delegate tasks to private helper methods (e.g., .prepareData(), .computeMetrics(), .buildTable()) defined within the private = list() block of the R6 class.

  2. Use of self$options and self$results:

  3. self$options: Holds inputs defined in .a.yaml. Access values directly (e.g., self$options$outcome, self$options$alpha).

  4. self$results: Contains output objects defined in .r.yaml. Populate outputs using $setContent(), $setRow(), $addRow(), and $setState().

  5. YAML Files Relationship:

  6. .a.yaml: Defines options (names, types, defaults, constraints) and registers the analysis.

  7. .u.yaml: Describes UI layout (controls, labels, grouping) by referencing options in .a.yaml.
  8. .r.yaml: Specifies result objects (tables, plots, text), renderFun for images, visibility conditions, and dependencies (clearWith).

  9. Jamovi Developer Guidelines Integration:

  10. Based on Jamovi Module Authoring, modules include:

    • DESCRIPTION file: Lists dependencies (e.g., Imports: jmvcore, survival).
    • jamovi-module.yml manifest: Registers version, dependencies, and jmvcore compatibility.
    • man/ folder: Contains Rd documentation generated from roxygen2 comments in R.
    • R/ folder: Contains .b.R (implementation), .h.R (auto-generated base), and other supporting functions.
    • resources/ folder: Provides icons or additional JS/CSS for custom UI if needed.
    • Testing structure: tests/testthat/ for unit tests; ensure R CMD check passes and CI workflows validate builds.

By examining real-world examples and official guidelines, we see how inputs flow from UI to R code and how outputs are formatted for display.

YAML Configuration Files

1. Analysis Definition (.a.yaml)

Purpose: Register the analysis, define options and defaults, and group in the menu.

Pattern Example (meddecide/kappaSizeCI.a.yaml):

analysis-name: kappaSizeCI
class: kappaSizeCIClass
package: meddecide
requiresData: false
title: "Kappa Sample Size Calculation"
menu:
  - name: "Interobserver Analysis"
    sub-menu:
      - name: "Kappa Sample Size"

options:
  - name: outcome
    type: list
    values: ["2", "3", "4", "5"]
    default: "2"
    title: "Number of Outcome Levels"

  - name: kappa0
    type: number
    default: 0.60
    minimum: 0.01
    maximum: 0.99
    title: "Null Kappa (K<sub>0</sub>)"
    description: "Expected kappa under null hypothesis"

  - name: kappaL
    type: number
    default: 0.40
    minimum: 0.01
    maximum: 0.99
    title: "Lower Bound of Kappa (K<sub>L</sub>)"
    description: "Lower limit of clinically acceptable kappa"

  - name: kappaU
    type: number
    default: 0.80
    minimum: 0.01
    maximum: 0.99
    title: "Upper Bound of Kappa (K<sub>U</sub>)"
    description: "Upper limit of clinically acceptable kappa"

  - name: props
    type: string
    default: "0.20, 0.80"
    title: "Category Proportions"
    description: "Comma-separated proportions for each outcome level"

  - name: raters
    type: integer
    default: 2
    minimum: 2
    maximum: 10
    title: "Number of Raters"

  - name: alpha
    type: number
    default: 0.05
    minimum: 0.01
    maximum: 0.10
    title: "Significance Level (α)"

refs:
  - ClinicoPathJamoviModule
  - kappaSize

Notes:

2. User Interface Layout (.u.yaml)

Purpose: Organize controls, labels, and grouping for the user interface.

Pattern Example (meddecide/kappaSizeCI.u.yaml):

- type: label
  title: "Kappa Sample Size"
  text: "Calculate sample size for desired kappa precision."
  footnote: "Based on Donner & Eliasziw (1992)."

- type: group
  title: "Outcome Settings"
  content:
    - type: combobox
      name: outcome
      title: "Number of Outcome Levels"
      values: $(options.outcome.values)

    - type: textbox
      name: props
      title: "Category Proportions"
      placeholder: "e.g., 0.20, 0.80"

- type: group
  title: "Kappa Parameters"
  content:
    - type: textbox
      name: kappa0
      title: "Null Kappa (K0)"

    - type: textbox
      name: kappaL
      title: "Lower Bound (KL)"

    - type: textbox
      name: kappaU
      title: "Upper Bound (KU)"

- type: group
  title: "Study Design"
  content:
    - type: spinner
      name: raters
      title: "Number of Raters"
      min: 2
      max: 10

    - type: textbox
      name: alpha
      title: "Alpha (α)"
      placeholder: "0.05"

Notes:

3. Results Specification (.r.yaml)

Purpose: Define output objects, rendering functions, visibility conditions, and dependencies.

Pattern Example (meddecide/kappaSizeCI.r.yaml):

refs:
  - ClinicoPathJamoviModule
  - kappaSize

output:
  - name: text1
    type: "preformatted"
    title: "Required Sample Size"
    clearWith: [outcome, kappa0, kappaL, kappaU, props, raters, alpha]
    visible: true

  - name: text2
    type: "preformatted"
    title: "Study Explanation"
    clearWith: [outcome, kappa0, kappaL, kappaU, props, raters, alpha]
    visible: true

Pattern Example (meddecide/decision.r.yaml):

refs:
  - ClinicoPathJamoviModule
  - epiR
  - FaganNomogram
  - pROC

output:
  # Pre-populated Count Table
  - name: cTable
    type: table
    title: "Test vs Gold Standard"
    rows: 0
    columns:
      - name: newtest
        type: text
        title: "Test Result"
      - name: GoldPos
        type: number
        title: "Gold Positive"
      - name: GoldNeg
        type: number
        title: "Gold Negative"
      - name: Total
        type: number
        title: "Total"
    clearWith: [gold, newtest]

  # Single-row summary
  - name: nTable
    type: table
    title: "Basic Counts"
    rows: 1
    columns:
      - name: TotalPop
        type: integer
        title: "Total Population"
      - name: Diseased
        type: integer
        title: "Diseased"
      - name: Healthy
        type: integer
        title: "Healthy"
      - name: TP
        type: integer
        title: "True Positive"
      - name: FP
        type: integer
        title: "False Positive"
      - name: FN
        type: integer
        title: "False Negative"
      - name: TN
        type: integer
        title: "True Negative"
    clearWith: [gold, newtest]

  # Ratio Table (Conditional on CI)
  - name: epirTable_ratio
    type: table
    title: "Effect Estimates (Ratio)"
    rows: 0
    columns:
      - name: statsnames
        type: text
        title: "Statistic"
      - name: est
        type: number
        title: "Estimate"
      - name: lower
        type: number
        title: "Lower 95% CI"
      - name: upper
        type: number
        title: "Upper 95% CI"
    visible: (ci)
    clearWith: [gold, newtest, ci]

  # Numeric Counts Table (Conditional)
  - name: epirTable_number
    type: table
    title: "Effect Estimates (Numbers)"
    rows: 0
    columns:
      - name: statsnames
        type: text
        title: "Statistic"
      - name: value
        type: integer
        title: "Value"
    visible: (ci)
    clearWith: [gold, newtest, ci]

  # Fagan Nomogram Plot
  - name: plot1
    type: image
    title: "Fagan Nomogram"
    renderFun: .plot1
    width: 600
    height: 450
    visible: (fagan)
    requiresData: true
    clearWith: [gold, newtest, fagan]

  # ROC Curve Plot
  - name: plot2
    type: image
    title: "ROC Curve"
    renderFun: .plot2
    width: 600
    height: 450
    visible: (roc)
    requiresData: true
    clearWith: [gold, newtest, roc]

Key Points:

R6 Analysis Class Patterns (.b.R)

Each agent’s computation is implemented in an R6 class, inheriting from a generated base class (from .h.R) that provides active bindings for all options and results. The .b.R file extends this base and implements the analysis logic.

1. Structure of the R6 Class

Example Skeleton:

kappaSizeCIClass <- R6::R6Class(
  "kappaSizeCIClass",
  inherit = kappaSizeCIBase,
  private = list(
    .init = function() {
      # e.g., set initial visibility or pre-populate table rows
    },

    .run = function() {
      # 1. Validate inputs
      # 2. Access inputs: outcome <- self$options$outcome
      # 3. Compute sample size via kappaSize package
      # 4. text1 <- ...; text2 <- ...
      # 5. Populate outputs:
      #    self$results$text1$setContent(text1)
      #    self$results$text2$setContent(text2)
    },

    .plot1 = function(image, ggtheme) {
      # Retrieve data: plotData <- image$state
      # Build ggplot object
      # print(plot)
      TRUE
    }
  )
)

2. Initialization (.init)

Example (meddecide/decision.b.R):

.decisionClass.init <- function() {
  # Pre-populate cTable with row labels
  cTable <- self$results$cTable
  cTable$addRow(rowKey = 1, values = list(newtest = "Test Positive"))
  cTable$addRow(rowKey = 2, values = list(newtest = "Test Negative"))
  cTable$addRow(rowKey = 3, values = list(newtest = "Total"))
}

3. Main Analysis (.run)

r outcome <- self$options$outcome kappa0 <- self$options$kappa0 props <- self$options$props raters <- self$options$raters alpha <- self$options$alpha Data Validation: Check self$data or required options. If missing, display instructions via a “todo” output and return() early. Split, Convert, Compute: For string inputs (e.g., props), split with strsplit() and convert to numeric. Call External Packages: E.g., kappaSize::CIBinary(), epiR::epi.tests(), survival::survfit(), tableone::CreateTableOne(). Store Intermediate Results: For complex or heavy plots, store data in the image’s state:

r plotData <- list(Sens = sens, Spec = spec) self$results$plot1$setState(plotData) * Populate Results:

r self$results$todo$setContent("")

4. Plot Rendering (.plotX)

r plotData <- image$state Construct Plot: Use ggplot2 or helper functions (e.g., nomogrammer(plotData$Prevalence, plotData$Sens, ...)). Apply Theme: Use ggtheme or jmvcore::theme() for consistency with Jamovi. * Print Plot: print(p) and return TRUE.

Example (meddecide/decision.b.R):

.decisionClass.plot1 <- function(image, ggtheme) {
  plotData <- image$state
  p <- nomogrammer(
    prevalence = plotData$Prevalence,
    sens       = plotData$Sens,
    spec       = plotData$Spec,
    plr        = plotData$Plr,
    nlr        = plotData$Nlr
  ) + ggtheme
  print(p)
  TRUE
}

5. Private Helper Methods

Example (jsurvival/survival.b.R):

.survivalClass.getData <- function() {
  data <- self$data
  timeVar   <- self$options$time
  eventVar  <- self$options$status
  groupVar  <- self$options$group

  # Convert factor levels if needed
  data[[eventVar]] <- as.numeric(data[[eventVar]] == "Yes")

  # Return list of cleaned data and variable names
  list(
    data    = data,
    timeVar  = timeVar,
    eventVar = eventVar,
    groupVar = groupVar
  )
}

.survivalClass.run <- function() {
  args <- private$.getData()
  survObj <- survival::Surv(time = args$data[[args$timeVar]],
                             event = args$data[[args$eventVar]])
  fit <- survival::survfit(survObj ~ args$data[[args$groupVar]], data = args$data)
  # Populate KM plot state
  self$results$survPlot$setState(list(fit = fit, data = args$data, group = args$groupVar))

  # Other outputs: median survival table
  medians <- summary(fit)$table
  for (i in seq_along(medians)) {
    self$results$medianTable$addRow(rowKey = i, values = list(
      Group  = medians$group[i],
      Median = medians$median[i]
    ))
  }
}

End-to-End Examples of Agent Workflow

Example 1: Kappa Sample Size Calculation (meddecide)

Context: Computes required sample size for an inter-rater agreement study.

Outcome: The module displays the computed sample size and an explanatory message illustrating parameter choices.

Example 2: Decision Analysis (meddecide)

Context: Computes diagnostic test accuracy metrics, populates count tables, and renders plots (Fagan nomogram, ROC curve).

Example 3: Table One (ClinicoPathDescriptives)

Context: Generates a descriptive summary table (“Table One”) in multiple formats: tableone, gtsummary, arsenal, or janitor.

```yaml options: - name: vars type: variable-list title: "Variables to Summarize"

- name: excl
  type: boolean
  default: false
  title: "Exclude Missing Values"

- name: sty
  type: list
  values: ["t1", "t2", "t3", "t4"]
  default: "t1"
  title: "Table Style"

refs: - ClinicoPathJamoviModule - tableone - gtsummary - arsenal - janitor ```

```yaml - type: label title: "Table One" text: "Generate descriptive statistics for selected variables."

```yaml refs: - ClinicoPathJamoviModule - tableone - gtsummary - arsenal - janitor

output: - name: todo type: html title: "Instructions" visible: (vars:empty)

- name: tablestyle1
  type: preformatted
  title: "TableOne Output"
  visible: (sty:t1)
  clearWith: [vars, excl]

- name: tablestyle2
  type: html
  title: "gtSummary Output"
  visible: (sty:t2)
  clearWith: [vars, excl]

- name: tablestyle3
  type: html
  title: "Arsenal Output"
  visible: (sty:t3)
  clearWith: [vars, excl]

- name: tablestyle4
  type: html
  title: "Janitor Output"
  visible: (sty:t4)
  clearWith: [vars, excl]

```

Notes:

Shared Design Conventions and Best Practices

  1. Consistent Naming and Inheritance

  2. R6 classes: AnalysisClass inherits from AnalysisBase.

  3. Base classes auto-generated from YAML provide self$options and self$results.

  4. Option Access and Types

  5. Access inputs with self$options$varName (matching .a.yaml).

  6. List options return character strings; numeric options return numeric; variable-list returns a vector of column names.
  7. Immediately assign to local variables and apply transformations (e.g., as.integer, as.numeric, strsplit).

  8. Result Access and Modification

  9. Text outputs: self$results$name$setContent(value).

  10. Fixed-row tables: self$results$name$setRow(rowNo, values = list(...)).
  11. Dynamic tables: self$results$name$addRow(rowKey, values = c(...)).
  12. Images/complex objects: self$results$name$setState(list(...)).
  13. Visibility overrides: self$results$name$setVisible(TRUE/FALSE).

  14. UI Guidance via Outputs

  15. Use a todo HTML output to guide users when required inputs are missing.

  16. Clear the message once inputs are valid: self$results$todo$setContent("").

  17. ClearWith and Dependencies

  18. List all relevant options in clearWith to prevent stale outputs.

  19. For images or tables depending on multiple inputs (e.g., gold, newtest, ci), include all in clearWith.

  20. Visible Conditions

  21. Use visible: (option) or visible: (option:value) in .r.yaml to control output display.

  22. Common patterns: (ci), (fagan), (roc), (sty:t1), (vars:empty).
  23. Keeps the interface uncluttered by showing only relevant outputs.

  24. Citing Sources

  25. Add external references in refs: under .a.yaml or .r.yaml.

  26. Ensure proper attribution for statistical methods (e.g., epiR, survival, kappaSize, gtsummary).

  27. Coding Style and Comments

  28. Use comment headers (e.g., # ----) to separate logical sections (data prep, error handling, computations, output).

  29. Comment out alternative approaches or TODOs for clarity and future work.
  30. Adhere to a consistent code style (tidyverse or base R) as per project guidelines.

  31. Private Helper Functions

  32. Encapsulate reusable logic in private methods (private$.functionName()).

  33. Examples: .getData(), .computeMetrics(), .preparePlotData(), .buildTableRows().

  34. Storage of Plot Data

    • Use image$setState() in .run to store data for plotting.
    • Implement .plotX() methods to retrieve and visualize image$state.
    • Ensures heavy computations occur once, separated from rendering logic.

Example Agent Workflow

  1. New Feature: Add Decision Curve Analysis to meddecide

  2. Use YAMLConfigAgent to draft the UI (.u.yaml) for computeDecisionCurve, defining inputs: data, outcome, predicted_prob, threshold_seq, plotROC.

  3. Use RFunctionGenAgent to implement computeDecisionCurve in R/decision_curve.R: validate inputs, calculate net benefit at each threshold, return a jmvcore::Table and a Plot.
  4. Use PlotDesignAgent to generate a net benefit vs. threshold plot with ggplot2, matching Jamovi’s style.
  5. Use TestQAAgent to write tests for net benefit computation (using a toy dataset with known expected values).
  6. Use DocumentationAgent to add roxygen2 comments and update .r.yaml and .a.yaml accordingly.
  7. Use CIIntegrationAgent to ensure tests run on CI/CD and update the GitHub Actions workflow as needed.
  8. Use ReleaseManagerAgent to draft release notes for the new function.

  9. Bug Fix: Handle Single-Level Group Error in jsurvival

  10. Provide the error message and relevant code snippet to BugTrackerAgent.

  11. BugTrackerAgent diagnoses the issue (single-level grouping) and proposes a guard clause.
  12. Use RefactorAgent to insert the guard clause, handle the edge case gracefully, and refactor code for clarity.
  13. Use TestQAAgent to write a test ensuring the edge case no longer errors.
  14. Use DocumentationAgent to update help text: note that grouping variables must have at least two levels, or else return a descriptive message.

Best Practices for Crafting Prompts

Appendix: Common Jamovi Module Components

  1. jmvcore Basics:

  2. requirePackage() to ensure dependencies.

  3. jmvcore::Options subclasses to collect user options.
  4. jmvcore::Analysis subclasses that define init(), run(), and results.
  5. Use jmvcore::preprocessData(), jmvcore::table(), and jmvcore::plot() for standard functionality.

  6. YAML File Structure:

  7. .u.yaml: Defines UI elements. Example:

    yaml - type: choices name: group label: "Grouping Variable" options: - var1 - var2 - type: integer name: alpha label: "Significance Level" default: 0.05 * .r.yaml: Maps UI options to R function parameters. Example:

    yaml - function-name: computeSurvival parameters: group: group time: time_col status: status_col * .a.yaml: Registers analyses. Example:

    yaml - analysis-name: SurvivalAnalysis class: AnalysisClass package: jsurvival requires-data: true allows-multiple-dependencies: false title: "Survival Analysis"

  8. Testing Conventions:

  9. Place tests in tests/testthat/.

  10. Name test files as test-<function>.R.
  11. Example structure:

    r test_that("computeSurvival handles single-group edge case", { data <- data.frame(time = c(1,2,3), status = c(1,0,1), group = c('A','A','A')) expect_error(computeSurvival(data), "group must have at least two levels") })

  12. Plot Export:

  13. Jamovi expects plots returned via jmvcore::Image$new() with a render() method that returns a ggplot object or grid.

  14. Example:

    r results$plot <- jmvcore::Image$new( plot = function() { p <- ggplot(data, aes(x = ..., y = ...)) + geom_line() return(p) }, width = 400, height = 300 )

Versioning

This AGENTS.md file is versioned at v1.0.2. Future updates should follow semantic versioning, reflecting changes to agent responsibilities, new agents, or prompt guidelines.

End of AGENTS.md



sbalci/ClinicoPathJamoviModule documentation built on June 13, 2025, 9:34 a.m.