Commit Graph
4 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 be60486fc4 perf(stack_host_copy): settle trees with empty leaf directories in the one-call check
Development worktrees carry empty .claude/.cc-writes directories in role source trees; the one-call unchanged check gave up on any directory without files, so the docs tooling copy fell back to per-file stat and copy round trips (about 56 s on the echoserver production deploy). file_checksums now also reports directories (isdir, mode), and the check asks for every leaf directory in the same remote call: it settles only when each leaf exists as a real directory and, when directory_mode is set, carries exactly that mode. With directory_mode unset the stock copy would not touch an existing directory either (file module with mode None). A missing directory, a mode mismatch, a file or symlink in its place, or a symbolic directory_mode with leaves still runs the regular copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:04:23 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e3ffa80c11 perf(stack_host_copy): leave Python bytecode caches out of directory copies
Source trees in a development worktree carry __pycache__ next to their sources (web-app-docs python/: 76 .pyc beside 42 .py). They defeated the one-call unchanged check and were copied file by file over SSH; on the echoserver production deploy the docs tooling staging took 155.9 s at about 1.27 s per file. The plugin now walks the tree without __pycache__ for both the unchanged check and the stock copy, which reads _walk_dirs as a module global; the swap is restored in finally and each task runs in its own forked worker. Sourceless .pyc outside a cache directory stay in the copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:04:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e91117e421 perf(stack_host_copy): settle an unchanged directory copy in one remote call
Ansible's copy action runs one stat and one file module per file of a
directory source even when nothing changed, about 0.6 s each over SSH.
The echoserver deploy paid it on 31 branding renders and the docs
tooling tree (~770 s and 169.5 s, deploy -9). Before falling through to
the stock copy, hash the tree locally and compare it with one
file_checksums call on the target; when every file matches in type,
content and mode, return the stock copy's unchanged result. Any
difference, symlink, empty directory, owner or symbolic mode falls
through to the stock copy unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:35:01 +01:00
kevinveenbirkenbachandClaude Opus 5 575a5a87ac refactor(layout): sort shipped scripts by language and split the unit suite
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).

This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.

Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:

  * 7 `src:` values built from a role variable holding a bare filename
    (svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
    The variable doubles as the destination name inside the container, so the
    language segment belongs at the call site, not in the variable.
  * `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
    through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
    18 roles ship exactly that and have no template fallback: the play would
    have died on each.
  * `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
    interface now reads `files/javascript/javascript.js`.
  * `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
    path through `path_absolute(... 'files', ...)`.
  * 4 `ansible.builtin.script:` invocations naming a bare command
    (pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
  * `dev-python/files/shell/install.sh` pointed at a sibling role's script.

Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 01:31:09 +02:00