From 01f262544f34397d4626578fc9882763843e126d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 17:36:53 +0000 Subject: [PATCH 1/2] Retention markers, the prune subprocess, the run record through the Tools helpers, and run identity Hosts never delete under results/ and runs/: the host writes .lc-owner.json when a folder is made and .lc-done.json (atomically) when the run ends, then starts `labconstrictor-tools prune` in a subprocess. The done status and the run record come from labconstrictor_tools.runs (Tools >= 0.3.0a1, checked). A run is frozen when Run is pressed (ImageData objects, selected ROIs, plane, areas) and placed only on that image while it is still shown with its exported shape (target_unavailable / target_changed_size). The script is split into files listed in parts.txt; lint joins them. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN --- .github/workflows/lint.yml | 22 +- README.md | 27 +- build.sh | 4 + docs/user-guide.md | 3 + .../qupath/LabConstrictorExtension.java | 29 +- .../qupath/LabConstrictorTools.groovy | 164 ++++++----- .../labconstrictor/qupath/LcCompat.groovy | 83 ++++++ .../labconstrictor/qupath/LcRetention.groovy | 175 ++++++++++++ .../qupath/LcRunIdentity.groovy | 169 +++++++++++ .../labconstrictor/qupath/parts.txt | 4 + tests/ci_setup_apps.sh | 2 + tests/gui_test_area_body.groovy | 1 + tests/gui_test_frame_body.groovy | 1 + tests/gui_test_identity_body.groovy | 237 ++++++++++++++++ tests/gui_test_parity_form_body.groovy | 2 +- tests/gui_test_parity_host_body.groovy | 2 +- tests/gui_test_parity_pixel_body.groovy | 2 +- tests/gui_test_parity_support_body.groovy | 6 +- tests/gui_test_retention_body.groovy | 265 ++++++++++++++++++ tests/run_gui_ci.sh | 7 +- tests/run_gui_test.sh | 4 +- tests/runs_app/runs_lc_tools.py | 83 ++++++ 22 files changed, 1205 insertions(+), 87 deletions(-) create mode 100644 src/main/resources/org/cellmigrationlab/labconstrictor/qupath/LcCompat.groovy create mode 100644 src/main/resources/org/cellmigrationlab/labconstrictor/qupath/LcRetention.groovy create mode 100644 src/main/resources/org/cellmigrationlab/labconstrictor/qupath/LcRunIdentity.groovy create mode 100644 src/main/resources/org/cellmigrationlab/labconstrictor/qupath/parts.txt create mode 100644 tests/gui_test_identity_body.groovy create mode 100644 tests/gui_test_retention_body.groovy create mode 100644 tests/runs_app/runs_lc_tools.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ad947cb..1418b4d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -# Lints the QuPath script (LabConstrictorTools.groovy) with npm-groovy-lint (CodeNarc rules); the rules are set in .groovylintrc.json. +# Lints the QuPath script (the files listed in parts.txt, joined into one) with npm-groovy-lint (CodeNarc rules); the rules are set in .groovylintrc.json. # # The GUI tests in tests/ run in their own workflow, qupath-gui.yml. name: lint @@ -20,9 +20,27 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "22" + - name: Part files stay below 600 lines (LabConstrictorTools.groovy is the grandfathered main script, from before the split) + run: | + RES=src/main/resources/org/cellmigrationlab/labconstrictor/qupath + status=0 + while IFS= read -r part || [ -n "$part" ]; do + [ -z "$part" ] || [ "$part" = "LabConstrictorTools.groovy" ] && continue + lines=$(wc -l < "$RES/$part") + echo "$part: $lines lines" + [ "$lines" -lt 600 ] || { echo "::error file=$RES/$part::$part has $lines lines (the limit is 600): split it into a new file and list it in parts.txt"; status=1; } + done < "$RES/parts.txt" + exit $status + - name: Join the script files (parts.txt) into the one script the extension runs + # CodeNarc reads one file at a time, so a class used from another file would not resolve: the joined text is what is linted + run: | + RES=src/main/resources/org/cellmigrationlab/labconstrictor/qupath + mkdir lint-input + { while IFS= read -r part || [ -n "$part" ]; do [ -z "$part" ] || { cat "$RES/$part"; echo; }; done < "$RES/parts.txt"; } > lint-input/LabConstrictorTools-all.groovy - name: Lint the script (fails on any finding) run: > npx --yes npm-groovy-lint@18.0.0 - --path src/main/resources/org/cellmigrationlab/labconstrictor/qupath + --path lint-input --files "**/*.groovy" + --config .groovylintrc.json --failon info diff --git a/README.md b/README.md index fd7feaa..a137add 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ so QuPath never imports the app's packages and needs no Python of its own. Same 2. Drop `labconstrictor-qupath-0.1.0.jar` on the QuPath window (or into the QuPath extensions folder) and restart QuPath. 3. **Extensions > LabConstrictor tools...** -Without the jar: open `src/main/resources/org/cellmigrationlab/labconstrictor/qupath/LabConstrictorTools.groovy` in QuPath's Script Editor and run it. +Without the jar: the script is several files now (`src/main/resources/org/cellmigrationlab/labconstrictor/qupath/`, joined in the order of `parts.txt`; the first is the main script). `./build.sh` also writes `target/LabConstrictorTools-all.groovy`, all of them in one file: open that one in QuPath's Script Editor and run it. The main script alone no longer runs. + +Needs **labconstrictor-tools 0.3.0a1 or newer** in the app's environment (the run record and the clean-up use its helpers and its `prune` command). A run of an app with an older one is refused with `this app's labconstrictor-tools 0.2.0rc1 is older than the 0.3.0a1 this host needs: update it`. ## What it does * Pick an app and a tool. Numbers (range, unit), choices, checkboxes, text, files and folders become fields; parameters of a group sit under a heading, advanced ones in a collapsed section, optional ones without a default have a "set" box (unticked = the tool gets nothing), and `enabled_when` greys fields out. @@ -21,7 +23,8 @@ Without the jar: open `src/main/resources/org/cellmigrationlab/labconstrictor/qu * **Points** found in the image that is open in QuPath become a point annotation named `:` (replaced by the next run when the output declares `Replace()`); points of any other image are shown as a table. * "No match" is a message, not an error. Progress bar, Cancel (a tool that ignores it is killed after 3 s), worker kept between runs (or not), details of the last run, no worker left behind when the window closes. * **Trust checks** on every registry entry (same rules as the Python registry and the Fiji script): the entry names its five text fields; the app name is a plain name; the interpreter lies inside the install prefix (which is not a filesystem root); the entry and its schema file are owned by you (in a shared folder also by root) and not writable by group or others; the schema sits next to the entry; the interpreter, its folder and the prefix are not writable by everybody (a sticky folder like `/tmp` excepted); tool ids and parameter names are plain names, parameter names Python identifiers (a parameter name becomes a file name). An entry that fails is listed under "skipped" with its reason and never started. On Windows the owner and permission checks do not apply. -* Results are kept in `~/.labconstrictor/results/