Releases: KenM76/scriptree
v0.8.0a22 — cross-platform overrides (one .scriptree, three OSes)
Cross-platform overrides
A single .scriptree file can now declare per-OS variants of the executable, argument template, PATH-prepend, env vars, and action-button argv. The same tool runs on Windows / macOS / Linux without forking the catalog into three parallel files.
Why
Most CLI tools have OS-dependent runtimes:
- Python:
py.exe -3 ./tool.pyon Windows,python3 ./tool.pyon macOS / Linux - Microsoft Office automation:
combridge.exedriving COM on Windows,osascriptdriving AppleScript on macOS (completely different argv shape) - Native binaries living under different filesystem roots per OS
Before v0.8.0a22 an author had to ship one .scriptree per OS and the user saw three near-identical entries in their menu. Now it's one file, one menu entry, the right binary fires per host.
What landed
Schema (scriptree/core/model.py):
{
"executable": "combridge.exe",
"argument_template": ["word", "active-document.text"],
"platforms": {
"macos": {
"executable": "/usr/bin/osascript",
"argument_template": ["-e", "tell application \"Microsoft Word\" to return content of active document as string"]
}
}
}Top-level fields are the default; platforms.<os> overrides per-field. Per-field replace (no deep merge). Empty per-OS entry {} = "supported, identical to default". Missing entry = "no explicit support claim; inherit defaults at run time anyway."
Runtime (scriptree/core/runner.py):
build_full_argv and resolve_action call resolve_for_host(tool) at the top so the resolved argv reflects the host OS before any spawn. Original ToolDef is never mutated — the editor keeps the full cross-platform view.
Editor UX (scriptree/ui/tool_editor.py + new platform_overrides_widget.py):
A new "Per-OS overrides" group sits between the Tool section and the Parameters splitter. Three tabs (Windows / macOS / Linux); each has an "Override for this OS" checkbox + editable fields for executable, argument_template, path_prepend. When the checkbox is off, the fields show a read-only "Inherited from default" preview. A "Preview command line as:" dropdown at the bottom flips the preview to any OS independent of which tab is being edited.
Docs:
docs/LLM/scriptree_format.md— full schema section with worked examples (Python on three OSes, combridge-vs-osascript)- Per-field semantics, OS-id mapping, fall-back rules, and the editor-support pointer all spelled out
OS detection
scriptree.core.platform.host_os() returns "windows" / "macos" / "linux". Maps Python's platform.system() ("Windows" / "Darwin" / "Linux"). Unknown platforms fall back to "linux" (safest POSIX shape). Cached per process.
Tests
+69 new tests across:
tests/test_platforms_io.py— 13 (round-trip, byte-identical legacy, malformed-tolerance)tests/test_platforms_resolve.py— 16 (host detection, per-field replace, original-not-mutated)tests/test_platforms_runtime.py— 7 (build_full_argv + resolve_action pick the right OS variant)tests/test_platform_overrides_widget.py— 13 (editor widget load / apply / refresh / round-trip)tests/test_doc_examples.py— 4 new JSON examples now parse against the loader
Existing runner + editor suites stay green (no regressions).
Internal
0.8.0a21 → 0.8.0a22. Build date 2026-05-30 09:31 EDT.
🤖 Generated with Claude Code
v0.8.0a21 — tree auto-discovery (parity with forest)
Tree auto-discovery
A .scriptreetree-bound cell can now scan its own folder for new .scriptree files and offer to add them. Parity with the existing .scriptreeforest auto-discovery feature, scoped to a single tree.
What landed
Right-click → Tree submenu (on any .scriptreetree-bound cell):
- Refresh from sources — run the walker against the tree's
rootsand react per the persistedupdate_mode. - Auto-add from this folder now — force a one-shot prompt regardless of mode.
- Tree settings… — edit the auto-discover config (enabled / roots / sibling-tree toggle / update_mode).
- Excluded items… — manage paths the user has explicitly removed.
First-load chooser — on the very first open of a .scriptreetree that has no auto_discover block, a small dialog asks the user to pick a mode (Prompt / Auto / Off). Choice persists; never asks again for that tree.
Diff dialog — three sections (Add / Remove / Previously excluded) with checkable rows. Default checked for Add and Remove, default unchecked for Previously excluded.
MainWindow File menu — new Scan tree for new tools… entry, enabled when a tree is loaded with a backing file. Editor-side equivalent of the cell shell's Refresh.
Walker rule
For each directory the walker reaches:
- If the directory contains another
.scriptreetree, stop descending (that subtree is owned by the other file); optionally surface the boundary file as a candidate sub-tree leaf viainclude_sibling_trees. - Otherwise emit every
.scriptreeand recurse.
.scriptreetree schema additions
Two new optional top-level fields documented in docs/LLM/scriptreetree_format.md:
auto_discover—TreeAutoDiscoverConfig(enabled/roots/include_sibling_trees/update_mode).excluded— paths the user has removed; routed by the diff to a separatePreviously excludedsection so they can be re-included.
A non-None auto_discover (even {}) signals "user has been asked"; the chooser fires only when the key is absent or null. Legacy files round-trip byte-identical when never touched by the feature.
What changed under the hood
| Module | Role |
|---|---|
scriptree/core/discovery.py |
Shared TreeAutoDiscoverConfig + UpdateMode literal |
scriptree/core/tree_discover.py |
Pure-Python walker (no Qt) |
scriptree/core/tree_diff.py |
Diff + apply (no Qt) |
scriptree/ui/tree_dialogs.py |
TreeUpdateDiffDialog, TreeSettingsDialog, ChooseUpdateModeDialog |
scriptree/ui/discovery_widgets.py |
Shared widget library (RootsEditor, UpdateModeChoice, IncludeKindsChecklist) used by both forest and tree settings dialogs |
scriptree/shell/tree_controller.py |
Per-cell orchestration; installs _tree_menu_extension |
scriptree/shell/cell_window.py |
Hook invocation + auto-attach on every catalog-mutation site (Load, drop, Save as, Clear) |
scripts/set_default_auto_discover.py |
One-shot batch updater that stamps auto_discover: {} on every shipped tree to suppress the first-load chooser on existing catalogs |
Tests
+95 new tests across these files:
tests/test_tree_auto_discover_io.py(17)tests/test_tree_discover.py(21)tests/test_tree_diff.py(23)tests/test_tree_dialogs.py(15)tests/test_tree_controller.py(20, includes the 5-test catalog-rebind suite)tests/test_main_window_scan_tree.py(7)
Existing forest + dialog suites stay green after the shared-widget refactor.
Other v0.8.0a21 notes
- Bumped version 0.8.0a20 → 0.8.0a21.
- Build date 2026-05-29 21:51 EDT.
- Every
.scriptreetreethis repo ships now carries"auto_discover": {}so the chooser doesn't fire on upgrade. cell_window.py's_attach_tree_controller_if_applicableis wired into every runtime_catalog_pathmutation site so loading / dropping / saving-as a new tree on an existing cell correctly swaps the controller.
🤖 Generated with Claude Code
v0.8.0a20 — rename help/ to docs/, drop internal artifacts
Repository structure cleanup
Adopts the universal open-source convention for documentation: docs/ is where user-facing documentation lives, not help/.
Why
Before this release the public repo had two confusingly-named folders:
docs/held internal artifacts — a competitive analysis Word doc, the script that produced it, and three beta-test reports from Claude sessions. None of that has any business in a public-facing repo.help/held the actual user-facing documentation — README, getting_started, quickstart, configurations, security, theLLM/authoring contract, the parser pages, etc.
That's backwards from convention. docs/ is the de-facto standard everywhere — Sphinx, mkdocs, Read the Docs, PyPI's "Documentation" link, GitHub Pages. help/ is uncommon and usually refers to in-app help systems (HTML help files bundled into desktop apps), not project documentation.
What changed
- The previous
docs/content (ScripTree_Competitive_Analysis.docx,competitive_analysis.py,beta-reports/) is no longer in the repo. It's preserved privately outside version control. - The previous
help/is nowdocs/. Same content, conventional name. scriptree/ui/help_dialog.py::help_root()resolves<pkg parent>/docsfirst, then falls back to<pkg parent>/helpfor backwards compatibility with end-user installs that haven't been refreshed yet.- Every in-repo cross-reference updated: README,
scriptree.schema.json, every doc-to-doc link in the renamed markdown, and the docstring/comment references inscriptree/core/{io,model,providers,runner}.py,scriptree/shell/{cell_window,icon_assets}.py,scriptree/resources/make_icon.py,scripts/gen_facet_icons.py, and the affected tests.
Tests
Full suite green: 1757 passed, 5 skipped.
Internal version
0.8.0a19 → 0.8.0a20, build date 2026-05-29 09:54 EDT.
🤖 Generated with Claude Code
v0.8.0a19 — form panel layout overhaul + cross-platform settings
Form panel layout overhaul
The user kept reporting across v0.8.0a14 / a16 / a17 / a18 that the Run / Stop buttons were getting scrolled off the bottom of the form dock in standalone mode, while developer mode worked fine. v0.8.0a19 lands the actual fix plus the two follow-on issues that surfaced once the bottom band was visible again.
Fixed
-
Run row no longer scrolls out of sight in standalone mode. Three load-bearing pieces:
_FormPanelContaineris now a realQWidgetsubclass with C++ virtualminimumSizeHint/sizeHintoverrides. The previous instance-attribute assignment was silently bypassed by Qt's C++ vtable, so QtAds saw 0×0 and freely shrank the form dock below the bottom band.form_dock.setMinimumSizeHintMode(MinimumSizeHintFromContent)so QtAds queries the contained widget'sminimumSizeHintinstead of the dock widget's own (always 0×0). Standalone now mirrors what MainWindow already did for its tools dock.- New
_FormScrollAreasubclass caps the params scroll area'ssizeHintat a small fixed value so it doesn't inflateform_panel.layout().sizeHint()past the dock viewport — which would force QtAds to wrap the whole form in a second scroll area and push the Run row off the bottom.
-
Beige gap between the params view and the Configuration row is gone.
_populate_form_rowsnow assigns the trailing layout stretch to the LAST inserted widget (QTabWidget / collapsible section / flat form) so it fillsform_groupvertically. Tab-section tools like Find Missing Refs (Source / Matching / Apply) now show their white params area all the way down to the Configuration row. -
Shrinking a resizable param widget tracks live.
ResizableContainer._on_draggedno longer clamps the row sizeHint atmax(current, new)— the row tracks the container's height in BOTH directions. Dragging back up to shrink a multi-line field now shrinks the row from the bottom edge and the next row slides up with the drag instead of waiting for a window resize.
Changed
-
Settings storage moved from Windows registry to portable INI.
standalone_window.pyandaction_result_dialog.pynow route throughcore.app_settings.get_settings(), writing toscriptree.ininext to the install instead ofHKCU\Software\ScripTree. Cross-platform; layout state travels with the install. -
_LAYOUT_SCHEMAbumped v3 → v4 so any saved standalone dock layout from a broken intermediate build is invalidated on first launch. -
StandaloneWindowno longer wrapsform_panelin aQStackedWidgetbefore installing in the QtAds dock. The wrapper empirically diverged from its child'ssizeHint()override when the child had a complex layout, re-inflating the dock past the viewport. Direct install + the C++ vtable override is enough.
Tests
- New
tests/test_form_panel_dock_sizing.pywith 10 regression tests covering the C++ vtable reachability, QtAdsMinimumSizeHintFromContentmode, Run-button visibility inside form_dock (not just inside form_panel), and tab-section content filling form_group. - New
test_shrink_drag_shrinks_row_sizehintintests/test_resizable_container.pypins the bidirectional row-sizeHint tracking. - Full suite: 1757 passed, 5 skipped.
Internal version
0.8.0a18→0.8.0a19, build date2026-05-28 11:25 EDT.
🤖 Generated with Claude Code
v0.8.0a17 — pin form panel minimum + propagate resize up
Two more layout fixes
1. Standalone run controls still got scrolled away
Even with the v0.8.0a14 bottom-band + a16 QStackedWidget wrapper, the form panel's minimumSizeHint was effectively 0 (because form_scroll.setMinimumHeight(0) from v0.8.0a13 contributes nothing and the bottom band's minimumSizeHint defaulted to 0 too). The MainWindow's QStackedWidget masked the issue; the standalone path didn't.
Fix: explicit minimum heights — bottom_band.setMinimumHeight(200) (covers cfg row + extras + cmd + Run/Stop row + status with room to spare) and container.setMinimumHeight(310) (header + small form floor + bottom band). Both modes now floor the form dock at a sane height; the run controls always have a place to sit.
2. Resizing a multi-line / list widget didn't grow the surrounding section
The v0.8.0a16 fix updated the QListWidgetItem's sizeHint but the QListWidget's own sizeHint wasn't re-asked, so the tab section's QScrollArea kept its original size and the resized widget overflowed visually behind the next section.
Fix: after rewriting the row item sizeHint, call form_list.updateGeometry() to invalidate the QListWidget's own size, then walk up the parent chain calling layout().invalidate() + updateGeometry() on every intermediate scroll area / group box. The entire chain re-asks for sizeHints and the section grows with the widget.
Tests
Suite: 1738 passing, 5 skipped (no test count change).
Download
ScripTree-v0.8.0a17.zip — portable Windows build with combridge bundled.
v0.8.0a16 — resize grows the row, standalone form wrap
Two fixes following v0.8.0a15
1. Resizing the box now grows the section it sits in
In v0.8.0a15 the resize handle grew the inner widget but the surrounding QListWidgetItem (the param row) kept its original sizeHint. The widget ended up overflowing behind the next param row — the failure mode you described.
The param row's cached sizeHint is now updated directly when the drag happens: walk up to the owning QListWidget, find the row's QListWidgetItem, and set its sizeHint to include the dragged-to height plus a small padding. Intermediate layouts are invalidated on the way so future sizeHint calls see the new value. The container's sizeHint is also overridden to report the desired (not natural) height so QPlainTextEdit.sizeHint doesn't reassert its fontmetric-derived hint.
2. Standalone mode now holds the bottom band in place
v0.8.0a14's bottom-band fix made the configurations bar + Run/Stop row hold their space in the developer-mode editor — which empirically depends on the form panel being wrapped in a QStackedWidget before QtAds receives it (MainWindow's editor uses the stack to switch between loaded tools; it also makes QtAds honour the inner Fixed sizePolicy reliably).
The standalone window installed the form panel directly into the QtAds dock without that wrapper, so QtAds's geometry negotiation pushed the bottom controls out of view when the dock was tight.
Fix: mirror MainWindow's pattern by wrapping form_panel in a QStackedWidget before the standalone dock receives it. No behavioural change otherwise — the stack has one page (the form panel) and that's the current widget.
Tests
+1 new test test_dragging_inside_param_form_grows_the_row verifies the row's sizeHint grows when the inner widget is dragged. Updated the existing standalone-window visibility test to reach through the stack via stack.currentWidget(). Suite: 1738 passing, 5 skipped.
Download
ScripTree-v0.8.0a16.zip — portable Windows build with combridge bundled.
v0.8.0a15 — drag-handle resize for multi-line + list widgets
What's new
The multi-line text area, checkbox list, and folder/file list param widgets were all capped at a fixed pixel height (80 px for text, 160 px for lists) via setMaximumHeight. Long content had to scroll inside the widget even when there was plenty of vertical room on the form.
Fix: a new ResizableContainer wrapper widget puts a thin grip handle (5 px high, three small dots in the middle) below the wrapped widget. Click-and-drag the handle to grow / shrink it vertically. No upper cap — the form's outer scroll area already handles overgrown rows by scrolling. The drag clamps at a lower bound (32 px for text, 48 px for lists) so the widget can't be dragged into a useless zero-pixel strip.
Applied to:
TextAreaWidget(thetextwidget forstringparams) — was capped at 80 pxCheckboxListWidget(thecheckbox_listwidget formultiselectparams) — was capped at 160 pxFolderListWidget/FileListWidget(thefolder_list/file_listwidgets formultiselectpaths) — were capped at 160 px
The value API is unchanged — get_value / set_value still round-trip through the original child widget reference, so no functional regression.
Tests
New test_resizable_container.py covers container basics (initial height, drag grows, min-height clamp, no upper cap, handle emits signal with delta) and per-widget wiring (each candidate widget builds with a ResizableContainer around its child). Suite: 1737 passing, 5 skipped (+9 net).
Download
ScripTree-v0.8.0a15.zip — portable Windows build with combridge bundled.
v0.8.0a14 — bottom-band fix for standalone mode
Bug fix (continuation of v0.8.0a13)
The v0.8.0a13 form_scroll.setMinimumHeight(0) fix kept the Run / Stop buttons visible in the developer-mode editor but not in the StandaloneWindow — the editor wraps the form panel in a QStackedWidget, while standalone puts the form panel directly inside a QtAds dock, and QtAds's dock geometry negotiation doesn't honour the inner scroll area's "I can shrink" promise reliably enough.
Fix: wrap the configurations bar + extras + command line + Run row + action-button row + status into a single bottom_band widget with QSizePolicy(Preferred, Fixed). Qt's QVBoxLayout reads Fixed as "must get sizeHint exactly," so the band claims its natural height first; form_scroll absorbs whatever is left over (or compresses to nothing). Result: the bottom controls always stay visible across both MainWindow and StandaloneWindow regardless of QtAds's quirks.
What stays the same
The visual shape is unchanged — same arrow-collapse sections, same vertical order. Only the underlying widget hierarchy changed (a single band widget instead of widgets sitting directly in the outer layout).
Tests
The existing test_form_panel_layout_order test was re-pointed at the band's own layout. Suite: 1728 passing, 5 skipped (no net change).
Download
ScripTree-v0.8.0a14.zip — portable Windows build with combridge bundled.
v0.8.0a13 — Run buttons no longer pushed off the form dock
Bug fix
v0.8.0a12 left the configuration line and Run / Stop button row sharing visual space with the parameters scroll area. When the form dock wasn't tall enough to show everything, the bottom rows got pushed below the dock's bottom edge instead of the params scroll area shrinking to fit.
Root cause: form_scroll was added with stretch=1 but its minimumHeight defaulted to its content's sizeHint. Qt's QVBoxLayout reserved that much space for it even when the dock was constrained, squeezing the bottom rows first.
Fix: form_scroll.setMinimumHeight(0) + QSizePolicy.Expanding. The scroll area can now compress all the way to a tiny strip; the bottom band (cfg / extras / cmd / Run row / status) keeps its natural height and stays visible.
Tests
New regression test test_run_button_visible_when_dock_is_tight forces a 16-param tool into a 600×280 runner and asserts the Run button stays mapped within the runner's own height. Suite: 1728 passing, 5 skipped.
Download
ScripTree-v0.8.0a13.zip — portable Windows build with combridge bundled.
v0.8.0a12 — arrow-collapse sections + flatter form layout
What's new
Arrow-collapse sections
The three checkboxes in the tool-runner form panel (description, Extra arguments, Command line) read as "enable / disable the feature" because Qt's QGroupBox(setCheckable=True) puts a literal checkbox in the title bar — but they were actually just show/hide affordances. Replaced with a new ArrowSection widget that uses a ▶ / ▼ arrow so the chrome matches the semantics.
- Click the arrow (or press Space / Enter when focused) to collapse / expand.
- The header bar stays visible at all times so the toggle is always findable when the section is collapsed.
- API mirrors
QGroupBox'sisChecked/setChecked/toggled/setTitleso external callers keep compiling.
Flatter form layout — controls always visible
Dropped the QSplitter that used to separate the form from the extras + command-line panes. The drag handle was rarely used and let the bottom controls disappear off-screen when accidentally pulled too high. New layout:
[Header arrow + description] ← collapsible
─────────────────────────────
[Parameters scroll area] ← takes available space
─────────────────────────────
[Configurations bar] ← always visible
[Extras arrow + edit] ← collapsible
[Command line arrow + edit] ← collapsible
[Run / Stop / etc row]
[Action buttons row]
[Status line]
The parameters scroll area is the only scrollable region; everything from the configurations bar downward stays at its natural height so the run controls never get pushed out of view.
Extras hidden by default in standalone mode
ToolRunnerView.set_standalone_mode(True) now also collapses the Extra arguments section. In standalone (direct-launch) mode the form is the canonical input surface and extras are almost never used; when run inside the main editor, extras still starts expanded so power users can see and edit at a glance.
Tests
The existing test_tool_runner_collapsibles.py was retargeted to the new ArrowSection-based contract. Added new tests for standalone-mode auto-collapse and for layout order. Full suite: 1727 passing, 5 skipped. No net change in count — the rework was a clean swap, not an addition.
Download
ScripTree-v0.8.0a12.zip — portable Windows build with combridge bundled.