Skip to content

Commit 69633bc

Browse files
authored
copilot instructions (#361)
1 parent e6d26ad commit 69633bc

File tree

3 files changed

+455
-0
lines changed

3 files changed

+455
-0
lines changed

.github/copilot-instructions.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# JASP Module
2+
3+
ALWAYS follow these instructions first and fallback to additional search and context gathering ONLY if the information in these instructions is incomplete or found to be in error.
4+
5+
This is a JASP module. It contains QML user-facing interfaces and R backend computations.
6+
7+
## Working Effectively
8+
9+
### Initial Setup and Build
10+
- R is and the JASP R packages are pre-installed in GitHub Actions runners
11+
- All tests should pass. Note that there might be many tests and the runtime might be ~ 5 minutes.
12+
13+
### Running Tests
14+
- `Rscript -e "library(jaspTools); testAll()"` -- runs full test suite, takes 70+ seconds. NEVER CANCEL.
15+
- Tests are located in `tests/testthat/test-*.R` files
16+
- Each test file corresponds to an R analysis file in the `R/` directory
17+
- Test snapshots are stored in `tests/testthat/_snaps/`
18+
19+
### Repository Structure
20+
```
21+
/
22+
├── R/ # Backend R analysis functions
23+
├── inst/
24+
│ ├── qml/ # QML interface definitions
25+
│ ├── Descriptions/ # Analysis descriptions (Description.qml)
26+
│ ├── help/ # Markdown help files
27+
│ └── Upgrades.qml # Version upgrade mappings
28+
├── tests/testthat/ # Unit tests using jaspTools
29+
├── .github/workflows/ # CI/CD automation
30+
├── DESCRIPTION # R package metadata
31+
├── renv.lock # R dependency lockfile
32+
└── jaspSummaryStatistics.Rproj # RStudio project
33+
```
34+
35+
### Key Files to Check After Changes
36+
- Always check corresponding test file in `tests/testthat/` when modifying R functions
37+
- Check `inst/Upgrades.qml` when renaming QML options to maintain backward compatibility
38+
39+
## Building and Testing Code Changes
40+
41+
### Before Making Changes
42+
- Run full test suite to establish baseline: `Rscript -e "library(jaspTools); testAll()"`
43+
- NEVER CANCEL: Tests can take 300+ seconds, set timeout to 300 seconds
44+
45+
### After Making Changes
46+
- Run tests again to verify your changes: `Rscript -e "library(jaspTools); testAll()"`
47+
- NEVER CANCEL: Build and test can take up to 5 minutes total
48+
- All tests must pass - do not proceed if tests fail
49+
- Some deprecation warnings are expected and can be ignored
50+
51+
### Manual Validation Scenarios
52+
Since this module runs within JASP desktop application, manual testing requires:
53+
- Testing via jaspTools test framework (covered above)
54+
- Individual analysis validation can be done through R console using jaspTools::runAnalysis()
55+
- CANNOT run standalone - module only functions within JASP ecosystem
56+
57+
## Development Rules
58+
59+
### QML Interface Rules
60+
- QML interfaces in `inst/qml/` define user-facing options passed to R functions
61+
- Each analysis links: `inst/Description.qml/``inst/qml/``R/` functions
62+
- QML elements use `name` (camelCase internal) and `title`/`label` (user-facing)
63+
- Document QML elements using `info` property for help generation
64+
- Use existing QML files as examples for structure and style
65+
- Add default values to unit tests when adding new QML options
66+
67+
### R Backend Rules
68+
- R functions in `R/` directory called by analyses in `inst/Descriptions/`
69+
- Use camelCase for all function and variable names
70+
- NEVER use `library()` or `require()` - use `package::function()` syntax
71+
- Avoid new dependencies - re-implement simple functions instead
72+
- Access `options` list via `options[["name"]]` notation to avoid partial matching
73+
- Follow CRAN guidelines for code structure and documentation
74+
75+
### Input Validation and Error Handling
76+
- **TARGETED VALIDATION ONLY**: Since `options` are validated in the GUI, R functions should NOT check user input validity except for specific cases
77+
- **VALIDATE ONLY**: `dataset` object (data.frame from GUI), `TextField` options, and `FormulaField` options (arbitrary text input)
78+
- Use `gettext()` and `gettextf()` for all user-visible messages (internationalization)
79+
- For `dataset` validation, check: missing values, infinity, negative values, insufficient observations, factor levels, variance
80+
- Example: `.hasErrors(dataset, type = c('observations', 'variance', 'infinity'), all.target = options$variables, observations.amount = '< 3', exitAnalysisIfErrors = TRUE)`
81+
- Validate dataset assumptions automatically when required for analysis validity
82+
- Use footnotes for assumption violations that affect specific cells/values
83+
- Place critical errors that invalidate entire analysis over the results table
84+
85+
### Error Message Guidelines (from jasp-human-guide.md)
86+
- Write clear, actionable error messages that prevent user confusion
87+
- Use `gettextf()` with placeholders for dynamic content: `gettextf("Number of factor levels is %1$s in %2$s", levels, variable)`
88+
- For multiple arguments, use `%1$s`, `%2$s` format for translator clarity
89+
- Use `ngettext()` for singular/plural forms
90+
- Never mark empty strings for translation
91+
- Use UTF-8 encoding for non-ASCII characters: `\u03B2` for β
92+
- Double `%` characters in format strings: `gettextf("%s%% CI for Mean")`
93+
94+
### Testing Requirements
95+
- Unit tests in `tests/testthat/` use jaspTools framework
96+
- Tests run via `jaspTools::testAll()` - takes 300+ seconds, NEVER CANCEL
97+
- Test files correspond to R analysis files (test-*.R matches *.R)
98+
- Update test expected values when changing analysis outputs
99+
100+
## CI/CD Pipeline
101+
- GitHub Actions in `.github/workflows/unittests.yml` runs on every push
102+
- Triggers on changes to R, test, or package files
103+
- Uses jasp-stats/jasp-actions reusable workflow
104+
- No external dependencies (JAGS, igraph) required for this module
105+
106+
## Common Tasks
107+
108+
### Adding New Analysis
109+
1. Create R function in `R/` directory following camelCase naming
110+
2. Add QML interface in `inst/qml/`
111+
3. Define analysis in `inst/Description.qml`
112+
4. Add unit tests in `tests/testthat/`
113+
5. Run `jaspTools::testAll()` to validate (300+ seconds, NEVER CANCEL)
114+
115+
### Modifying Existing Analysis
116+
1. Update R function maintaining existing interface
117+
2. Update QML if adding/changing options
118+
3. Update unit tests and expected results
119+
4. Add upgrade mapping to `inst/Upgrades.qml` if renaming options
120+
5. Run tests: `jaspTools::testAll()` (NEVER CANCEL, 300+ seconds)
121+
122+
### Detailed Development Process
123+
- **Step 1**: Create main analysis function with `jaspResults`, `dataset`, `options` arguments
124+
- **Step 2**: **CRITICAL** - Use `.quitAnalysis()` for `dataset`, `TextField`, `FormulaField` validation only
125+
- **Step 3**: Create output tables/plots with proper dependencies, citations, column specs
126+
- Use `createJaspTable()`, `createJaspPlot()`, `createJaspHtml()` for output elements
127+
- Always set `$dependOn()` for proper caching and state management
128+
- Use containers for grouping related elements, state objects for reusing computed results
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
---
2+
applyTo: "**/R/*.R"
3+
---
4+
5+
# R Instructions
6+
7+
## 1) Core Basics
8+
9+
- **Main entry point (name matters):**
10+
- The R function name **must match** the case-sensitive `"function"` field in `Description.qml`.
11+
- Signature is always:
12+
```r
13+
AnalysisName <- function(jaspResults, dataset, options) { ... }
14+
```
15+
- `jaspResults` is a container that stores all of the analysis output and byproducts (if they are supposed to be kept for later use).
16+
- `dataset` is the loaded dataset in JASP
17+
- `options` are the UI choices from QML; **do not rename** option keys (theyre your API).
18+
19+
- **Recommended structure (3 roles):**
20+
1) **Main function** orchestrates and wires output elements.
21+
2) **create* functions** declare output markup (tables/plots/text).
22+
3) **fill* (or compute*) functions** compute results and fill outputs.
23+
24+
- **Dependencies (cache & reuse):**
25+
Add `$dependOn()` to every output (table/plot/text/container/state) so JASP knows when to reuse or drop it.
26+
Outputs nested within containers inherit all dependencies from the container.
27+
28+
- **Errors:**
29+
- Catch run-time errors with `try(...)` and report via `$setError()`.
30+
- Wrap user-visible text with `gettext()` / `gettextf()` for translation.
31+
32+
---
33+
34+
## 2) Input Validation
35+
36+
Only validate the `dataset`. `options` input is validated in the QML automatically.
37+
38+
Common checks (prefix arguments with the check name):
39+
```r
40+
.hasErrors(
41+
dataset, type = c("factorLevels", "observations", "variance", "infinity", "missingValues"),
42+
factorLevels.target = options$variables,
43+
factorLevels.amount = "< 1",
44+
observations.target = options$variables,
45+
observations.amount = "< 1"
46+
)
47+
```
48+
Other useful checks:
49+
- `limits.min/max` (inclusive bounds),
50+
- `varCovData.target/corFun` (positive-definiteness),
51+
- `modelInteractions` (ensure lower-order terms exist).
52+
53+
---
54+
55+
## 3) Output Components
56+
57+
### Tables — `createJaspTable()`
58+
**Key methods/properties:**
59+
- `$dependOn(<optionNames>)`
60+
- `$addCitation("<text>")`
61+
- `$addColumnInfo(name, title, type = "string|number|integer|pvalue", format = "sf:4;dp:3", combine = FALSE, overtitle = NULL)`
62+
- `$showSpecifiedColumnsOnly <- TRUE` (hide unspecified stats you happen to compute)
63+
- `$setExpectedSize(nRows)` (for long computations)
64+
- `$addFootnote(message, colNames = NULL, rowNames = NULL)`
65+
- `$addRows(list(...))` or `$setData(df)`
66+
- `$setError("<message>")`
67+
68+
**Skeleton:**
69+
```r
70+
.createMyTable <- function(jaspResults, dataset, options, ready) {
71+
if (!is.null(jaspResults[["mainTable"]])) return()
72+
tab <- createJaspTable(title = gettext("My Table"))
73+
tab$dependOn(c("variables", "alpha", "showCI"))
74+
tab$addColumnInfo("variable", gettext("Variable"), "string", combine = TRUE)
75+
tab$addColumnInfo("estimate", gettext("Estimate"), "number")
76+
if (options$showCI) {
77+
over <- gettextf("%f%% CI", 100 * options[["alpha"]])
78+
tab$addColumnInfo("lcl", gettext("Lower"), "number", overtitle = over)
79+
tab$addColumnInfo("ucl", gettext("Upper"), "number", overtitle = over)
80+
}
81+
tab$showSpecifiedColumnsOnly <- TRUE
82+
jaspResults[["mainTable"]] <- tab
83+
if (!ready) return()
84+
.fillMyTable(tab, dataset, options)
85+
}
86+
```
87+
88+
### Plots — `createJaspPlot()`
89+
**Key methods/properties:**
90+
- `$dependOn(<optionNames>)`, `$addCitation()`
91+
- Set `plotObject <- ggplot2::ggplot(...)`
92+
- `$setError("<message>")`
93+
94+
**Skeleton:**
95+
```r
96+
.createMyPlot <- function(jaspResults, dataset, options, ready) {
97+
if (!is.null(jaspResults[["descPlot"]])) return()
98+
plt <- createJaspPlot(title = gettext("My Plot"), width = 400, height = 300)
99+
plt$dependOn(c("variables", "alpha"))
100+
jaspResults[["descPlot"]] <- plt
101+
if (!ready) return()
102+
.fillMyPlot(plt, dataset, options)
103+
}
104+
```
105+
106+
### Text blocks — `createJaspHtml()`
107+
Display formatted messages; can depend on options like other outputs.
108+
```r
109+
if (!is.null(jaspResults[["note"]])) return()
110+
msg <- createJaspHtml(text = gettextf("The variable <b>%s</b> was omitted.", options[["variable"]]))
111+
msg$dependOn(c("variable"))
112+
jaspResults[["note"]] <- msg
113+
```
114+
115+
### Containers — `createJaspContainer()`
116+
Group related outputs; container dependencies propagate to children. Useful for “one-per-variable” sections.
117+
- `$dependOn(...)`, `$setError("<message>")`, `$getError()`
118+
- Nest containers freely.
119+
120+
```r
121+
if (is.null(jaspResults[["descGroup"]])) {
122+
grp <- createJaspContainer(title = gettext("Descriptive Plots"))
123+
grp$dependOn(c("variables", "alpha"))
124+
jaspResults[["descGroup"]] <- grp
125+
} else {
126+
grp <- jaspResults[["descGroup"]]
127+
}
128+
for (v in options[["variables"]]) {
129+
if (!is.null(grp[[v]])) next
130+
p <- createJaspPlot(title = v, width = 480, height = 320)
131+
p$dependOn(optionContainsValue = list(variables = v))
132+
grp[[v]] <- p
133+
}
134+
```
135+
136+
### State (cache) — `createJaspState()`
137+
Cache computed results across reruns (while dependencies hold).
138+
- `$dependOn(...)`
139+
- `$object <- results` (store) / `results <- state$object` (retrieve)
140+
141+
```r
142+
.stateCompute <- function(jaspResults, dataset, options) {
143+
st <- createJaspState()
144+
st$dependOn(c("variables", "alpha"))
145+
jaspResults[["internalResults"]] <- st
146+
res <- colMeans(dataset[options[["variables"]]], na.rm = TRUE)
147+
st$object <- res
148+
}
149+
```
150+
151+
---
152+
153+
## 4) Style & Conventions
154+
155+
- **Follow the project R style guide.** Keep functions short; prefer pure helpers; avoid global state; no I/O or printing in analyses.
156+
- **Naming:**
157+
- Helpers start with a dot, e.g., `.computeFoo()`, `.fillBarTable()`, `.plotBaz()`.
158+
- Stable keys in `jaspResults[["..."]]` (don’t rename them later).
159+
- **Internationalization:** All visible text via `gettext()`/`gettextf()`.
160+
- **Performance:** Read only needed columns; postpone decoding; reuse `createJaspState()` when multiple outputs share results.
161+
- **Robustness:** Validate early; guard long loops with `if (!ready) return()`; wrap risky code in `try()` and call `$setError()`.
162+
- **Reproducibility:** Set column formats explicitly in tables; document assumptions in footnotes/citations.
163+
164+
---
165+
166+
## 5) Minimal main() template (copy/paste)
167+
168+
```r
169+
MyAnalysis <- function(jaspResults, dataset, options) {
170+
171+
ready <- length(options[["variables"]]) > 0
172+
173+
.createMyTable(jaspResults, dataset, options, ready)
174+
.createMyPlot(jaspResults, dataset, options, ready)
175+
}

0 commit comments

Comments
 (0)