From 7cad98b4347b9daa88d58d3e679a2addfbd04ba1 Mon Sep 17 00:00:00 2001 From: Jared Knowles Date: Sun, 23 Aug 2026 23:33:48 -0400 Subject: [PATCH] chore(packaging): initialise compass project tracking Adds pm/compass.toml with five workstreams (theme, logo, quarto, helpers, packaging), the journal and decision-record scaffold, and a composed .roborev.toml carrying nine project-specific review rules derived from the package's actual conventions. Also adds AGENTS.md as the cross-tool conventions file, wires project memory into .agent/memory/ so it travels between machines, and untracks tests/testthat/Rplots.pdf, which churned on every test run. --- .agent/memory/.gitkeep | 0 .gitignore | 15 ++++-- .roborev.toml | 62 ++++++++++++++++++++++ AGENTS.md | 87 +++++++++++++++++++++++++++++++ pm/JOURNAL.md | 14 +++++ pm/compass.toml | 105 ++++++++++++++++++++++++++++++++++++++ pm/decisions/README.md | 11 ++++ tests/testthat/Rplots.pdf | Bin 3830 -> 0 bytes 8 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 .agent/memory/.gitkeep create mode 100644 .roborev.toml create mode 100644 AGENTS.md create mode 100644 pm/JOURNAL.md create mode 100644 pm/compass.toml create mode 100644 pm/decisions/README.md delete mode 100644 tests/testthat/Rplots.pdf diff --git a/.agent/memory/.gitkeep b/.agent/memory/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index d44df33..434e3f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ -.Rproj.user -.Rhistory -.RData -.Ruserdata +.Rproj.user +.Rhistory +.RData +.Ruserdata +.compass-cache/ +# roborev snapshots +/.roborev/ + +# Test run debris +tests/testthat/Rplots.pdf +tests/testthat/_problems/ diff --git a/.roborev.toml b/.roborev.toml new file mode 100644 index 0000000..7b16901 --- /dev/null +++ b/.roborev.toml @@ -0,0 +1,62 @@ +# roborev configuration, initialised by compass. +# Reviews are queued to a background daemon -- they never block a commit. + +post_commit_review = 'commit' +excluded_commit_patterns = ['WIP', 'chore:', 'docs:', 'Merge '] + +review_guidelines = ''' +# --- compass:begin (generated -- edit the sources, not this) --- +- Prefer returning new values to mutating arguments in place. A function that edits + its caller's object is a bug waiting for a second caller. +- Validate at system boundaries -- user input, API responses, file contents, config. + Fail fast with a message naming the field and the file. +- Never swallow an error. Handle it or let it propagate; a bare catch that continues + is worse than a crash. +- No hardcoded secrets, tokens, or credentials, and no secrets in log output or error + messages. +- Parameterise every query. String-built SQL is a defect even when the input looks safe. +- Keep functions under roughly 50 lines and files under roughly 400. Flag nesting + deeper than four levels. +- No magic numbers or hardcoded paths -- name them as constants or read them from config. +- New behaviour needs a test. A bug fix needs a test that fails without the fix. +- Prose a person reads -- an issue title or body, a journal entry, a decision record, + the narrative on the status board -- names the action or the thing, not the shape of + the machinery. Flag "gate", "seam", "surface area", "load-bearing", "first-class", + "primitive", "blast radius". A project's own defined vocabulary is not the target. +- Use the native pipe `|>`, not magrittr `%>%`. +- snake_case for objects and functions; UPPER_SNAKE for constants. Never use `.` as a + word separator in a function name -- it collides with S3 dispatch. +- Validate arguments at the top of exported functions with `stopifnot()` or an explicit + check, and say which argument was wrong. +- Never `setDT()`, `set()`, or otherwise modify by reference a data.table the caller + still owns. `as.data.table()` copies; use it. +- Prefer `vapply()` to `sapply()` -- `sapply()` silently returns a list when the type + varies, which turns a type error into a downstream mystery. +- Use `seq_len(n)` / `seq_along(x)`, never `1:n`, which iterates backwards when n is 0. +- Compare strings with `==` only after checking for NA; use `identical()` for scalars + where NA would be wrong. +- Do not call `library()` inside package or module files; attach packages in scripts and + test helpers only. +- Namespace-qualify calls into other packages (`stats::sd`) in code that is sourced. +- Every exported function needs roxygen with `@param` for each argument (type, meaning, + and why the default is what it is) and `@return`. Add `@examples` for exported API. +- Declare dependencies in DESCRIPTION. Prefer base R or an existing dependency over + adding a new one; a package with zero hard deps is worth keeping that way. +- Signal errors with `stop()` carrying a condition class, so callers can catch the kind + rather than matching on message text. +- Keep internals internal. Export only what a user needs; an accidentally exported + helper becomes an API you have to keep. +- Tests use testthat edition 3. Each test is self-sufficient -- no reliance on state + left by an earlier test or on a fixture built elsewhere in the file. +- Prefer duplication in tests over a helper that hides what is being asserted. +- Brand colors come from `civilytics_colors`; never write a hex literal in theme, scale, logo, or table code. `R/flextable.R` still carries off-brand Bootstrap defaults (#2c3e50, #f0f0eb, #888888, #cccccc, #555555) -- do not add more. +- No tidyverse dependency. Imports is base R plus ggplot2, grid/gridExtra, png/jpeg, stringr, stringdist, showtext/sysfonts, jsonlite. Reject dplyr, purrr, magrittr, tibble, and data.table; use base R idioms. +- NAMESPACE is roxygen-generated. Declare imports with `@importFrom` in the roxygen block and re-run roxygen; never hand-edit NAMESPACE. +- Themes default to a transparent background (`paper_bg = FALSE`) so plots composite onto any Quarto, Reveal, or Typst background. Making an opaque background the default is a regression, not a preference. +- Theme functions thread `ink`/`paper`/`accent` into ggplot2 4.0's base-theme parameters rather than setting element colors ad hoc. ggplot2 >= 4.0 is a hard dependency, so use S7 `@` property access on plot and theme objects, not `$`. +- User-facing progress goes through `message()` so callers can suppress it. No `cat()` or `print()` in package code. +- Nothing personal or client-identifying is vendored into `inst/` -- brand assets only. Version 0.3.2 removed headshots for exactly this reason. +- The camelCase exports (`countCleanr`, `dbSafeNames`, `simpleCap`, `waldInterval`, `countDots`, `countNA`, `findDots`, `nvals`) are frozen public API; do not rename them. New functions are snake_case. +- `R/theme.R` and `R/logo.R` are long by design -- one file per surface, with dense roxygen. File length is not a finding in this package; flag a single function over roughly 80 lines instead. +# --- compass:end --- +''' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c8f2951 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,87 @@ +# AGENTS.md + +`civilytics` is the house R package for Civilytics Consulting: a ggplot2 brand theme +system, curated palettes, logo composition, Quarto/Typst/Reveal templates, and +data-wrangling helpers for public-sector analysis. It is a library other projects +depend on, so a breaking change here breaks reports that are already published. + +## Project tracking + +This repo is managed with compass. `pm/compass.toml` +defines the workstreams; `pm/JOURNAL.md` records sessions; `pm/decisions/` holds +numbered, immutable decision records. Ask compass where things stand rather than +reading the config back. + +Workstreams (the `ws/` label on every issue): + +| `ws/` | covers | +|---|---| +| `theme` | `R/theme.R`, `R/colors.R`, `R/fonts.R` — themes, palettes, font loading | +| `logo` | `R/logo.R`, `R/flextable.R`, `inst/img/` — logo and branded output | +| `quarto` | `R/quarto.R`, `inst/quarto/` — HTML, PDF, Typst, Reveal templates | +| `helpers` | `R/utils.R`, `R/prop_conf.R`, `R/join_utilities.R`, `R/db.R`, `R/notifications.R` | +| `packaging` | `DESCRIPTION`, `NAMESPACE`, `Makefile`, `Dockerfile`, `.gitea/workflows/` | + +## Commit cadence + +One coherent unit per commit, subject line: + +``` +type(ws): subject (#N) +``` + +`type` is one of `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`. +`ws` is a workstream id from the table above. `#N` is the issue, when there is one. + +## Conventions + +**Dependencies.** Base R plus ggplot2 (>= 4.0), grid/gridExtra, png/jpeg, stringr, +stringdist, showtext/sysfonts, jsonlite. No tidyverse: no dplyr, purrr, magrittr, +tibble, or data.table. Prefer an existing dependency or base R over adding one — +this package is installed into other people's environments. + +**Brand colors** live in `civilytics_colors` and nowhere else. Never write a hex +literal in theme, scale, logo, or table code. (`R/flextable.R` still carries +off-brand Bootstrap defaults; that is known debt, not a pattern to copy.) + +**Themes** default to a transparent background (`paper_bg = FALSE`) so plots +composite onto any Quarto, Reveal, or Typst background. Thread `ink`, `paper`, and +`accent` into ggplot2 4.0's base-theme parameters rather than setting element colors +one at a time. ggplot2 >= 4.0 is a hard dependency, so use S7 `@` property access on +plot and theme objects, not `$`. + +**Naming.** New functions are snake_case. The camelCase exports (`countCleanr`, +`dbSafeNames`, `simpleCap`, `waldInterval`, `countDots`, `countNA`, `findDots`, +`nvals`) are frozen public API — do not rename them. + +**Documentation.** Roxygen generates both `man/` and `NAMESPACE`. Declare imports +with `@importFrom` in the roxygen block; never hand-edit `NAMESPACE`. Every exported +function needs `@param` for each argument and `@return`; exported API needs +`@examples`. Re-run roxygen in the same commit as the code change — compass files +documentation debt when code moves and its `man/` pages do not. + +**Output.** User-facing progress goes through `message()` so callers can suppress it. +No `cat()` or `print()` in package code. + +**Assets.** Nothing personal or client-identifying is vendored into `inst/` — brand +assets only. Version 0.3.2 removed headshots for exactly this reason. + +## Testing + +testthat edition 3, one file per source file (`tests/testthat/test_theme.R` etc.). +Tests are self-sufficient: no reliance on state left by an earlier test. New +behaviour needs a test; a bug fix needs a test that fails without the fix. + +```zsh +make check # R CMD check --no-manual against a built tarball +Rscript -e 'devtools::test()' +``` + +CI runs `R CMD check` on `rocker/r-ver:4.6` via `.gitea/workflows/check.yaml` for +every push and PR to `master`. + +## Release + +Bump `Version` in `DESCRIPTION` and add a `NEWS.md` entry under **New features**, +**Bug fixes**, or **Internal**. `NEWS.md` is the changelog; compass tracks the work +that led to it, not the release itself. diff --git a/pm/JOURNAL.md b/pm/JOURNAL.md new file mode 100644 index 0000000..b34a322 --- /dev/null +++ b/pm/JOURNAL.md @@ -0,0 +1,14 @@ +# Project journal + +Append-only, newest first. **Entries are never edited** — the value of this file is +that it records what was believed at the time, including the parts that turned out +wrong. Where things stand *today* is in `STATUS.md`, which is generated. + +Four lines per entry. The analysis belongs in the issue or the decision record; this +file carries the reasoning and the pointers. + +- **Why** — the driver. The one line git cannot reconstruct later. +- **Obligates** — issues this change created elsewhere. Numbers, not prose. +- **Refs** — commits, issues, decision records. + +--- diff --git a/pm/compass.toml b/pm/compass.toml new file mode 100644 index 0000000..122eb39 --- /dev/null +++ b/pm/compass.toml @@ -0,0 +1,105 @@ +[project] +name = "civilytics" +forge = "Civilytics/civilyticsR" + +[[workstream]] +id = "theme" +title = "Themes, palettes, and fonts" +paths = [ + "R/theme.R", + "R/colors.R", + "R/fonts.R", + "tests/testthat/test_theme.R", +] +docs = [ + "man/theme_civilytics*.Rd", + "man/scale_color_civilytics.Rd", + "man/scale_fill_civilytics.Rd", + "man/civilytics_pal*.Rd", + "man/civilytics_colors.Rd", + "man/civilytics_load_fonts.Rd", + "README.Rmd", +] + +[[workstream]] +id = "logo" +title = "Logo and branded output composition" +paths = [ + "R/logo.R", + "R/flextable.R", + "inst/img/**", + "tests/testthat/test_logo.R", + "tests/testthat/test_flextable.R", +] +docs = [ + "man/*logo*.Rd", + "man/*flextable*.Rd", + "man/plot_jpeg.Rd", + "man/get_png.Rd", + "man/has_caption.Rd", + "man/measure_caption.Rd", + "README.Rmd", +] + +[[workstream]] +id = "quarto" +title = "Quarto themes and publishing templates" +paths = [ + "R/quarto.R", + "inst/quarto/**", +] +docs = [ + "man/use_civilytics_*.Rd", + "man/quarto-helpers.Rd", + "inst/quarto/examples/*.qmd", +] + +[[workstream]] +id = "helpers" +title = "Analysis and workflow helpers" +paths = [ + "R/utils.R", + "R/prop_conf.R", + "R/join_utilities.R", + "R/db.R", + "R/notifications.R", + "tests/testthat/test_utils.R", + "tests/testthat/test_propint.R", + "tests/testthat/test_joins.R", + "tests/testthat/test_db.R", + "tests/testthat/test_notifications.R", +] +docs = [ + "README.Rmd", + "NEWS.md", +] + +[[workstream]] +id = "packaging" +title = "Package infrastructure and release" +paths = [ + "DESCRIPTION", + "NAMESPACE", + "Makefile", + "Dockerfile", + ".Rbuildignore", + ".gitea/workflows/**", + "tests/testthat.R", +] +docs = [ + "NEWS.md", + "README.Rmd", +] + +[roborev] +project_guidelines = [ + "Brand colors come from `civilytics_colors`; never write a hex literal in theme, scale, logo, or table code. `R/flextable.R` still carries off-brand Bootstrap defaults (#2c3e50, #f0f0eb, #888888, #cccccc, #555555) -- do not add more.", + "No tidyverse dependency. Imports is base R plus ggplot2, grid/gridExtra, png/jpeg, stringr, stringdist, showtext/sysfonts, jsonlite. Reject dplyr, purrr, magrittr, tibble, and data.table; use base R idioms.", + "NAMESPACE is roxygen-generated. Declare imports with `@importFrom` in the roxygen block and re-run roxygen; never hand-edit NAMESPACE.", + "Themes default to a transparent background (`paper_bg = FALSE`) so plots composite onto any Quarto, Reveal, or Typst background. Making an opaque background the default is a regression, not a preference.", + "Theme functions thread `ink`/`paper`/`accent` into ggplot2 4.0's base-theme parameters rather than setting element colors ad hoc. ggplot2 >= 4.0 is a hard dependency, so use S7 `@` property access on plot and theme objects, not `$`.", + "User-facing progress goes through `message()` so callers can suppress it. No `cat()` or `print()` in package code.", + "Nothing personal or client-identifying is vendored into `inst/` -- brand assets only. Version 0.3.2 removed headshots for exactly this reason.", + "The camelCase exports (`countCleanr`, `dbSafeNames`, `simpleCap`, `waldInterval`, `countDots`, `countNA`, `findDots`, `nvals`) are frozen public API; do not rename them. New functions are snake_case.", + "`R/theme.R` and `R/logo.R` are long by design -- one file per surface, with dense roxygen. File length is not a finding in this package; flag a single function over roughly 80 lines instead.", +] diff --git a/pm/decisions/README.md b/pm/decisions/README.md new file mode 100644 index 0000000..338a708 --- /dev/null +++ b/pm/decisions/README.md @@ -0,0 +1,11 @@ +# Decisions + +One file per decision, numbered and immutable. A decision that changes is superseded +by a new record, never edited in place — the old reasoning is the point. + +The table below is **generated** by `compass:decide`. Do not hand-edit it. + + +| # | Date | Decision | Status | +|---|---|---|---| + diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf deleted file mode 100644 index 9fbe60d47725ccda75b7bc9b7f78d8ed9890ff5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3830 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 Ml&Y$^y#?xj0qqED`Tzg`