beatrina 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +92 -0
  2. package/NOTICES +72 -0
  3. package/README.md +124 -0
  4. package/bin/beatrina.mjs +223 -0
  5. package/bin/cli.mjs +80 -0
  6. package/bin/failsafe.mjs +74 -0
  7. package/bin/identity.mjs +45 -0
  8. package/bin/prova-post.mjs +51 -0
  9. package/bin/sessions.mjs +95 -0
  10. package/bin/shortcut.mjs +151 -0
  11. package/bin/update-check.mjs +55 -0
  12. package/bin/upgrade.mjs +76 -0
  13. package/build-info.json +1 -0
  14. package/carmar_V0.8.6.html +1310 -0
  15. package/check/acceptance.mjs +278 -0
  16. package/check/session.mjs +215 -0
  17. package/engines/js/document-exec.mjs +82 -0
  18. package/engines/js/persist.mjs +214 -0
  19. package/engines/js/worker.mjs +424 -0
  20. package/engines/python/adapter.py +577 -0
  21. package/engines/python/analyze.py +814 -0
  22. package/engines/python/bootstrap.py +309 -0
  23. package/engines/python/dataview.py +735 -0
  24. package/engines/python/debugger.py +346 -0
  25. package/engines/python/document_exec.py +158 -0
  26. package/engines/python/engine.json +28 -0
  27. package/engines/python/handoff.py +118 -0
  28. package/engines/python/worker.py +564 -0
  29. package/engines/r/engine.json +25 -0
  30. package/engines/r/handoff.R +92 -0
  31. package/failsafe/ai-policy.R +255 -0
  32. package/failsafe/ai-store.R +373 -0
  33. package/failsafe/cite.R +418 -0
  34. package/failsafe/journal.R +684 -0
  35. package/failsafe/plugins.R +809 -0
  36. package/failsafe/serve.R +5500 -0
  37. package/host/ai-policy.mjs +218 -0
  38. package/host/deployment.mjs +160 -0
  39. package/host/engine-js.mjs +98 -0
  40. package/host/engine-pool.mjs +383 -0
  41. package/host/engine-python.mjs +228 -0
  42. package/host/engine-r.mjs +206 -0
  43. package/host/engine-stdio.mjs +401 -0
  44. package/host/journal-store.mjs +749 -0
  45. package/host/main.mjs +503 -0
  46. package/host/planes/README.md +41 -0
  47. package/host/planes/ai-store.mjs +327 -0
  48. package/host/planes/ai.mjs +467 -0
  49. package/host/planes/analyze.mjs +397 -0
  50. package/host/planes/cite.mjs +517 -0
  51. package/host/planes/files.mjs +0 -0
  52. package/host/planes/jobs.mjs +704 -0
  53. package/host/planes/journal.mjs +53 -0
  54. package/host/planes/latex.mjs +201 -0
  55. package/host/planes/mcp.mjs +493 -0
  56. package/host/planes/pair.mjs +325 -0
  57. package/host/planes/pipe-term.mjs +122 -0
  58. package/host/planes/plugins.mjs +112 -0
  59. package/host/planes/proc-tree.mjs +76 -0
  60. package/host/planes/sessions.mjs +434 -0
  61. package/host/planes/settings.mjs +164 -0
  62. package/host/planes/terminal.mjs +286 -0
  63. package/host/planes/test-file.mjs +80 -0
  64. package/host/planes/update.mjs +214 -0
  65. package/host/plugin-store.mjs +838 -0
  66. package/host/server.mjs +441 -0
  67. package/host/settings.mjs +379 -0
  68. package/host/update-record.mjs +59 -0
  69. package/host/user-dirs.mjs +117 -0
  70. package/host/windows-runtime.mjs +145 -0
  71. package/host/worker-plane.mjs +713 -0
  72. package/host/ws.mjs +190 -0
  73. package/kernel/analyze.R +668 -0
  74. package/kernel/deployment.R +165 -0
  75. package/kernel/examples/NOTICE.md +38 -0
  76. package/kernel/examples/tna-complete-tutorial.Rmd +210 -0
  77. package/kernel/fileio.R +656 -0
  78. package/kernel/index.html +96 -0
  79. package/kernel/job-run.R +391 -0
  80. package/kernel/jobs.R +276 -0
  81. package/kernel/kernel-protocol +1 -0
  82. package/kernel/kernel-version +1 -0
  83. package/kernel/kernel.R +671 -0
  84. package/kernel/knitr-run.R +245 -0
  85. package/kernel/latex.R +609 -0
  86. package/kernel/mcp/carmar-mcp.mjs +516 -0
  87. package/kernel/notebook-page.R +67 -0
  88. package/kernel/plugins/csl/apa/apa.csl +2273 -0
  89. package/kernel/plugins/csl/apa/plugin.json +19 -0
  90. package/kernel/plugins/csl/chicago-author-date/chicago-author-date.csl +4216 -0
  91. package/kernel/plugins/csl/chicago-author-date/plugin.json +19 -0
  92. package/kernel/plugins/csl/harvard-cite-them-right/harvard-cite-them-right.csl +316 -0
  93. package/kernel/plugins/csl/harvard-cite-them-right/plugin.json +19 -0
  94. package/kernel/plugins/csl/ieee/ieee.csl +519 -0
  95. package/kernel/plugins/csl/ieee/plugin.json +19 -0
  96. package/kernel/plugins/csl/modern-language-association/modern-language-association.csl +1184 -0
  97. package/kernel/plugins/csl/modern-language-association/plugin.json +19 -0
  98. package/kernel/plugins/csl/nature/nature.csl +189 -0
  99. package/kernel/plugins/csl/nature/plugin.json +19 -0
  100. package/kernel/plugins/latex/apa7/apa7.json +14 -0
  101. package/kernel/plugins/latex/apa7/plugin.json +19 -0
  102. package/kernel/plugins/latex/elsarticle/elsarticle.json +14 -0
  103. package/kernel/plugins/latex/elsarticle/plugin.json +19 -0
  104. package/kernel/plugins/latex/ieeetran/ieeetran.json +10 -0
  105. package/kernel/plugins/latex/ieeetran/plugin.json +19 -0
  106. package/kernel/project.R +131 -0
  107. package/kernel/settings.R +410 -0
  108. package/kernel/sniff.R +769 -0
  109. package/kernel/worker-boot.R +22 -0
  110. package/kernel/worker.R +3496 -0
  111. package/lib/agent-authoring-contract.js +547 -0
  112. package/lib/cell-kinds.js +108 -0
  113. package/lib/engine-labels.js +324 -0
  114. package/package.json +32 -0
package/kernel/jobs.R ADDED
@@ -0,0 +1,276 @@
1
+ #
2
+ # jobs.R — what a development task IS, decided without starting one.
3
+ #
4
+ # ── the shape, and why it is this shape ───────────────────────────────────
5
+ #
6
+ # Stage 5 slice 3 and Stage 6 item 7 ask for the same thing from two
7
+ # directions: package tasks that do not consume the interactive worker, and
8
+ # detached jobs that outlive the run that started them. Both are one model —
9
+ # a THIRD kind of child process, beside the worker (evaluates the user's
10
+ # notebook) and the analyzer (evaluates nothing at all).
11
+ #
12
+ # A job sits between them: it DOES evaluate code, which is why it may not be
13
+ # the analyzer, and it evaluates code that is not the notebook's, which is
14
+ # why it may not be the worker. `devtools::check()` runs the package's own
15
+ # tests in a fresh process; doing that in the session would mean the user's
16
+ # workspace is gone and their Stop button is a lie for four minutes.
17
+ #
18
+ # ── the one security decision in this file ────────────────────────────────
19
+ #
20
+ # THE WIRE NEVER CARRIES CODE. The browser sends a task NAME out of a fixed
21
+ # vocabulary and a directory; this file decides whether that pair is
22
+ # runnable, and the supervisor builds the argument vector. Nothing from the
23
+ # socket is ever pasted into an R expression or a shell string — task names
24
+ # are matched against a list, and the root and filter travel in the child's
25
+ # ENVIRONMENT, where they are data to `Sys.getenv()` and can never be parsed.
26
+ #
27
+ # That is the same rule spike/broker.R follows for systemctl, and it is worth
28
+ # stating as a rule rather than a habit: `exec` already lets a page run
29
+ # arbitrary R, so this is not the only door to evaluation — but it is a door
30
+ # whose vocabulary is small enough to read in one screen, and keeping it that
31
+ # way is cheaper than auditing an interpolation.
32
+ #
33
+ # Pure by construction: nothing here starts a process, writes a file, or
34
+ # reads a socket, so spike/test-jobs.R can exercise every branch without one.
35
+
36
+ #' Is this directory the root of an R package?
37
+ #'
38
+ #' The test is a DESCRIPTION carrying a `Package:` field, not merely a file
39
+ #' called DESCRIPTION — Bioconductor build dirs, some data packages and a
40
+ #' stray copied file all put that name somewhere it does not mean this.
41
+ #'
42
+ #' A DESCRIPTION that cannot be parsed answers FALSE. That is a decision, not
43
+ #' a swallowed error: "this is not a package root" is exactly the right
44
+ #' answer for a malformed file, and the caller's next step (say so, offer the
45
+ #' folder anyway) is the same either way.
46
+ #'
47
+ #' @param dir Directory to test.
48
+ #' @return TRUE when `dir` is an R package root.
49
+ is_package_root <- function(dir) {
50
+ desc <- file.path(dir, "DESCRIPTION")
51
+ if (!file.exists(desc) || dir.exists(desc)) return(FALSE)
52
+ fields <- tryCatch(read.dcf(desc, fields = "Package"),
53
+ error = function(e) NULL,
54
+ warning = function(w) NULL)
55
+ !is.null(fields) && nrow(fields) >= 1L &&
56
+ !is.na(fields[1L, 1L]) && nzchar(trimws(fields[1L, 1L]))
57
+ }
58
+
59
+ #' Every directory from `path` up to the filesystem root.
60
+ #'
61
+ #' A parent chain cannot be vectorised — each step is computed from the last —
62
+ #' so this is a bounded `while`, and the bound is the point: a symlink loop or
63
+ #' a pathological path must not spin the supervisor's event loop.
64
+ #'
65
+ #' @param path Starting path.
66
+ #' @param max_up Most ancestors to walk.
67
+ #' @return Character vector, nearest first.
68
+ path_ancestors <- function(path, max_up = 40L) {
69
+ out <- character(0)
70
+ current <- path
71
+ while (length(out) < max_up) {
72
+ out <- c(out, current)
73
+ parent <- dirname(current)
74
+ if (identical(parent, current)) break # "/" is its own dirname
75
+ current <- parent
76
+ }
77
+ out
78
+ }
79
+
80
+ #' Find the package root at or above a path.
81
+ #'
82
+ #' @param path A file or directory inside the package.
83
+ #' @return The package root, or "" when there is none.
84
+ find_package_root <- function(path) {
85
+ if (!is.character(path) || length(path) != 1L || is.na(path) || !nzchar(path)) return("")
86
+ start <- if (dir.exists(path)) path else dirname(path)
87
+ start <- tryCatch(normalizePath(start, mustWork = FALSE), error = function(e) start)
88
+ Find(is_package_root, path_ancestors(start)) %||% ""
89
+ }
90
+
91
+ #' The package's name, for a label a person recognises.
92
+ #'
93
+ #' @param root A package root.
94
+ #' @return The `Package:` field, or "" when unreadable.
95
+ package_name <- function(root) {
96
+ if (!nzchar(root) || !is_package_root(root)) return("")
97
+ fields <- tryCatch(read.dcf(file.path(root, "DESCRIPTION"), fields = "Package"),
98
+ error = function(e) NULL, warning = function(w) NULL)
99
+ # unname(): read.dcf() returns a matrix whose dimnames survive the [1,1]
100
+ # extraction, so the "obvious" version returns a string NAMED "Package".
101
+ # It prints identically, compares unequal to the plain string, and is the
102
+ # kind of stowaway attribute that turns into a JSON object on the wire.
103
+ if (is.null(fields)) "" else unname(trimws(fields[1L, 1L]))
104
+ }
105
+
106
+ # ── the vocabulary ──────────────────────────────────────────────────────────
107
+ #
108
+ # Every task declares WHAT IT ACTS ON, and that field is the reason this list
109
+ # is a table rather than a vector of names. Six tasks act on a package root;
110
+ # `render` acts on a single document, which has no DESCRIPTION and never
111
+ # will. Before `target` existed, `job_spec()` demanded a package root for
112
+ # everything — so rendering a standalone .qmd was refused for not being an R
113
+ # package, which is a true statement and a useless one.
114
+ #
115
+ # `pkg` is the package the task needs INSTALLED. It is carried so the child
116
+ # can say "devtools is not installed" instead of failing with an
117
+ # object-not-found deep inside a call the reader never made. `render` has no
118
+ # fixed answer there — it depends on the document — so it carries NA and the
119
+ # child decides; see pick_renderer() in job-run.R.
120
+ JOB_TASKS <- list(
121
+ load = list(label = "Load", running = "Loading", target = "package", pkg = "pkgload"),
122
+ document = list(label = "Document", running = "Documenting", target = "package", pkg = "devtools"),
123
+ test = list(label = "Test", running = "Testing", target = "package", pkg = "testthat"),
124
+ check = list(label = "Check", running = "Checking", target = "package", pkg = "devtools"),
125
+ build = list(label = "Build", running = "Building", target = "package", pkg = "devtools"),
126
+ install = list(label = "Install", running = "Installing", target = "package", pkg = "devtools"),
127
+ render = list(label = "Render", running = "Rendering", target = "document", pkg = NA_character_),
128
+ latex = list(label = "Compile", running = "Compiling", target = "document", pkg = NA_character_)
129
+ )
130
+
131
+ #' Documents this kernel will render. Lower-cased extension, no dot.
132
+ #'
133
+ #' `.md` is here because both renderers accept it; a plain Markdown file with
134
+ #' no chunks still renders, and refusing it would be a distinction the reader
135
+ #' does not share.
136
+ RENDER_EXTENSIONS <- c("qmd", "rmd", "md")
137
+
138
+ # The formats offered, and "" FIRST because it is the default and the default
139
+ # matters: an empty format passes no `--to` at all, so the document's own YAML
140
+ # decides. A document whose header says `format: revealjs` must render as
141
+ # revealjs when the reader presses Render without choosing anything —
142
+ # defaulting to html would silently overrule the file.
143
+ RENDER_FORMATS <- c("", "html", "pdf", "docx")
144
+
145
+ #' Is this a single, plain, non-empty string? Mirrors serve.R's scalar_chr —
146
+ #' R's coercion rules are exactly permissive enough to turn a JSON array into
147
+ #' something that runs but is not what anybody meant.
148
+ #' @param x Value from the wire.
149
+ #' @return TRUE for a length-1 non-NA character vector.
150
+ job_scalar <- function(x) is.character(x) && length(x) == 1L && !is.na(x)
151
+
152
+ # A test filter is a regular expression testthat matches against file names.
153
+ # It is data, never code — but it is still bounded and stripped of control
154
+ # characters, because it is echoed into a label the UI renders and into the
155
+ # audit stream, where a newline would forge a record.
156
+ JOB_FILTER_MAX <- 200L
157
+
158
+ #' The lower-cased extension of a path, with no dot.
159
+ #' @param path A file path.
160
+ #' @return The extension, or "".
161
+ path_extension <- function(path) {
162
+ base <- basename(path)
163
+ if (!grepl(".", base, fixed = TRUE)) return("")
164
+ tolower(sub("^.*\\.", "", base))
165
+ }
166
+
167
+ #' Decide whether one task may run, and exactly what would run.
168
+ #'
169
+ #' The whole decision in one pure function, returning it whole — the same
170
+ #' shape as `carmar_deployment()`, and for the same reason: three callers
171
+ #' (the supervisor, the tests, the UI's error message) must not each
172
+ #' re-derive it and drift.
173
+ #'
174
+ #' `target` is the ONE thing the task acts on: a package root for a package
175
+ #' task, a document for `render`. Which of those it must be is the task's own
176
+ #' declaration, never the caller's guess.
177
+ #'
178
+ #' @param task Task name from the wire.
179
+ #' @param target Directory or file from the wire.
180
+ #' @param opts Named list of extras from the wire: `filter` (test), `format`
181
+ #' (render). Unknown members are ignored rather than refused — a page one
182
+ #' version ahead must not be an error.
183
+ #' @return list(ok, error, task, target, root, label, package, env) — `env` is
184
+ #' the named character vector of environment variables for the child, and is
185
+ #' the ONLY channel by which wire data reaches it.
186
+ job_spec <- function(task, target, opts = list()) {
187
+ bad <- function(msg) list(ok = FALSE, error = msg)
188
+ if (!job_scalar(task) || !nzchar(task)) return(bad("no task was named"))
189
+ if (!task %in% names(JOB_TASKS)) {
190
+ return(bad(sprintf("'%s' is not a task this kernel runs (%s)",
191
+ task, paste(names(JOB_TASKS), collapse = ", "))))
192
+ }
193
+ spec <- JOB_TASKS[[task]]
194
+ if (!job_scalar(target) || !nzchar(target)) {
195
+ return(bad(if (identical(spec$target, "document")) "no document was named"
196
+ else "no folder was named"))
197
+ }
198
+ filter <- if (job_scalar(opts$filter)) opts$filter else ""
199
+ format <- if (job_scalar(opts$format)) opts$format else ""
200
+ latex_env <- if (identical(task, "latex")) tryCatch(latex_options(opts), error = function(e) e) else character()
201
+ if (inherits(latex_env, "error")) return(bad(conditionMessage(latex_env)))
202
+
203
+ if (identical(spec$target, "package")) {
204
+ if (!dir.exists(target)) return(bad(sprintf("'%s' is not a folder", target)))
205
+ target <- tryCatch(normalizePath(target, mustWork = TRUE), error = function(e) target)
206
+ if (!is_package_root(target)) {
207
+ return(bad(paste0("'", target, "' is not an R package — no DESCRIPTION with a Package field. ",
208
+ "Open a file inside the package and try again.")))
209
+ }
210
+ root <- target
211
+ } else {
212
+ if (!file.exists(target) || dir.exists(target)) {
213
+ return(bad(sprintf("'%s' is not a file", target)))
214
+ }
215
+ target <- tryCatch(normalizePath(target, mustWork = TRUE), error = function(e) target)
216
+ ext <- path_extension(target)
217
+ extensions <- if (identical(task, "latex")) "tex" else RENDER_EXTENSIONS
218
+ if (!ext %in% extensions) {
219
+ return(bad(sprintf("CarmaR renders %s files; '%s' is a .%s",
220
+ paste0(".", extensions, collapse = ", "), basename(target),
221
+ if (nzchar(ext)) ext else "(no extension)")))
222
+ }
223
+ # The document's own folder, so a job still has one place it belongs —
224
+ # the pane labels by it, and a relative output path resolves against it.
225
+ root <- dirname(target)
226
+ }
227
+
228
+ if (nzchar(filter) && !identical(task, "test")) {
229
+ return(bad("a filter only applies to the test task"))
230
+ }
231
+ filter <- gsub("[[:cntrl:]]", "", filter)
232
+ if (nchar(filter) > JOB_FILTER_MAX) return(bad("the test filter is too long"))
233
+
234
+ if (nzchar(format) && !identical(task, "render")) {
235
+ return(bad("a format only applies to the render task"))
236
+ }
237
+ if (!format %in% RENDER_FORMATS) {
238
+ return(bad(sprintf("'%s' is not a format this kernel renders (%s)",
239
+ format, paste(setdiff(RENDER_FORMATS, ""), collapse = ", "))))
240
+ }
241
+
242
+ list(
243
+ ok = TRUE, error = "",
244
+ task = task, target = target, root = root,
245
+ label = spec$label, running = spec$running, targets = spec$target,
246
+ package = if (identical(spec$target, "package")) package_name(root) else "",
247
+ needs = spec$pkg,
248
+ env = c(CARMAR_JOB_TASK = task, CARMAR_JOB_TARGET = target,
249
+ CARMAR_JOB_ROOT = root, CARMAR_JOB_FILTER = filter,
250
+ CARMAR_JOB_FORMAT = format, latex_env)
251
+ )
252
+ }
253
+
254
+ #' A sentence naming what is about to happen, before it happens.
255
+ #'
256
+ #' The same principle as Stage 6's `describePlan`: nothing runs unannounced,
257
+ #' and a label the reader cannot predict from the button they pressed is a
258
+ #' click-through waiting to happen.
259
+ #'
260
+ #' @param spec A successful `job_spec()`.
261
+ #' @return One line of text.
262
+ job_title <- function(spec) {
263
+ if (identical(spec$task, "latex")) return(sprintf("Compiling %s", basename(spec$target)))
264
+ if (identical(spec$targets, "document")) {
265
+ format <- spec$env[["CARMAR_JOB_FORMAT"]]
266
+ return(sprintf("Rendering %s%s", basename(spec$target),
267
+ if (nzchar(format)) paste0(" to ", format) else ""))
268
+ }
269
+ name <- if (nzchar(spec$package)) spec$package else basename(spec$root)
270
+ filter <- spec$env[["CARMAR_JOB_FILTER"]]
271
+ if (identical(spec$task, "test") && nzchar(filter)) {
272
+ sprintf("%s %s — tests matching '%s'", spec$running, name, filter)
273
+ } else {
274
+ sprintf("%s %s", spec$running, name)
275
+ }
276
+ }
@@ -0,0 +1 @@
1
+ 1
@@ -0,0 +1 @@
1
+ 0.8.6