|
| 1 | +<!-- |
| 2 | + Licensed to the Apache Software Foundation (ASF) under one |
| 3 | + or more contributor license agreements. See the NOTICE file |
| 4 | + distributed with this work for additional information |
| 5 | + regarding copyright ownership. The ASF licenses this file |
| 6 | + to you under the Apache License, Version 2.0 (the |
| 7 | + "License"); you may not use this file except in compliance |
| 8 | + with the License. You may obtain a copy of the License at |
| 9 | +
|
| 10 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +
|
| 12 | + Unless required by applicable law or agreed to in writing, |
| 13 | + software distributed under the License is distributed on an |
| 14 | + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | + KIND, either express or implied. See the License for the |
| 16 | + specific language governing permissions and limitations |
| 17 | + under the License. |
| 18 | +--> |
| 19 | +--- |
| 20 | +name: groovysh |
| 21 | +description: Guidance for changes in subprojects/groovy-groovysh/ — REPL command implementations, JLine integration, vendored fork files, and the layered terminal-aware test stack. Use when modifying anything in that subproject's tree, bumping the JLine version, or syncing the vendored fork files against upstream. |
| 22 | +license: Apache-2.0 |
| 23 | +compatibility: claude, codex, copilot, cursor, gemini, aider |
| 24 | +metadata: |
| 25 | + audience: contributors to apache/groovy |
| 26 | + scope: subproject-groovy-groovysh |
| 27 | +--- |
| 28 | + |
| 29 | +# groovysh |
| 30 | + |
| 31 | +Use this skill for work in the `subprojects/groovy-groovysh/` tree — |
| 32 | +the interactive Groovy REPL. The subproject is unusual in two ways: |
| 33 | + |
| 34 | +1. It vendors files from JLine because we can't depend on the upstream |
| 35 | + `jline-groovy` artifact (circular dependency on Groovy itself). |
| 36 | +2. Its tests touch a real JLine `Terminal`, making them more |
| 37 | + platform-sensitive than the rest of the codebase. |
| 38 | + |
| 39 | +This skill layers on top of [`groovy-tests`](../groovy-tests/SKILL.md) — |
| 40 | +load both together when adding tests for groovysh code. |
| 41 | + |
| 42 | +## When to use this skill |
| 43 | + |
| 44 | +**Use it for:** |
| 45 | + |
| 46 | +- REPL command changes (`subprojects/groovy-groovysh/src/main/groovy/.../jline/`). |
| 47 | +- Anything terminal-related: writing tests, integrating JLine APIs, image rendering. |
| 48 | +- Bumping the JLine version in `versions.properties`. |
| 49 | +- Syncing vendored fork files against upstream JLine. |
| 50 | + |
| 51 | +**Don't use it for:** |
| 52 | + |
| 53 | +- Pure compiler/runtime changes elsewhere — use [`groovy-internals`](../groovy-internals/SKILL.md). |
| 54 | +- Changes to the project-wide build (root `build.gradle`, `build-logic/`) — use [`groovy-build`](../groovy-build/SKILL.md). |
| 55 | + |
| 56 | +## Read first |
| 57 | + |
| 58 | +- [`subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc`](../../../subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc) — user-facing reference, the source of truth for command behaviour. |
| 59 | +- The test support classes under `subprojects/groovy-groovysh/src/test/.../commands/`: `ConsoleTestSupport` (engine + console + printer) and `SystemTestSupport` (adds dumb terminal + system registry). |
| 60 | + |
| 61 | +## Vendored JLine files |
| 62 | + |
| 63 | +Five BSD-licensed files under `src/main/groovy/.../jline/` (`GroovyEngine.java`, |
| 64 | +`PackageHelper.java`, `JrtJavaBasePackages.java`, `ObjectInspector.groovy`, |
| 65 | +`Utils.groovy`) are forks of JLine sources, kept in-tree because the |
| 66 | +upstream artifact (`org.jline:jline-groovy`) depends on `org.apache.groovy:groovy` |
| 67 | +and would create a circular dependency. `GroovyPosixCommands.java` is |
| 68 | +similarly derived but diverged enough to be Apache-licensed. |
| 69 | + |
| 70 | +If our customisations are merged upstream, the goal is to delete the |
| 71 | +in-tree forks and depend on the upstream artifact instead. Until then, |
| 72 | +treat the forks as code we own — but check the upstream version when |
| 73 | +bumping JLine, in case there are fixes worth picking up. |
| 74 | + |
| 75 | +## Test layers |
| 76 | + |
| 77 | +Three layers of decreasing portability — prefer the lowest one that |
| 78 | +demonstrates the property under test: |
| 79 | + |
| 80 | +1. **Engine** — `GroovyEngine` directly; no terminal, no registry. See `GroovyEngineTest`. |
| 81 | +2. **Registry** — `GroovySystemRegistry` over a dumb terminal. See `GroovySystemRegistryTest`. |
| 82 | +3. **Command** — full `SystemTestSupport` stack. See `DelTest`, `ImportTest` for printer-based assertions; `HelpCommandTest` for the `terminalOutput()` capture pattern when a builtin writes through `terminal.writer()` instead of the printer. |
| 83 | + |
| 84 | +## Top failure modes |
| 85 | + |
| 86 | +1. **`TerminalBuilder.builder().build()` in a test.** Auto-detects the |
| 87 | + JVM's TTY and may probe native bindings. Use |
| 88 | + `TerminalBuilder.builder().dumb(true).streams(...).build()` instead; |
| 89 | + `SystemTestSupport` already does this. |
| 90 | +2. **Asserting on full terminal output strings.** Prefer |
| 91 | + `printer.output` — the `DummyPrinter` captures `object.toString()`, |
| 92 | + bypassing ANSI rendering. For JLine builtins that write through |
| 93 | + `terminal.writer()` instead (e.g. `/help`), use |
| 94 | + `SystemTestSupport.terminalOutput()` and assert on stable |
| 95 | + substrings, never on full-string compares. The dumb terminal still |
| 96 | + emits capability-probe escapes (`\e[?2027$p\e[c`) at startup, and |
| 97 | + JLine layout/spacing shifts between releases. |
| 98 | +3. **Treating the vendored forks as independent.** They are tightly |
| 99 | + coupled to the `GroovyEngine` deep fork — the small files reference |
| 100 | + `GroovyEngine.Format` etc. Don't delete one without re-deriving the |
| 101 | + coupling. |
| 102 | +4. **Confusing "we changed it" vs "upstream changed it".** When |
| 103 | + syncing forks, diff against the *fork-base* tag (the JLine version |
| 104 | + we originally forked from), not just current upstream. Otherwise |
| 105 | + our renames look like upstream additions. |
| 106 | +5. **Network/Maven tests without `-Djunit.network=true` gating.** |
| 107 | + `/grab` and similar pull from the network; they must be opt-in. |
| 108 | +6. **Hard-coded or implicit terminal width.** The dumb terminal |
| 109 | + reports columns/rows of 0. If a test cares, set |
| 110 | + `terminal.size = new Size(120, 40)` explicitly. |
| 111 | +7. **Calling `getWidth()`/`getHeight()` after a JLine bump.** |
| 112 | + Deprecated since JLine 4.x; use `getColumns()`/`getRows()`. |
| 113 | +8. **`/grep` and similar Posix commands emit ANSI match highlights |
| 114 | + by default.** The colour decision is per-command, not per-terminal, |
| 115 | + so a dumb terminal doesn't suppress it. When unit-testing, pass |
| 116 | + `--color=never` (or strip ANSI from the captured output) so |
| 117 | + substring assertions match contiguously. |
| 118 | +9. **Assuming `/save <file>` captures variable assignments.** It |
| 119 | + serialises `engine.buffer`, which in default mode includes only |
| 120 | + `IMPORT|TYPE|METHOD` snippets — bare variable assignments aren't |
| 121 | + there. Variables enter the buffer only when interpreter mode is |
| 122 | + enabled (`GROOVYSH_OPTIONS[INTERPRETER_MODE_PREFERENCE_KEY] = true`). |
| 123 | + The no-arg `/save` form is a separate path that JSON-serialises |
| 124 | + `engine.sharedData` and *does* include variables. Round-trip tests |
| 125 | + for the file-form should exercise definitions, not bare variables. |
| 126 | + |
| 127 | +(Locale/platform/format brittleness is covered by the project-wide |
| 128 | +[`groovy-tests`](../groovy-tests/SKILL.md) skill — it's not unique to |
| 129 | +groovysh, though terminal-aware tests are particularly exposed.) |
| 130 | + |
| 131 | +## Procedure for a JLine version bump |
| 132 | + |
| 133 | +1. Bump `versions.properties:jline=...`. |
| 134 | +2. Run `:groovy-groovysh:test`. |
| 135 | +3. Compile-scan for new deprecation warnings; fix at the call site. |
| 136 | +4. Diff each vendored fork against the new upstream tag. Pick up |
| 137 | + substantive upstream fixes; skip cosmetic noise. |
| 138 | +5. Update this skill if any finding above changed. |
| 139 | + |
| 140 | +## Validation checklist |
| 141 | + |
| 142 | +Before declaring a groovysh change ready: |
| 143 | + |
| 144 | +- [ ] Tests use `dumb(true).streams(...)` for any terminal they construct. |
| 145 | +- [ ] Output assertions use stable substrings, not full-string compares. Prefer `printer.output`; for terminal-side use `terminalOutput()`; for Posix commands use the context's `out` buffer with `--color=never` where applicable. |
| 146 | +- [ ] No locale-, platform-, or width-dependent assumptions. |
| 147 | +- [ ] `@AfterEach` closes any terminal the test constructed. |
| 148 | +- [ ] Network/Maven tests are gated by `-Djunit.network=true` or skipped. |
| 149 | +- [ ] No new calls to deprecated JLine APIs (`getWidth`/`getHeight`). |
| 150 | +- [ ] If a fork file was synced, the diff against fork-base distinguishes our changes from upstream. |
| 151 | + |
| 152 | +## References |
| 153 | + |
| 154 | +- [`subprojects/groovy-groovysh/AGENTS.md`](../../../subprojects/groovy-groovysh/AGENTS.md) — subproject pointer file that loads this skill. |
| 155 | +- [`subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc`](../../../subprojects/groovy-groovysh/src/spec/doc/groovysh.adoc) — user-facing reference. |
| 156 | +- [`subprojects/groovy-groovysh/LICENSE`](../../../subprojects/groovy-groovysh/LICENSE) — provenance for the BSD-licensed vendored files. |
| 157 | +- [`groovy-tests`](../groovy-tests/SKILL.md) — sister skill for test conventions; load alongside this one. |
| 158 | +- [`AGENTS.md`](../../../AGENTS.md) — root agent guide. |
0 commit comments