5.51 2026-10-03 - Problem 25 follow-up: keep help handling within internal CLI dispatch and pass native help flags through to dotted external skill commands. - Add a Docker regression fixture for `d2 tira.tasklist.prune --help` and document the internal-versus-skill help ownership boundary. 5.50 2026-10-03 - Problem 35: configure Dist::Zilla's UploadToCPAN releaser so an explicit `dzil release` has a real PAUSE upload action; keep the upload separate from `dzil build` and document that credentials belong outside the repository. - Add a release metadata regression test for the configured Releaser plugin. 5.49 2026-10-03 - Problem 12, 32, 33, and 34 delivery follow-up: publish the final Docker, coverage, package-build, and blank-container integration evidence in the numbered problem report. - Refresh the release archive to include the completed problem status index. 5.48 2026-10-03 - Problem 12 follow-up: normalize dotted workspace refs consistently for tmux session lookup and creation, and reuse a duplicate session only after verifying the tagged logical workspace owner. - Problem 32: separate path aliases from command completion and bound cdr candidates to direct children, narrowing one directory per entered term. - Problem 33: add regression coverage confirming an unregistered leading cdr word remains a search term and participates in completion narrowing. - Problem 34: use invocation-directory Compose files as the base project and restrict automatic runtime service merging to services declared there. - Add Docker regression and complete statement, branch, condition, and subroutine coverage verification for the affected library. 5.47 2026-10-03 - Problem 34: use invocation-directory Compose files as the local project base and limit automatic runtime service overlays to declared local services; keep explicit service selection and no-local-base discovery behavior intact. - Add Problem 33 regressions proving an unregistered first cdr argument remains a search term for both target resolution and later TAB narrowing. - Problem 12 follow-up: map dotted workspaces to tmux's underscore-normalized session names, check existing session ownership, and confirm duplicate create races before reuse. - Problem 32: keep path aliases out of root command completion, offer them beside tmux sessions for `workspace`, and restrict initial `cdr` TAB candidates and subsequent narrowing to one directory level per term to avoid recursive tree scans. - Add regression tests for completion namespaces, direct-child cdr listing, and concurrent workspace session creation; synchronize docs and POD. 5.46 2026-10-03 - Align the main manual with the blank-environment integration behavior: plain cpanm runs the distribution tests, then installed d2 version and delegated grep/Compose help are verified against the archive. - Rebuild the final release artifact and Docker image after documenting the selected-home marker rule and installed short-entrypoint regression gate. 5.45 2026-10-03 - Strengthen blank-environment release verification through the installed d2 entrypoint: compare its version with the tarball, verify delegated grep help, and preserve native Docker Compose config/help arguments. - Record explicit Problem 31 coverage that all Docker marker creation uses the selected home runtime while marker discovery/removal spans active service layers. 5.44 2026-10-03 - Problem 12: Resolve workspace -c targets through configured aliases and skill Folder.pm methods at any nested skill depth, preserving config-first precedence and propagating real resolver errors. - Problem 25: Pass delegated grep and Docker Compose help requests through to their owning CLIs even when internal options or Compose selectors come first, while retaining Dashboard's explicit built-in help. - Problem 30: Parse standard five-field collector cron expressions from config.json, apply crontab day matching and named/ranged/stepped fields, deduplicate each minute, and reject missing or invalid schedules before launching a collector loop. - Problem 31: Honor disabled.yml and develop.yml markers from every active service config layer; service-enable and development-disable remove all matching markers across those layers. - Complete the 100% statement, branch, condition, and subroutine coverage gate across the full Perl library. - Run the blank-environment tarball install with plain `cpanm` so the packaged distribution test suite executes after installation. - Validate release installation with `cpanm`'s test phase enabled. - Add Docker regression coverage for skill workspace aliases and downstream help dispatch; synchronize CLI documentation and POD. - Enable an init reaper in the blank-environment test container so process tree assertions observe terminated descendants instead of unreaped zombies. 5.41 2026-10-02 - Exclude all root-level Devel::Cover databases, including custom `cover_db_*` names, from distribution archives; add a release-metadata regression guard for the exclusion. 5.40 2026-10-02 - Problem 27: Restore `dashboard of grep ...` as a recursive content search, using argv-safe grep execution and the existing file chooser; retain full Perl module source lookup across every directory in `@INC`. - Problem 28: Resolve collector `cwd` values from configured path aliases and skill-qualified `lib/Folder.pm` aliases, with config aliases taking priority. - Close the reachable four-metric coverage gaps found by the release gate, including grep editor/no-match dispatch and collector alias resolution. 5.38 2026-10-01 - Problem 26 follow-up: preserve `origin/` in `dashboard ps1` unless a same-named local branch points to the exact same commit. Apply the rule to symbolic and detached HEADs, including loose and packed refs. 5.37 2026-10-01 - Problem 26: preserve complete slash-delimited branch names in `dashboard ps1` for every shell adapter. Detached HEADs matching loose or packed `origin` refs retain the `origin/` prefix unless a same-named local branch points to the same commit; symlinked ref roots are ignored. Add regression coverage for both ref formats, symlink rejection, unmatched detached HEADs, and shared-renderer delegation from every supported shell bootstrap. 5.36 2026-10-01 - Problem 7: resolve nested skill cli/__init__ entrypoints when addressed directly at any nesting depth. Prefer the deepest matching initializer after checking explicit commands, and document the dotted invocation. 5.35 2026-10-01 - Problem 25: add a shared help catalog for every internal CLI command, compatibility alias, public action, and the direct version command. Route help before command execution and align TAB candidates with the same catalog, including nested actions and recognized option flags. Add concise global help, package/runtime regressions, and workspace completion that does not query tmux for option flags. Keep release metadata version-neutral. 5.34 2026-09-30 - Complete Problem 24's Pod::Text 6.1.1 security floor and fail-closed isolated dependency audit guard. Keep the workflow release-gate assertion in the checkout-only metadata test so the packaged test suite does not require the intentionally unshipped .github tree. Problem 20 aggregate coverage remains paused. - Problem 20: bring lib/ to 100.0 percent statement, branch, condition and subroutine coverage. Add coverage tests t/300-t/305, t/330-t/331, t/360-t/362, t/390, t/420-t/422 and t/450-t/451; failure paths are injected with CORE::GLOBAL overrides so they run the same for root and non-root users. Genuinely unreachable branches carry their own uncoverable annotation. Fix scheme-relative bookmark targets (//host/path) dying on URI::_generic host(). Route the effective-uid check in SkillManager through _effective_uid so both answers are testable. 5.33 2026-09-30 - Problem 24: Require Pod::Text 6.1.1 or later for CLI help rendering and enforce the fixed module path/version in the isolated dependency audit. Correct the CI audit step classification and guard the exact CPANSA podlators advisory against CPAN::Audit's independent Pod::Man version mapping. Keep Problem 20's aggregate coverage work paused. 5.32 2026-09-30 - Correct Problem 21–23 report release labels and preserve the actual post-push 8.0/10 Scorecard result and its unresolved governance actions. Keep Problem 20 coverage paused. 5.31 2026-09-30 - Correct the Problem 21–23 delivery report to reflect the pushed 5.30 release and live 8.0/10 Scorecard result. Record remaining GitHub-side governance tasks explicitly; Problem 20 coverage remains paused. 5.30 2026-09-30 - Complete the Problem 21–23 report with expected outcomes, Docker reproductions, root causes, current Git references, and explicit delivery status. Keep Problem 20 aggregate coverage paused as requested. 5.29 2026-09-30 - Ship the final Problem 23 delivery report with the verified archive and image results instead of the pre-verification in-progress status. 5.28 2026-09-30 - Exclude stale generated pax-output artifacts from source distributions so an old local binary cannot leak into the interpreted-only package. 5.27 2026-09-30 - Exclude stale generated pax-output artifacts from source distributions so an old local binary cannot leak into the interpreted-only package. - Keep the blank-environment outsider-browser check portable across Chromium's generic 401 page and its HTTP response failure while continuing to assert that bootstrap guidance and the login form are not exposed. 5.26 2026-09-30 - Remove the vendored PAX compiler, self-compilation cache, CLI command, private helper staging entry, and compiled-binary release workflow. Keep the interpreted Perl CLI as the single supported execution path, and add Docker regression coverage for the removed integration. Make the blank container's outsider-browser assertion accept Chromium's generic 401 page or its equivalent HTTP response failure while still rejecting leaked bootstrap content. 5.25 2026-09-30 - Preserve eligible module source paths across standalone runtime-family expansion and payload mapping. Excluded compiled modules no longer mask runtime copies, duplicate modules are not re-added, and PAX-owned modules are packaged relative to their actual import root. Add Docker regressions for each collision/layout case and complete Problems 21 and 22 validation. 5.23 2026-09-30 - Preserve known dependency source paths while expanding Perl package-family files, preventing a stale duplicate from being re-added to standalone runtime manifests. Add a Docker regression using matching module-relative paths. 5.22 2026-09-29 - Complete Problems 21 and 22 delivery: verify both runtime roots participate together and keep ddfile.local dependencies private to their owning skill. Stabilize the standalone-image source-path regression with a unique fixture namespace. Confirm package tests in an init-enabled blank Docker container and update the Chromium headless SSL diagnostic checks. 5.21 2026-09-29 - Complete delivery fixes for Problems 21 and 22: verify both runtime roots participate together and keep ddfile.local dependencies private to their owning skill. Update Docker regressions and packaged-test compatibility for newer Chromium headless certificate diagnostics. 5.20 2026-09-29 - Discover both .d2 and .developer-dashboard runtime directories at each home and project depth, preserving canonical lookup/write precedence while allowing commands, config, Docker, dashboards, skills, and environment files from both roots to participate. - Install dependencies from an installed skill's ddfile.local into that skill's private skills/ tree instead of the runtime-wide skills root. Add Docker-run regressions for dual runtime roots and private dependencies. 5.18 2026-09-28 - Preserve response headers set by skill Dancer2 before hooks when the adapter applies default security headers; explicit backend response headers retain final precedence. Document Dancer2 hook syntax and verify that request-local hook variables are readable in page and Ajax CODE. Confirm that skill Dashboard.pm code loads once at PSGI app startup. 5.17 2026-09-28 - Add bookmark HEAD content for raw metadata/script/style injection into the rendered document head, with parsing, serialization, editor highlighting, and explicit trusted-content documentation. 5.16 2026-09-28 - Correct skill install progress labels when ddfile or ddfile.local exists but no dependency install is needed; report a missing manifest only when the file is actually absent. 5.15 2026-09-28 - Add -b/--branch to remote skill installs. Fresh installs try master then main by default; reinstalls keep the installed checkout's current named branch unless an explicit branch overrides it. 5.14 2026-09-28 - Include each skill's configured and Folder.pm path aliases in d2 skill command completion after the skill-qualified prefix, loading only that skill's Folder.pm for the completion request. 5.13 2026-09-28 - Added skill-provided path aliases through lib/Folder.pm. cdr and path resolve now check skill config aliases first, then call the matching Folder method. d2 paths, path list, and cdr completion merge names from Folder->__list__ without modifying skill files; path add continues to persist only in config/config.json. 5.12 2026-09-28 - Added useful help output to d2 docker --help, dashboard docker --help, and the related -h/help/no-subcommand forms. Docker command completion now includes the development subcommand and its enable/disable actions, so the available nested commands can be explored interactively. 5.11 2026-09-28 - Fixed skill dashboard CODE blocks and saved skill Ajax handlers so the active skill's ordered .env and .env.pl files are loaded into a scoped environment. Values now reach both page rendering and Ajax subprocesses without leaking into the web worker or other requests. Added PageRuntime and installed-skill route regressions for the documented FOO=BAR case. 5.10 2026-09-27 - Fixed a `dashboard serve --foreground` startup hang for any skill declaring a collector: CollectorRunner::_find_running_loop scanned every /proc PID and fell back to spawning a `ps -o args= -p $pid` subprocess whenever a candidate's cmdline was readable-but-empty - the permanent, non-racy shape of a kernel thread, not just a vanished-process race. On a host with hundreds of kernel threads that cost one subprocess spawn per thread, sequentially, making the startup hang 90+ seconds with zero output. Now skips a readable-but-empty cmdline candidate before ever calling _read_process_title, since it can never match a collector's own non-empty process title. 5.09 2026-09-27 - Fixed a dependency-scan masking defect: _locate_module_runtime_file walked @INC/roots and returned the FIRST existing file for a module name, discarding the dependency scan's own already-known source_path for that module. If an earlier @INC entry held a stale/duplicate copy of the module, the wrong file was silently bundled into a compiled standalone binary. _locate_module_runtime_file now accepts an optional known_source_paths lookup (built in _runtime_selected_files from %args{dependencies}) and prefers it over the @INC walk. 5.08 2026-09-27 - Fixed a compiled-standalone-binary crash: three closures in StandaloneRuntime.pm's recursive merge/collect ops (config_merge_hashes, skill_dispatcher_merge_skill_hashes, suggest_collect_skill_commands) resolved their own recursion via _code_for($name), the bare unqualified sub name, instead of _code_for($full), the package-qualified name available in the same scope - _code_for() does a raw typeglob lookup, so the bare name resolved against the wrong package and returned undef, crashing `dashboard init`. 5.07 2026-09-27 - Added explicit Docker development overlay toggles through `dashboard docker development enable|disable ` and the `develop.yml` service marker. - Kept each service `compose.yml` as the base and append `development.compose.yml` only when opted in; a missing opted-in overlay is skipped while the base remains active. - Added regression coverage for overlay ordering, marker commands, missing overlays, safe service paths, and PAX standalone runtime parity. 5.06 2026-09-27 - Made the skill directory providing an `/app//` dashboard the first `@INC` entry for its CODE blocks, ahead of other layered skill libs. - Made the skill directory providing a Perl CLI script the first `@INC` entry and first skill library in `PERL5LIB`, ahead of generic dashboard and shared local Perl libraries. - Added tests that print and check the actual `@INC` from dashboard CODE and a dispatched Perl CLI script; both verify the page/command skill's `lib/` directory comes first. - Excluded and pruned generated `pax-output/` binaries from distribution scans and tarballs; an ignored 90+ MB compiled dashboard and deep excluded trees had made `dzil build` spend minutes walking build output. 5.05 2026-09-27 - Fixed transient Ajax URL generation to encode the rendered Template Toolkit code instead of the original template source. For example, data containing `args => 123` now turns `my $foobar = [% args %];` into executable Perl before tokenization. Added regression coverage for the generated payload. - Documented templating behavior for both transient URLs and saved Ajax files. 5.04 2026-09-26 - DD-1059: added the missing full-POD-DOC block to t/226-pax-paxfile-parser.t (a race in the shared main checkout meant this fix landed only as an uncommitted working-tree change before 5.03 was tagged - see FIXED_BUGS.md). 5.03 2026-09-26 - Fixed a background-job grandchild surviving the process-group SIGKILL in `Local::BoundedCommand::run_bounded` (t/lib/, test harness) when run inside a container started without an init that reaps orphans - the kind of container this project's own gate scripts use. The group kill already reached and killed the grandchild; nothing ever reaped it once its immediate parent died, so it was a permanent zombie and `kill 0` kept reporting it alive. `run_bounded` now declares itself a Linux/ x86_64 subreaper before forking (best-effort, a documented no-op elsewhere), and the cleanup sweep now runs on both its polite (TERM) and escalated (KILL) exit paths - the first version of the fix only swept on the escalated path and missed the common case where TERM alone already finishes the job. (DD-1051) - Documented the mechanism at docs/bounded-command-orphan-reaping.md. - Filed DD-1067 for the same defect class found, independently and pre-existing, in PageRuntime's own `_terminate_saved_ajax_process`. 5.02 2026-09-26 - DD-1059: brought lib/Developer/Dashboard/Pax/Paxfile.pm to 100.0 percent Devel::Cover on all four metrics (statement, branch, condition, subroutine), up from 11.5 percent. Added t/226-pax-paxfile-parser.t (17 assertions) covering every load_optional/load/_scalar code path, including the open()-failure branch and both directions of the defined-or section-reuse condition. Added missing purpose/input/output comments to all three subroutines. 5.01 2026-09-26 - Resolved bare skill-qualified workspace aliases (for example, `bar.foo`) without requiring `-c`, starting the tmux session in the registered directory. - Automatically imported the namespaced DataHelper JSON functions in every saved-page CODE block. - Loaded trusted skill `lib/Dashboard.pm` extensions into the shared Dancer2 app at web startup, including on restart and serve commands, with routes behind the dashboard authorization gate. - Excluded generated PAX test executables from distribution scanning so Dist::Zilla no longer decodes binary scratch output as UTF-8 source. - Marked inline helper packages as no-index metadata so the provides scan does not warn that they lack standalone module files. - Added focused regression coverage and operator documentation for all three changes. 5.00 2026-09-26 - Prepared the 5.00 release metadata and synchronized module versions, POD, and the generated README. 4.99 2026-09-26 - Made the final checkout-only coverage workflow gate skip cleanly in installed release tarballs where .github/ is intentionally excluded. 4.98 2026-09-26 - Made source-checkout-only release gates skip cleanly in installed tarballs, added UTF-8 POD metadata to the non-ASCII regression test, and removed a coverage-gate warning when checkout paths are unavailable. 4.97 2026-09-26 - Added skill C directories to skill CLI C and skill page CODE C<@INC> so both command scripts and dashboard CODE blocks can load modules shipped by their skill. - Loaded skill C files and changed skill-command env precedence so the invocation cwd C<.env> is applied last and can override skill values. - Allowed cwd C<.env> loading even when C is launched outside the home or project-root ancestry. - Rendered skill C completion entries as the bare skill name instead of C. - Removed the public C helper alias in favour of C. - Added regression coverage for multi-level nested skill command resolution. - Fixed checkout-only GitHub workflow validation so packaged tarball installs skip it cleanly when C<.github/> is intentionally excluded from the release payload. 4.95 2026-09-25 - Merged incoming query parameters into saved skill bookmark URLs, replacing matching keys while preserving saved parameters and appending new values. 4.94 2026-09-25 - Fixed skill bookmark files containing saved HTTP(S) URLs so they forward to the target instead of being parsed as instruction documents. 4.93 2026-09-25 - Fixed saved URL bookmark forwarding so instruction parsing errors are not leaked when a legacy entry is routed through named-page loading. 4.92 2026-09-25 - Bumped the release metadata and every library version to 4.92. - Restored legacy saved URL bookmark compatibility: external HTTP(S) bookmarks redirect with the incoming query, local routes forward internally, and legacy selected array parameters are resolved. - Made Template Toolkit bookmark includes skill-aware, supporting both relative skill dashboard fragments and explicit skills//dashboards include paths. - Made saved Ajax C templates render their supplied C values before the generated handler is stored. - Kept dashboard-owned init helpers inside the private C namespace for both C and C, without overwriting user CLI files. - Nested skill CLI commands now inherit the DD-OOP runtime env chain and every parent skill's C<.env> and C<.env.pl> before the leaf command runs. 4.91 2026-09-25 - Raised the Dancer2 dependency floor to 2.2.0, the first release outside the newly reported vulnerable ranges. 4.90 2026-09-25 - DD-1050: removed 'lib' from _skip_dependency_module's skip-list regex in StandaloneImage.pm. Unlike strict/warnings (true compile-time-only pragmas), lib.pm is a real, physically-requirable module whose import() runs real code (unshift @INC) - any 'use lib' reached through StandaloneRuntime.pm's require wrapper at runtime needs the actual lib.pm file bundled. Confirmed live via a real GitHub Release linux-amd64 binary (commit bf1c4ccc/v4.87) crashing --help and jq with "Can't locate lib.pm in @INC" in a hostile env -i container. Same class of defect as DD-1022 (overload). 4.89 2026-09-24 - DD-1052: quoted every status-format[N] literal in the zsh/bash/sh POSIX-family shell blocks of _dd_apply_tmux_ticket_status inside share/private-cli/_dashboard-core. An unquoted status-format[N] is treated as a glob pattern by zsh, which aborts the shell hook with a NOMATCH error under zsh's default options - crashing the tmux ticket-status integration on macOS, whose default shell is zsh. PowerShell block left unquoted (different argument-parsing model, does not reproduce the bug). 4.88 2026-09-24 - DD-1029 follow-up: added the standard purpose/input/output header comment to _declared_modules (missed in the original overloading.pm fix), and extended the docs vault page explaining the overload.pm -> overloading.pm transitive-dependency discovery gap. No executable change - the functional fix already shipped in v4.87. 4.87 2026-09-24 - DD-1035 follow-up: fixed a real regression in v4.86's own fix, caught by real GitHub Actions CI before this ticket reached a terminal column (Test workflow's "Run tests" step, t/183's "self-built pax runs without source checkout in its working directory"). v4.86 made $PAX_OWN_LIB_ROOT's exclusion from the normal by-@INC selection genuinely work for the first time - correct for hybrid_compiled_pcu_v1 dependencies (that's what it fixed) - but the same exclusion also stopped bundled_pure_perl/bundled_xs dependencies whose real source_path happens to live under that root (e.g. Developer::Dashboard::Pax::CLI, hit when the pax tool compiles itself - a genuine self-hosting case). Widened the force-include mechanism to cover any dependency living under $PAX_OWN_LIB_ROOT, not only hybrid_compiled_pcu_v1-packaged ones. New regression test AC-5 in t/223. Reproduced and fixed locally using the exact hostile-environment condition t/183 constructs (env -i, no inherited PERL5LIB/@INC) before landing. 4.86 2026-09-24 - DD-1035: fixed a real, published-release-breaking bug where a standalone PAX binary could crash with "Can't locate Developer/Dashboard/SeedSync.pm in @INC" on --help/jq (any subcommand reaching a hybrid_compiled_pcu_v1-packaged dependency). Two coupled defects in StandaloneImage.pm: (1) $PAX_OWN_LIB_ROOT was computed via File::Spec->catdir(...,updir,updir,updir) with no abs_path() wrap, so its own exclusion of this checkout's lib/ from runtime_inc bundling was silently a no-op; (2) the force-include mechanism for hybrid_compiled_pcu_v1 dependencies only ever protected an already-selected file from exclusion, never added a file the normal by-@INC selection failed to find. Fixed both together (fixing either alone would have made things worse). Affects all 46 hybrid_compiled_pcu_v1-packaged modules in this project, not just SeedSync. Never reproduced on any local/container host this ticket was diagnosed on - every one happened to have a stray installed copy of this project's own package somewhere on @INC, masking the gap that only a genuinely clean GitHub Actions runner exposes. New regression test t/223 (4 assertions), 2 branches genuinely unreachable and annotated. Verified clean on Alpine (musl libc, distinct Perl version) in addition to the Debian-based gate container. 4.85 2026-09-23 - DD-1010: bin/dashboard's self-compile mechanism (DD-882) now actually execs a resolved PAX cache hit instead of discarding it, via the same _exec_switchboard_command() the ps1 allowlist already used. Re-enabled once DD-930's own root cause (a CodeUnitCompiler EnvLoader.pm special-case producing an unreachable EnvAudit->record call in the compiled binary) shipped. t/182 updated (11/11 PASS, including a real compiled-binary regression run). Full suite verified clean in a container - 9 pre-existing environmental failures found and independently confirmed unrelated to this change (cpan-audit missing, git-worktree-in-container, Devel::Cover missing from the dev image). Real timing measured: cold PAX compile 86.8s, interpreted "version" 165ms, self-exec warm run 129-138ms, the same compiled binary invoked directly 2-4ms - the compiled binary itself is 40-80x faster, though bin/dashboard's own outer interpreted startup currently dominates the end-to-end warm-path cost for a trivial command. 4.84 2026-09-23 - DD-1021: t/09-runtime-manager.t and t/105-web-app-coverage-2.t now pass in a root, ss-less, file-less container - t/09 stubbed command_in_path('ss') alongside the existing capture() mock; t/105 uses an attempt-based (not identity-based) skip for its unreadable-file assertion, matching t/78's established pattern. - DD-1043: d2 .version now natively reads VERSION= from a skill's own .env when the skill ships no cli/version script, instead of requiring every skill to hand-write one. - DD-1044: EnvLoader::_load_env_pl_file now records a .env.pl's same-value re-assignment ($ENV{KEY} set to the value it already had) in DEVELOPER_DASHBOARD_ENV_AUDIT - a pure before/after value diff could never see this case. - DD-1009: Pax::ArtifactCache::write_artifact now writes its cache-metadata JSON atomically (temp path + rename), closing the same truncate-on-interrupt defect class already fixed in DD-989 and DD-1003. - DD-1023: release-github.yml and test.yml now install binutils explicitly before their build-dependent steps, fixing t/183's self-hosted pax-build scenarios on a runner missing `as`. 4.83 2026-09-22 - DD-1015: real Windows amd64 PAX build wired into pax-release.yml, replacing the placeholder - MinGW-w64's real GNU objcopy supports the same binary-to-object conversion the Linux path already uses, targeting PE-COFF. windows-arm64 stays a placeholder pending verification of binutils' PE-ARM64 target name against the real tool. - DD-1025: the GitHub Release page only ever attached the source tarball - the compiled PAX binaries existed only as ephemeral workflow-run artifacts, not discoverable on the release page and subject to expiry. Fixed by calling pax-release.yml as a reusable workflow from release-github.yml and attaching its build outputs to the release as real assets. 4.82 2026-09-22 - DD-1022: `_skip_dependency_module` wrongly treated `overload` as a compile-time-only pragma like strict/warnings/utf8, so it was never bundled into a standalone binary's runtime payload. Any subcommand whose code path reached File::Temp (which itself `use overload`s stringification/numification) crashed at BEGIN time with "Can't locate overload.pm in @INC" - shipped in v4.78/ v4.79 undetected because the release smoke-check only ever ran `dashboard version`. Fixed by removing `overload` from the skip list. 4.81 2026-09-22 - DD-1020 (real fix for t/183, found via real CI on v4.80): the compiler probe added for DD-1020's objcopy architecture detection ran BEFORE _compile_launcher's own PATH restoration was applied, so it failed under a caller that deliberately strips $ENV{PATH} (t/183's own standalone-execution test) - misdiagnosed as DD-1023 ("as"/binutils missing from the runner, which was never actually true). Fixed by moving the probe inside the same PATH-restoration scope the objcopy/cc calls already use. 4.80 2026-09-22 - DD-1019: t/159 and t/185 were failing for real, deterministic reasons (not CI-load flakiness as first assumed) - a stale $?-guard baseline entry, 2 genuinely-unguarded local $?; gaps, a heredoc blind spot in the sweep's own comment-stripper, and t/185 missing the DD_PAX=on opt-in that a later change required. All fixed and verified. - DD-1011: t/43's usage-string regexes for 'file add'/'path add' were stale since DD-1005 added the -c/--create flag. Corrected. - DD-1023: release-github.yml and test.yml's runners lacked a working assembler (as), breaking t/183's self-hosted pax-build scenarios. Fixed by installing binutils explicitly. 4.79 2026-09-22 - DD-1020 (correction, same day as v4.78's fix): the archname-based objcopy architecture detection was itself wrong for linux-i686 cross-compilation - a real GitHub Actions run confirmed linux-arm64 genuinely fixed (native aarch64 runner) while linux-i686 still failed, because `$Config{archname}` reflects the Perl interpreter's own build, not a `-m32`-wrapped cross-compile target. Replaced with a real compiler probe (`_compile_probe_object_header`) that compiles a trivial source with the actual `cc` and reads back the genuine ELF class/machine the result carries, honoring any PATH wrapper. 4.78 2026-09-22 - DD-1020: `_compile_launcher`'s objcopy invocations previously hardcoded an x86_64 `--output`/`--binary-architecture` spec unconditionally, breaking non-x86_64 PAX build hosts - confirmed live in real CI, where linux-arm64 and linux-i686 both failed at the objcopy step while linux-amd64 succeeded fully. Added `_objcopy_target_for_arch`, which detects the build host's real architecture from `$Config{archname}` and selects the correct objcopy target pair, verified against the real objcopy/binutils tool for x86_64/i686/aarch64. Unblocks DDE-006's linux-i686 and linux-arm64 multi-platform PAX CI targets. 4.77 2026-09-22 - DD-1018 (follow-up): a real PAX Release CI run of v4.76's two prior fixes surfaced a third defect - `share/private-cli/pax`'s FindBin- based lib resolution failed in the actual GitHub Actions environment ("Can't locate Developer/Dashboard/Pax/CLI.pm"), though every local invocation this session ran succeeded. Fixed by passing `-Ilib` explicitly on the `perl` invocation in `pax-release.yml`'s Linux build step, sidestepping FindBin+cwd inference entirely. 4.76 2026-09-22 - DD-1018: `pax-release.yml`'s Linux matrix jobs now bootstrap Perl and `cpanm` via the same `shogo82148/actions-setup-perl` step `test.yml` already uses (real CI runs failed with "cpanm: command not found" otherwise). Also re-pinned `actions/upload-artifact` from v4.6.2 (node20, below this project's enforced node24 floor) to v7.0.1 (node24-verified) - a second real defect found via the same live CI investigation. Every real PAX Release workflow run failed from one of these two causes until now. 4.75 2026-09-22 - DD-1017: `bin/d2`'s dispatch line unconditionally routed through the perl interpreter to invoke its sibling `dashboard` file, assuming it was always Perl source - crashing whenever that sibling was itself a PAX-compiled standalone binary. Fixed by detecting compiled-vs- interpreted via the sibling's first two bytes (Perl source always opens with a `#!` shebang, a compiled binary never does) and execing the compiled binary directly with no perl interpreter when appropriate. The existing interpreted-source dispatch path is completely unchanged. 4.74 2026-09-22 - DD-1013: wired `.github/workflows/pax-release.yml`'s linux-amd64/ arm64/i686 matrix jobs (part of epic DDE-006) to a real `dashboard pax build` invocation and a native smoke check comparing the compiled binary's own `version` output against the source-Perl CLI's, failing the job on any divergence. linux-i686 installs gcc-multilib and routes around `_which('cc')`'s hardcoded PATH resolution (it never reads `$ENV{CC}`) with a PATH-prepended wrapper forcing `-m32` - caught and fixed a self-recursion bug in that wrapper before landing (it execed the bare name `cc` instead of an absolute path, so it called itself forever). d2 is deferred pending DD-1017 (bin/d2's re-exec line cannot currently dispatch to a compiled dashboard binary, found live while implementing this ticket). 4.73 2026-09-22 - DD-1012: added `.github/workflows/pax-release.yml`, part of epic DDE-006 (Multi-platform PAX CI) - a push-to-master workflow skeleton matching `package-ghcr.yml`'s established trigger convention, with a 6-entry build matrix covering the owner-confirmed platform/arch targets (mac arm64; linux arm64/amd64/i686; windows arm64/amd64) and per-target artifact-upload wiring. The real `dashboard pax build` invocation and functional verification per platform are deferred to sibling tickets under the same epic. 4.72 2026-09-22 - DD-1002: nine `lib/Developer/Dashboard/Pax/*.pm` modules each hand-rolled their own `JSON::XS->new->...->encode()` chain with independently drifted option sets, instead of using the shared `Developer::Dashboard::JSON` wrapper the rest of the codebase already uses. Added `json_encode_with_options($value, %opts)` supporting the ascii/pretty/utf8 combinations these call sites need, and converted all nine sites (`ArtifactCache.pm`, `AppImage.pm`, `AppServer.pm`, `CLI.pm`, `CodeUnitCompiler.pm` (6 sites), `ProfileGuidedAOT.pm`, `StandaloneImage.pm`) - `Pax/Capture.pm`'s isolated child-probe call site is a documented, deliberate exception (runs with no guaranteed `-I lib` path). 4.71 2026-09-22 - DD-1006: `_compile_launcher` in `Pax::StandaloneImage` ran a build eval followed by a cwd-restore eval and read `$@` once for both failure paths' `reason` field. Perl resets `$@` at the start and on successful completion of every eval, so when the build eval failed but the subsequent restore eval succeeded (the common case), the restore eval's success silently erased the real build-failure diagnostic before it was ever read - a failed standalone-image build reported an empty reason. Fixed by capturing each eval's `$@` into its own variable immediately after that eval returns, before the other eval can run and clobber it. 4.70 2026-09-22 - DD-1005: `d2 path add`/`d2 file add` now accept a `-c`/`--create` flag (with an optional octal chmod mode, in bare/space-separated/=-form/ short-flag syntax) marking an alias as lazily creatable. The first time anything resolves a lazy-create alias whose target is missing, it is created - a directory (with parents) for a path alias via `PathRegistry::resolve_dir`, or just the parent directory chain for a file alias via `FileRegistry::resolve_file` (per Q-184: a file alias's own target file is left absent, since the caller intends to write it). The create/mode metadata composes cleanly with DD-1004's nested skill-depth alias storage, and is fully backward compatible - an alias saved without `-c`/`--create` stores and reads back exactly as a plain path string, as it always did. 4.69 2026-09-21 - DD-1003: `PaxCache::_run_compile_and_install`'s `md5_file` digest write is now atomic (write to a sibling temp path, then `rename()`), matching the atomic-install pattern already used for `bin_file` two lines above it. Previously a plain `open('>', $md5_file)` truncated the file to zero bytes the instant `open()` succeeded, well before the digest was written - any interruption in that window (SIGKILL, OOM, host reboot) left `md5_file` as a real, existing, zero-byte file beside an already-valid, already-renamed binary, permanently wedging `resolve()` into treating a fresh cache as stale forever. `t/206-paxcache-md5-atomic-write.t` proves the fix directly via a fork+SIGKILL race against the real `_run_compile_and_install` path. 4.68 2026-09-21 - DD-1004: `d2 path add`/`d2 file add` now support skill-depth-prefixed dotted alias names (e.g. `d2 path add foo.bar.something /foobar`), resolving arbitrarily nested `skills//skills//...` directories. The write NEVER targets a skill's own `config/config.json` directly, at any depth, regardless of whether that skill carries its own `.git` (owner correction, Q-182, generalising the original git-preservation framing): it always walks up to the nearest git-free PARENT ancestor, or the global config's `skills` fallback section when none exists (always true for a depth-1 name, which has no parent skill) - storing the alias as a NESTED hash mirroring the remaining depth segments, never a flattened dotted string. A user's override this way always shadows a skill's own shipped default at read time without ever modifying that skill's file, letting a skill's shipped config survive every future `d2 skill install`/update untouched. New: `PathRegistry::nested_skill_dir_chain`/`nested_skill_entries`/ `skill_config_write_location`; `Config::split_skill_alias_name`, `save_skill_path_alias`/`save_skill_file_alias`, `remove_skill_path_alias`/`remove_skill_file_alias`, and the top-level `save_path_alias`/`save_file_alias`/`remove_path_alias`/ `remove_file_alias` dispatchers both CLI::Paths and CLI::Files now route through. An undotted alias name keeps today's exact global-config behavior, byte-for-byte unchanged. Added t/205-skill-depth-alias.t. 4.67 2026-09-21 - DD-989: fixed a symlink-clobber vulnerability in Developer::Dashboard::IndicatorStore::set_indicator. Its staging path used to be the bare literal "$file.pending" with no pid/time/counter uniquifier at all, and permissions were secured only AFTER the write - the exact write-then-secure anti-pattern already fixed six other times (DD-599/600/601/602/848/850), but worse: a fully predictable path, missed by both prior sweeps because neither searched for a pending-file writer with no uniquifier whatsoever. Live-reproduced: a symlink pre-planted at the predictable staging path let a local actor clobber an arbitrary victim file's content and leave the real status.json pointing at the victim afterward. Fixed by routing through the shared Developer::Dashboard::PathRegistry::atomic_write_secure helper via a new _pending_indicator_file staging-path helper (pid + time + a per-process monotonic counter), matching the pattern already used by SessionStore.pm/Auth.pm/Collector.pm/CLI/Ask.pm/Zipper.pm. Added t/204-indicatorstore-pending-path-symlink.t, a regression test that plants a symlink at the historical predictable path and asserts the victim file is untouched and the real indicator file ends up a regular file with correct content. Updated docs/pending-file-staging-paths-across-the-codebase.md to add IndicatorStore as the 7th migrated site. Filed DD-999 (pre-existing 500-line module-size overage) and DD-1000 (unaudited lock-file symlink exposure, low severity) as separate, out-of-scope follow-ups found during this ticket's audits. 4.66 2026-09-21 - DD-993: fixed a CI-only, warnings-FATAL build failure ("Prototype mismatch: sub Developer::Dashboard::RuntimeManager::sleep (;$) vs (;@)") in t/100-runtimemanager-coverage.t and t/106-runtimemanager-coverage-2.t. RuntimeManager.pm imports its "sleep" from Time::HiRes, so RuntimeManager::sleep is an alias whose prototype is whatever the running Perl's Time::HiRes gives sleep() - "(;$)" on the Perl 5.44 GitHub Actions used, "(;@)" on the Perl 5.40.1 this project's own Docker image bundles, which is why the bug was invisible locally. Both coverage files installed their test-only mock of RuntimeManager::sleep as a permanent (non-local) typeglob assignment guarded only by "no warnings 'redefine'", which does not cover Perl's separate 'prototype' warning category - even an unprototyped override mismatches "(;$) vs none" once only 'redefine' is silenced. Fixed by silencing both categories together ("no warnings qw(redefine prototype);") and dropping the mock's own explicit prototype. Added t/203-runtimemanager-sleep-mock-prototype.t, a deterministic, Perl-version-independent regression test for the mechanism, and a new system-scoped section in docs/expected-warnings-in-tests.md on overriding subs imported (not locally declared) from another module. 4.65 2026-09-21 - DD-941: t/15-release-metadata.t's retired-internal-wording gate (unlike $doc, qr/\blegacy\b/i) correctly bans that retired wording, case-insensitively, in shipped docs/POD, but FIXED_BUGS.md's append-only DD-931 (4.37) and DD-933 (4.42) entries legitimately reference the real, current, shipping identifier __PAX_RUNTIME_LEGACY_NAMESPACE__ and describe it in prose as a "legacy-namespace alias" - the DD-931 entry uses that phrase without the literal identifier present on the same line, so an identifier-only exemption did not close the gate (owner decision, Q-179). Widened the allow-list (a new _strip_legacy_namespace_mentions helper in t/15-release-metadata.t) to also strip the phrase "legacy-namespace" case-insensitively before the check runs, alongside the existing identifier exemption, while a genuinely retired bare mention of that same banned term anywhere else still fails the gate. No FIXED_BUGS.md entry was reworded. 4.64 2026-09-21 - DD-926: pax build's intermediate build cache directory (.pax-launcher-build, under Developer::Dashboard::Pax::StandaloneImage's _compile_launcher) was a single literal path shared by every uid that ever built against the same output parent directory (commonly bare /tmp). A root-owned build (e.g. from inside a developer-dashboard:latest container) left it root-owned, permanently breaking every later non-root build sharing that output parent with a Permission Denied that did not self-clear. Namespaced the directory per invoking uid (.pax-launcher-build-, via a new _pax_launcher_build_dir_name helper), the same technique File::Temp uses to avoid this exact shared-/tmp collision class. Cache reuse within one uid's repeated builds is unchanged. 4.63 2026-09-21 - DD-986: CLI/Skills.pm kept its own private _render_table/_format_row, left out of DD-773's consolidation of Files.pm/Paths.pm/Which.pm onto the shared Developer::Dashboard::CLI::TableHelpers::render_table. Confirmed via grep of every real call site that its ANSI-stripping behavior (_plain_text) was dead weight - the only content-producing helpers feeding cells return hardcoded plain text. Migrated Skills.pm onto the shared helper, removed ~40 lines of duplicated code, verified byte-identical output for representative skill-list data. 4.62 2026-09-21 - DD-984: Developer::Dashboard.pm's "Layered Env Files" POD section had no cross-reference to DD-979's env->include/# include feature, documented ~2500 lines later with no backlink either. Added a pointer paragraph in both directions; README.md regenerated. 4.61 2026-09-21 - DD-985: fixed a path-traversal vulnerability (CWE-22) in 'dashboard of'/open-file's exact-match fast path (_scope_relative_path_match). A '..' segment in the search pattern escaped the resolved scope directory entirely, so ordinary usage such as 'dashboard of myproject ../../.ssh id_rsa' could open or print any file readable by the invoking user, with no special flag required. Fixed by resolving through the project's existing lexical containment helper (_contained_cache_path, the same one DD-498's Zip Slip fix established) instead of a second bespoke check. The regex-search fallback path was confirmed structurally non-vulnerable and left untouched. 4.60 2026-09-21 - DD-979: new .env/.env.pl skill include feature. A "# include " comment directive in a plain .env file (or "# include " to also pull in every nested sub-skill) loads that skill's own .env/.env.pl into the current environment on request; env->include('foo.bar') does the same programmatically from a .env.pl file after `use Developer::Dashboard;`. env is a real second package (Perl resolves a bareword before -> as a literal package name at parse time, so it cannot be a plain exported sub like d2). Included variables are namespaced __ (foo.bar's BOB=1 becomes FOO_BAR__BOB), resolved DD-OOP-LAYERS-style across every layer with deeper layers winning without dropping shallower-only keys. Hardened against a path-traversal exposure found during implementation (a spec like "../.." surviving the dot-split as a bare "/" segment) with an explicit _valid_segment() guard. New module Developer::Dashboard::EnvInclude, 100.0/100.0/ 100.0/100.0 coverage, 42 new tests, new SYSTEM-scoped doc docs/env-skill-include-directive.md. 4.59 2026-09-21 - DD-978: the parallel gap to DD-977, for file_aliases instead of path_aliases - a skill's own config/config.json file_aliases block was completely invisible to cdr/d2 paths. Added Config::_skill_file_aliases() (an exact parallel to DD-977's _skill_path_aliases()), merged into file_aliases(). Same skill-name qualification convention, same OOP-LAYERS-safe-by- construction merge behavior. Verified with 20 new tests and a live end-to-end docker probe. 4.58 2026-09-21 - DD-977: a skill's own config/config.json path_aliases block was completely invisible to cdr/d2 paths - Config::path_aliases() only ever read the top-level project/global merged config key. Added Config::_skill_path_aliases(), which merges in each installed skill's own path_aliases, qualified by skill name (skill.alias). Verified OOP-LAYERS recursive merge with two real layers of one skill. file_aliases has the identical gap, tracked separately as DD-978. 4.57 2026-09-19 - DD-964: docs/pax-coverage-measurement.md's follow-up-work pointer cited DD-929, which is now closed (done-not-released) and never did that follow-up. Filed DD-967 as the real open follow-up ticket and updated the doc to cite it instead. 4.56 2026-09-19 - DD-929: lib/Developer/Dashboard/Pax/*.pm was entirely absent (zero rows) from every Devel::Cover run - a PAX-compiled binary's extraction root gets spliced onto PERL5LIB before exec, and Devel::Cover's default startup behavior silently ignores anything already on @INC at that point. The extraction path was already stable (content-addressed by source_hash); the missing piece was telling Devel::Cover to -select it explicitly. Added Developer::Dashboard::Pax::CoverageSelect (4 tested helper functions) and t/200-pax-coverage-select.t (32 tests, including a live build-and-run integration check), plus docs/pax-coverage-measurement.md documenting the mechanism. 4.55 2026-09-18 - DD-958: Platform.pm's resolve_runnable_file (lines 183-184) and command_argv_for_path (line 226/227's Windows fallback) each had an untested direction, on both the forced-Windows side and its natural Linux counterpart - Devel::Cover's true/false columns track the literal written condition, not its display-rewritten "unless" text. Added 4 targeted test assertions to t/84-platform-coverage.t (2 forced via the project's established local $OS_NAME override, 2 natural-Linux counterparts); Platform.pm now 100.0 on all 4 Devel::Cover metrics. New SYSTEM-scoped page docs/forced-windows-unit-tests.md documents the override convention. 4.54 2026-09-18 - DD-928: Zipper.pm's load_saved_ajax_code open()-failure branch now carries a correct `# uncoverable branch true` annotation (it had none), restoring 100.0 branch coverage - the failure path is only reachable as a non-root user, and gate containers run as root. Extends docs/uncoverable-annotations.md with the underlying mechanism: Devel::Cover rewrites `EXPR or STATEMENT` as `unless(EXPR){STATEMENT}`, inverting true/false from the naive reading. 4.53 2026-09-18 - DD-953: measured PAX self-compiled vs interpreter speed for the 7 data-query CLI commands (jq/yq/tomq/propq/iniq/csvq/xmlq) against an equivalent ~50,000-record fixture, in a developer-dashboard:latest container - documented in docs/pax-alpine-and-runtime-speed-measured.md. Finding: the compiled binary was slower on every command (1.2x-6.4x), the opposite of the earlier dashboard-version benchmark - a real parsing workload's own cost dominates the standalone binary's unpacking overhead. No code changes; a measurement/documentation ticket only. 4.52 2026-09-18 - DD-951: skills using Go now get per-layer module/build cache isolation - `_exec_go_source` sets GOMODCACHE/GOCACHE to a skill layer's own `local/go-cache/` directory whenever a sibling go.mod is found, so two skills declaring different Go module versions never share one global cache. Mirrors DD-823 (Java/Maven) and DD-824 (Python/venv). A skill with no go.mod is unaffected. 4.51 2026-09-18 - DD-946: `dashboard ask`'s direct-API backend (--claude, with a key set) now runs a real Anthropic tool_use round-trip loop instead of a single non-streaming POST, offering two read-only tools (read_file, grep_repo) scoped to the current project root - closing the gap where only the CLI-fallback path could answer genuinely repo-specific questions. A plain question needing no repo search is unchanged (one request, same shape as before). 4.50 2026-09-18 - DD-952: added Amazon Nova as a dashboard ask backend (--nova), against Nova's own standalone REST endpoint (api.nova.amazon.com/v1/chat/ completions, bearer-token auth via NOVA_API_KEY) - the same direct-API shape --claude already uses, not AWS Bedrock/SigV4. 4.49 2026-09-18 - DD-954: a skill's own cli/__init__. self-script now runs when it is invoked with no subcommand at all - d2 (bare) and d2 . (dotted, when no explicit subcommand file exists) both fall back to it, any extension. An explicit top-level or subcommand always wins over the fallback. 4.48 2026-09-18 - DD-948: dashboard ask's CLI-fallback backend path (codex/copilot/gemini, or claude with no API key set) now tees its child process's output live to the caller's terminal instead of fully buffering it until the process exits. Switched _run_cli from Capture::Tiny's capture{} to tee{}. The direct-API path is unchanged (real streaming there needs Anthropic SSE wiring, a separate, larger change). 4.47 2026-09-18 - DD-950: excluded a stray NYTProf profiling artifact (nytprof.out) from the release tarball. dzil's GatherDir gathers from disk, not git, so an untracked, ungitignored leftover file ships exactly like a real source file - the same mechanism that let FIX.md leak into the 4.22 tarball (DD-401). Found auditing DD-947's 4.46 build. Added dist.ini exclusion, .gitignore entries for nytprof.out/nytprof/, and RED-then-GREEN test coverage in t/15-release-metadata.t. 4.46 2026-09-18 - DD-947: extracted CollectorRunner.pm's command-execution cluster (run_command, await_windows_command, spawn_windows_command, record_command_pid, command_launcher_argv, command_pid_from_file, await_command_pid, forward_command_signal, terminate_command_process, exit_code_from_status) into a new sibling module, Developer::Dashboard::CommandRunner - first cut toward DD-641's 1963-line oversized-module finding. Confirmed zero instance-state ($self->{...}) dependency in the 241-line cluster before moving it; every original call site kept as a one-line forwarder so no existing caller changed. CommandRunner.pm reaches 100.0 on all four Devel::Cover metrics. CollectorRunner.pm drops from 1963 to 1754 lines. 4.45 2026-09-18 - DD-942: closed Ask.pm's pre-existing branch/condition coverage gap (confirmed present before DD-938 touched the file: 99.2/89.4/77.7/100.0 stmt/branch/cond/sub). Added 52 new regression subtests to t/48-ask.t, calling private helpers directly via $M->can() where run_ask's public interface cannot reach a specific internal branch. Result: 100.0/98.5/96.8/100.0. Two residual lines are genuine Devel::Cover tooling limitations, filed separately as DD-944. Also caught and fixed a genuine regression mid-gate (t/168-permission-assertion-guards.t) from an incorrect uid-based SKIP guard, rewritten to match this project's established probe-then-restore permission-test pattern. 4.44 2026-09-18 - DD-937: reviewed the Tira board-engine upgrade 5.131 -> 5.150 (19 releases). Nothing new to declare (tira.policy.undeclared confirmed empty). Adopted two genuinely useful additions into how this project reads its own board: `d2 tira.police.explain --ref REF --rule RULE` (prints a rule's exact computation rather than only its final verdict, verified live against DD-667's own long-standing card-duration finding) and the `checklist-idle` "standing" label exemption for a container card meant to stay open indefinitely. Findings recorded in docs/tira-upgrade-review.md. Documentation-only, no product code changed. 4.43 2026-09-18 - DD-943: fixed t/15-release-metadata.t's _perl_doc_paths() incorrectly scanning t/183-pax-cli-build-run-contract.t's leftover scratch fixtures under t/tmp-sow03/ (deliberately minimal-POD test inputs for the PAX build pipeline, never cleaned up at the end of that test's own run) - causing ~25 spurious POD-completeness failures whenever t/183 happened to run before t/15 in the same prove process. Added the same exclusion pattern already used elsewhere in the same file. Confirmed pre-existing (present in CI before DD-933 landed, not caused by it). 4.42 2026-09-17 - DD-933: generalized DD-931's finding - CodeUnitCompiler.pm's dependency-discovery pass walks literal source text to decide a compiled entrypoint's compiled_packages closure, so a class referenced only inside a sub body the compiler special-cases (substituted wholesale with a runtime op) is invisible to it and never gets a __PAX_RUNTIME_LEGACY_NAMESPACE__ alias installed for some entrypoints. Confirmed the same gap hits JSON (~53 call sites) and SeedSync (4 call sites) in StandaloneRuntime.pm, called via the legacy-namespace alias from inside other special-cased ops. Fixed by requiring both classes directly at file-load time and calling them by their real package names, same convention already shipped for EnvAudit in DD-931. New test t/198 proves genuine RED-then-GREEN by invoking the real op dispatcher directly. Verified against a real Docker container with a clean-master baseline comparison: zero regressions. 4.41 2026-09-17 - DD-934: fixed a compiled bin/dashboard entrypoint crash on ANY subcommand with a trailing argv token ("Can't use string as a HASH ref"). StandaloneRuntime.pm's _run_cli_router_unit called _prime_command_result_env with only ($cmd, @ARGV), silently dropping the $main_gate_results positional argument the function's own signature expects - the first argv token filled that slot instead of a hashref, crashing the moment any argument followed the command name. Fixed by passing an explicit {} at the call site. This blocked safely re-enabling self-exec, since almost every real invocation carries at least one argument. Verified RED-then-GREEN against a real compiled binary. 4.40 2026-09-17 - DD-938: dashboard ask is now a Developer Dashboard expert by default. `dashboard ask --docs` prints a curated onboarding summary (DD-OOP-LAYERS, .env/docker-compose conventions, where a disposable helper script belongs) directly to stdout - never the full CLAUDE.md, never a new file. `dashboard source --files` lists every file installed under ~/perl5/{lib,bin} as a fallback deep-dive reference. A workspace's genuinely first-ever `dashboard ask ` call (empty transcript, not --no-memory) silently prepends the --docs context to that turn's prompt - no flag, no injected instruction line - and every later turn sees it already in replayed history. Also fixed a t/196 numeric-prefix collision with a pre-existing test file (renamed to t/197) and three POD gaps surfaced by the release-metadata gate. 4.39 2026-09-17 - DD-935: fixed a pathological per-character substr() loop in CodeUnitCompiler.pm's own _extract_sub_body/_extract_sub_source (the compiler self-compiling its own ~13,000-line source was the dominant cost of a pax build - 4.5+ minutes for one file vs ~4 minutes for the other 112 combined). Replaced with a \G-anchored regex scan: 1.4s -> 0.0005s per extraction call, verified repeatedly in isolation. Also dropped the launcher's cc flag from -O2 to -O0. A second, separate contributor to full-pipeline compile time was found during verification and filed as DD-939. 4.38 2026-09-17 - DD-936: PaxCache::resolve() no longer spawns a new background pax build by default - owner-specified opt-in via DD_PAX=on, after observing pax build burn ~66% CPU for 90+ seconds per invocation merely from running dashboard/d2 init. Gate scoped to only the spawn-a-new-compile path (not cache-hit reporting), after an earlier blanket-gate version was found to break bin/d2's own self-exec feature and corrected the same day (Q-167). 4.37 2026-09-17 - DD-931: require Developer::Dashboard::EnvAudit directly (instead of the never-populated __PAX_RUNTIME_LEGACY_NAMESPACE__ alias) in both of Pax's env-loading runtime ops, root-causing DD-930's mitigation. Verified against a real compiled binary: EnvAudit string-table references go from 0 (crash) to 51 (clean run). bin/dashboard's self-exec-on-cache-hit guard stays DISABLED - genuine end-to-end verification surfaced three further, independent defects (DD-932, DD-933, DD-934), each filed separately. 4.36 2026-09-17 - DD-924: d2's self-compiled binary resolved $Bin to the pax cache directory it physically ran from, not the original bin/ source directory, breaking its own exec-into-dashboard fallback with "Can't open perl script". Fixed by baking the correct path into an env var on the genuinely-interpreted first run, before self-exec. 4.35 2026-09-17 - DD-922: root-caused and fixed StandaloneRuntime's file-read corruption (a `local $/;` spanning too far, leaking into a later `eval` of the entrypoint's own compiled source via eval STRING's shared dynamic scope). Re-enabled bin/dashboard's self-exec-on-cache-hit - and DD-930 (same day) found a second, independent defect in the same area and disabled it again; see that entry. - DD-923: fixed a UTF-8 double-encoding defect in CodeUnitCompiler.pm that corrupted literal non-ASCII characters (e.g. dashboard ps1's status icons) in PAX-compiled binaries. - DD-930: bin/dashboard's self-exec-on-cache-hit disabled again, hours after DD-922 re-enabled it - a second, independent PAX-compiler defect (EnvAudit->record failing inside the compiled binary's own legacy namespace) was found the moment the exec path was genuinely exercised against real project config for the first time since DD-905. 4.34 2026-09-17 - DD-917: OpenFile.pm's _scope_match_rank compiled/resolved every pattern's regex unconditionally at the top of its per-pattern loop, even though two cheaper checks (exact basename match, prefix match) are tried first and often decide the score without ever needing it. Resolved lazily instead, only at the point of use. 4.33 2026-09-16 - DD-918: lib/Developer/Dashboard/CLI/OpenFile.pm was 823 lines, exceeding this project's 500-line-per-module guideline. Extracted the Java-class resolution machinery (archive extraction, Maven Central search/download, the --online network gate) into OpenFileJavaSource.pm, the interactive multi-match chooser into OpenFileChooser.pm, and shared dedup helpers into OpenFileUtil.pm. All moved subs are re-exported into OpenFile.pm's own namespace so existing callers and t/98-cli-openfile-coverage.t need no behavior change. 4.32 2026-09-15 - DD-868/DD-870/DD-878: FileRegistry::resolve_file, PathRegistry::resolve_dir, File::_resolve_file and Folder::_resolve_path all fell back to dispatching a caller-supplied alias name to ANY method the resolved object/class answered can() true for. Replaced with a compile-time allowlist of the intended no-arg path getters in each. - DD-874: Config.pm's api_registry merged the operator's own deepest-writable api.json layer BEFORE a skill's bundled key fragments, so an installed skill could silently replace or tombstone an operator-set API key. Skill fragments now merge first (lowest priority), the operator's own layer last (highest). - DD-877: added Developer::Dashboard::PaxCache, an MD5-keyed, non-blocking PAX compile cache - a cache miss runs interpreted immediately and triggers at most one detached background compile per source file, with an O_CREAT|O_EXCL lock guarding against overlapping invocations spawning duplicate compiles. Wired into dashboard ps1 as a proof of concept. - DD-871/872/875/876: researched PAX (a sibling adaptive Perl compiler) compiling this codebase - confirmed clean on glibc/Debian with a measured ~36x runtime speedup, found and reported two PAX bugs upstream (an objcopy long-option ambiguity on newer binutils, a hardcoded ELF output width ignoring the target architecture), and confirmed PAX has no macOS Mach-O build path at all (hardcoded ELF-only, zero platform branching) - a real, immediate blocker for any Mac user. - DD-866/867/869/873: corrected drifted architecture-overview POD in Developer::Dashboard.pm (an inactive UpdateManager module described as wired-in when it is intentional dormant groundwork; Data Query Commands listing 4 shipped formats instead of all 7; "prompt commands" phrasing inconsistent with the file's own established "ps1 commands" wording). 4.31 2026-09-11 - DD-770: .claude/tools/host-ready's foreign-process detector matched on command-line TEXT, which is spoofable - a process can rewrite its own $0, and ps args/cmdline/comm all read the same rewritten buffer. Added a positive-only /proc/PID/exe veto to dd_foreign_now(): a candidate is excluded only when exe provably resolves to something other than perl/prove/cover; an unreadable exe (the ordinary case across a container's mount namespace) leaves it counted, never turning could-not-look into excluded. - DD-771: corrected a false premise found during this card's own research - the /tmp/dd-gate-host.lock shared with the Tira project is deliberate (TKT-857), not an accidental collision. Fixed the two genuine survivors: run-suite's refusal now names the holding project via its cwd instead of a bare pid, and a retry no longer silently truncates a failed attempt's log (preserved to $LOG.prev before truncation). - DD-740: extracted .claude/tools/board's DD-545 checkout-root resolution (git --git-common-dir) into a shared .claude/tools/checkout-root helper; board-pulse, next-action and tira-bridge-run now source it instead of each hardcoding the checkout's absolute path. - DD-785: extracted the duplicated paths-registry constructor guard from seven modules (Collector, Doctor, IndicatorStore, PageStore, Housekeeper, FileRegistry, Prompt) into Developer::Dashboard::PathsRegistryArg (require_paths_arg), mirroring DD-762's DirEntries.pm precedent. - DD-767: bound t/100 and t/103's empty-environ probe loops by wall-clock (Time::HiRes, 60s) instead of a fixed 3000-poll count - closing a gap DD-482 specified but never shipped. A poll count is not a stable time bound under Devel::Cover, where instrumentation overhead and host contention both change what a fixed count buys; raising the count (as shipped twice before) failed a third time under real load. Also fixes a zombie-blind "-e /proc/PID" liveness check with a state+cmdline diagnostic. 4.30 2026-09-05 - DD-810: the switchboard now runs a main gate before resolving any command: every executable file directly under /hooks/ in the runtime layer stack runs once per invocation, deepest layer first and home last, with the full command argv, and each hook's RESULT/LAST_RESULT is recorded under the same keys the per-command chain writes. A hook that prints the marker [[STOP]] on stderr skips the remaining hooks and the command does not run; dashboard exits 1 with the hook's stderr visible. A layer stack with no hooks/ directory produces bit-for-bit today's output and an empty stderr. The hooks run with DEVELOPER_DASHBOARD_MAIN_GATE set so a nested dashboard or d2 does not re-fire the gate, and d2's re-exec of dashboard fires it exactly once. `dashboard which ` lists the main-gate hooks before the per-command ones, in run order. - DD-764: a stored session record whose expires_at was absent, empty or "0" was accepted as valid forever by SessionStore::from_cookie and skipped by sweep_expired, so the record was both immortal and uncollectable - while a malformed timestamp already failed closed, because _iso8601_to_epoch returns 0 for anything not matching its strict ISO-8601 pattern. Both truthiness guards are gone, so an absent expiry now compares the same way malformed input already did: the session is rejected and deleted on use, and the file is collected by the sweep. create() always writes expires_at, so reaching this needed a record written outside the product's own path - a hand-edit, a restore, or an older schema - which makes it a latent fail-open on a session bearer credential rather than an exploitable bug. - DD-737: a collectors config entry carrying only a name and an interval (no 'command' and no 'code') was forked into a loop worker anyway. That worker died on its very first tick with "Collector missing command or code" and on every tick after that, forever, without ever disabling itself, leaving an unreaped process behind each time it died on a host whose PID 1 does not reap orphans. Reproduced in the project's container image, where two `dashboard restart` cycles left three or more defunct "dashboard collector" entries that later restarts never cleared. start_loop now runs the same command/code validation _collector_source already performed per tick, before writing a pidfile or forking, so a misconfigured collector fails immediately with that error and spawns no process. - DD-753: RuntimeManager launched PowerShell by hardcoding the bare string 'powershell' at four call sites, while CollectorRunner did the same job through a resolver that tries PATH under both spellings, then the SystemRoot install path, and names the missing executable when none resolves. On a Windows host where PowerShell is not on PATH, the collector said what was wrong and the dashboard runtime failed through system() into an empty capture, indistinguishable from a legitimately empty result. The resolver now lives once in ProcessSupervision and is used by both modules, so all four runtime call sites resolve PowerShell the same way, and each reports the missing executable in the shape its own contract requires - a status pair, a die, or an empty list. Verified on Linux only: no Windows guest was available, and the card records the platform verification as still owed. - DD-738: d2()'s AUTOLOAD handled only a single word and shelled out immediately, returning a plain string, so a second method call on it - d2()->collector->list - died as a bareword class lookup. A bareword method now starts a lazy proxy that accumulates dotted segments at any depth, mirroring the CLI's own dotted dispatch, and nothing executes until the chain is terminated with a call: d2()->foo->bar->() runs `dashboard foo.bar`. An un-terminated chain is inert in boolean, numeric and string context and stringifies to something obviously non-executing ("d2 proxy: foo.bar"), so a stray debug print cannot run a command; underscores in a segment are rewritten to hyphens, since a Perl method name cannot carry a hyphen and the hyphenated half of the CLI was otherwise unreachable through the chained form. INCOMPATIBLE CHANGE, and it reaches the single-word form too: d2->doctor now needs the trailing ->() to execute. d2->run(...), which takes its words as separate arguments and passes them through verbatim, is unaffected. - DD-616: Developer::Dashboard::File and Developer::Dashboard::Folder each carried their own copy of the computation that builds the configured-alias cache key, so the key and the runtime roots it is derived from could drift apart. Both now delegate to a single Developer::Dashboard::PathRegistry::alias_cache_key, on the registry that owns those roots. Of the five sub names the two modules share across 45 lines, none was byte-identical, and the AUTOLOAD and configured-alias-loading pairs were deliberately left alone because they encode a real difference - Folder creates the directory it resolves and File must not create the file it resolves. Behaviour of the installed product is unchanged. - DD-669: nine subroutines shared a name between RuntimeManager and CollectorRunner while having different bodies, so the name asserted a sameness that did not hold and no copy-paste detector could see it. Four are now single implementations in ProcessSupervision - _helper_file_supports_internal_command, _same_pid_namespace (RuntimeManager's _current_pid_namespace_id became dead and was deleted), _close_inherited_fds (a union, since RuntimeManager's preserve_harness guard is a strict superset) and _dashboard_core_helper_path (its command argument is now required, the two copies having differed only in a default no caller in lib/ reached) - for a net -33 lines in lib/: 155 duplicated lines removed, 114 shared added. The remaining five keep their per-class names with the reason recorded beside each, including two that are interface contracts the shared helpers dispatch through and one that diverges because RuntimeManager's side is defective (tracked separately as DD-753). t/160 now derives the divergent set from source and compares it against the declared set, so both a new divergence and a resolved-but-still-declared one fail. - DD-656: tests that chmod a file unreadable and then assert the operation is denied cannot observe a denial when the running process is root, so a container run of the suite reported seven of them as product failures when the mismatch was in the environment, not the code. A new gate, t/168-permission-assertion-guards.t, scans every test file for that shape and was RED at 62 sites across 26 files; each site now probes whether a denial is actually observable - by attempting the operation after the chmod - and skips with a named reason when it is not, replacing earlier guards that tested process identity instead. A follow-up run on ubuntu:24.04, debian:stable-slim and the project image measured that -r still answers true for uid 0 where the open is denied, and that the `filetest` pragma named as the alternative is absent from stock perl on Ubuntu and Debian, so attempting the operation is the form kept. Test-suite work only; no change to shipped code. - DD-725: t/97-pathregistry-coverage.t deliberately writes to /dev/full to exercise a failed flush, but unlike t/103 and t/89 it carried no tolerance for the resulting "unable to close filehandle ... No space left on device" warning, so every full-suite run on a host with /dev/full leaked it to stderr. That block now carries a scoped local $SIG{__WARN__} matching exactly that text - both the close-filehandle phrase and the device clause - and rethrowing anything else. Measured before and after on the same tree: 164 tests and 0 failures both times, stderr lines 1 to 0, with the ENOSPC assertion present and not skipped in both runs. No library code changed: the warning is perl's own notice about an implicit close during die-unwind, not a defect in PathRegistry. - DD-732: t/35-js-fast-check.t asserted npm ci's exit code inside the `if ( !-d node_modules )` branch that performed the install, so the file planned 1..4 on the first run in a tree and 1..3 on every run afterwards - the same file at the same commit reporting a different count depending on a directory it had created itself. That made the whole suite's total a property of the working tree rather than of the commit, presenting as flakiness (17540 once and 17539 four times on one unchanged tree) and hiding any genuine loss of a single test in the noise. The install stays conditional, but the assertion now runs every time and checks the postcondition - that the dependency tree is present - which also catches an install that exits 0 and produces nothing. Verified from both states, with node_modules present and with it removed. - DD-748: in a container without Devel::Cover, t/148-coverage-gate-entrypoint.t failed 29 of 50 assertions and t/151-coverage-gate-launch-boundary.t died after 3 of a planned 9, so a missing optional tool was reported as a test failure rather than an absent prerequisite. Both files now detect Devel::Cover's absence before any plan is declared and skip_all with a reason naming the module, following t/138-coverage-exec-truncation.t which already did this. A new t/170-optional-tooling-skips.t masks the module with an @INC hook in a child process and asserts all three specs skip, name the module, exit 0 and abandon no plan - while confirming t/148 and t/151 still run all 59 assertions when Devel::Cover is present, so a guard that always skipped would be caught. - DD-766: the behavioural tarball-exclusion block in t/15-release-metadata.t compiled all 17 exclude_match patterns out of dist.ini but applied them to a hardcoded list of 8 sample paths, so 9 patterns were compiled, exercised by nothing, and passed silently. Nothing leaked, but a deleted dist.ini line would not have been noticed. Sample paths were added for those nine, and a loop now asserts that every compiled exclusion is matched by at least one sample, so an exclude_match added to dist.ini without a sample fails instead of quietly reducing what the block covers. Added alone, the loop failed for exactly nine patterns; with the samples it reports 17 of 17 exercised, and the pre-existing must_be_shipped assertions are unchanged at 7. Falsified by adding a throwaway exclusion, confirming it went red, and removing it again. - DD-741: CodeQL refuses to run unless its init, autobuild and analyze steps use the same version, but Dependabot opens one pull request per action path - so each bump moved a single step, stranded the other two, and every pull request failed the required "analyze (javascript-typescript)" check with "Loaded a configuration file for version X, but running version Y". It happened three times (4.37.6 to 4.37.7, 4.37.7 to 4.37.8 and 4.37.8 to 4.37.9, as PRs 48/49/50) before being fixed at the cause. A Dependabot group scoped to "github/codeql-action*" now makes the family arrive as a single pull request that can pass; the pattern is deliberately family-scoped rather than a blanket wildcard, which would batch every action update so one failure blocked the rest. t/171-dependabot-grouping.t guards the block and was falsified in three directions - removing the block, narrowing it to one path, and widening it to "*" - before being trusted. - DD-655: a recorded gate verdict stored only the HEAD tree hash, so gate-status could tell that a verdict described an older commit but not that it described different uncommitted work - measured on DD-652, where the sandbox's HEAD tree had not moved all night while all six files the card changed hashed to something that tree does not describe. coverage-run now records a working-state fingerprint beside the tree hash - git status --porcelain --untracked-files=no plus git diff HEAD for uncommitted tracked work, and every file under the operator tools directory by relative path and content for the git-ignored tooling - and gate-status compares it with the same expression, so "a different commit" and "the same commit with different uncommitted work" are told apart, while a fingerprint that cannot be computed reports unknown rather than clean. The rejected alternatives are recorded with their reasons: git stash create captures tracked modifications only, and hashing the whole working tree invalidates on every unrelated edit. A follow-up closed a blind spot in the walk itself - a filename containing a newline split into two unreadable non-paths, letting its content change without moving the digest. - DD-696: host exclusivity between gate runs was enforced on only one side: coverage-run serialises by delegating to script/coverage-gate's flock, while run-suite exec'd prove directly and took no lock at all - measured by starting two full suites twelve seconds apart, both of which ran and the second printed no refusal. run-suite now takes an exclusive lock immediately before launching prove, refuses naming the holder rather than waiting, and scopes the lock to the prove run only; script/coverage-gate was deliberately left unchanged here, with that half tracked separately as DD-734. - DD-734: the suite runner has held a host-wide lock since DD-696, while script/coverage-gate locked only a file named after its coverage database (DD-526) - two different files, so neither tool could ever refuse the other, and a suite starting mid-coverage silently invalidated the coverage verdict. The gate now also takes the same host lock the suite runner uses (DD_SUITE_LOCK, default /tmp/dd-gate-host.lock), appended to the database lock rather than replacing it, so two gates given different --database paths still do not refuse each other. Because the suite itself invokes the gate in nine test files, the host lock is skipped when HARNESS_ACTIVE is set, so the gate cannot be refused by the suite that is running it; a follow-up fixed the lock test, whose real-run cases had inherited HARNESS_ACTIVE from prove and were exercising the guard instead of the lock. t/146-coverage-gate-lock.t grew from 9 tests to 15. - DD-744: script/coverage-gate derived its repository from its own file location and chdir'd there, so every relative path after that - the cover_db database and the t/ test directory - resolved in the script's own tree; invoking one checkout's copy from a different working directory ran that first tree's suite and deleted and rebuilt its coverage database, while the run succeeded and named neither tree. The gate now refuses when both the working directory and its own repository are git checkouts and they differ, naming both absolute paths, and it reports the resolved absolute database instead of the configured relative name that was byte-identical whichever tree it landed in. The refusal condition is deliberately narrow so the existing specs' temporary non-checkout repositories still run, and the two roots are compared case-insensitively where the filesystem is case-tolerant so the guard cannot falsely refuse in its own checkout on Windows or a case-tolerant macOS volume. New spec t/169-gate-target-resolution.t covers the refusal, the proceed case, and the absolute database reporting. - DD-729: host-readiness waiting was rewritten from scratch in each session that needed it, and the copies disagreed about what "foreign" means: the in-run sampler counted only a Devel::Cover process while the ad-hoc pre-run waits also matched a foreign workspace path, so a run could be launched as clear by one definition and then classified against the other. There is now a single readiness waiter with a spec, and both mechanisms obtain one definition rather than each carrying a copy. Two recorded claims were corrected by measurement: the path-based definition returns 19 processes on this host against 2 for the harness-based one - 18 of them a policy bridge, a `tail -F` and another project's watcher - so a wait using it would never launch, and every contention figure recorded before the definition change now carries a caveat that it cannot be pooled with anything measured after it. - DD-746: the suite runner's in-run sampler counted only Devel::Cover processes, so a competing plain prove was never counted - measured side by side, a /proc scan saw one foreign prove at the same moment the runner wrote FOREIGN_PEAK=0, a marker that reads as "the host was empty". Widening the pattern under DD-729 reversed the defect rather than fixing it: the verdict still called what it saw "a foreign coverage run" that "held the host", while the grep now matched any foreign test or coverage process and being seen in a sample is not evidence of holding anything. The CONTENDED and SLOWED lines now read "a foreign test or coverage process was seen in N of M sampled windows (peak P)", and the marker line carries FOREIGN_PATTERN so every recorded figure states the definition that produced it - without which the eleven figures gathered under the old narrow pattern cannot be pooled with new ones. - DD-727: the gate-status reporter decided whether a suite verdict stood from SUITE_EXIT and GATE_EXIT alone, and read none of the contention markers run-suite writes into the same log - a grep for CONTENDED, SLOWED, FOREIGN_PEAK, FOREIGN_SAMPLES and WINDOW_SAMPLES across the whole tool returned zero matches - so a log whose own line read "this verdict competed for the host and does not stand" was reported as a finished gate, in the reassuring direction. Corroborated four times on the card (DD-682 at 49 of 55 contended windows, DD-733 at 50 of 56, and both of DD-744's runs at 34 of 44 and 35 of 45), all of which passed and all of which were discarded by hand. The reporter now carries run-suite's own classification rather than re-deriving the threshold in a second place: a CONTENDED line overrides SUITE_EXIT and the verdict is reported as not standing, a SLOWED line is recorded without invalidating, and the three states - passed, did not stand, could not look - stay distinct, so broken code remains distinguishable from inadmissible evidence. - DD-750: the coverage tool's host-contention sampler excluded only its own process group, while the gate it launches runs under setsid in a different one - and a coverage run's children are Devel::Cover processes by construction, so the count was inflated in every window of every real run and no coverage verdict on this host could come out uncontended. Measured with a control pair at identical ambient load, a stand-in gate spawning no Devel::Cover-shaped child gave FOREIGN_PEAK=6 and one spawning a single child gave 7; after the fix both read 2. Rather than patch it in place, the private copy of the predicate was replaced by a call to the shared definition, which also supplies four protections that copy had never received: the process-group exclusion list, the quoted-pattern grep, the pattern-compile check and the empty-process-table check. - DD-761: the Docker image the platform gate runs against was built FROM itself and tagged back to the same name, so rebuilds layered on the previous build instead of starting clean, and a checked-in tarball that nothing regenerated left the image three weeks behind master. String::Compare::ConstantTime - declared as a runtime dependency in cpanfile, dist.ini and Makefile.PL, and loaded by Auth.pm at compile time - was absent from the image, so every path through Auth.pm died there and `dashboard encode`/`decode` exited 2 for every input; the failure first surfaced as a plausible-looking empty-string property-test counterexample, and only a control run (encode("hello") failing identically) showed the module was never loading at all. The image build now pins a real base, regenerates its tarball from master, and fails at build time when any dependency cpanfile declares cannot load. No shipped code changed: the dependency was always declared correctly, and the fix lives in the operator's local image-build directory, which the distribution excludes. - DD-682: the scheduled repository hunters fingerprinted a finding's whole item set, so any change of membership refiled every member: 28 filed cards carried only 11 distinct findings, 17 of them redundant refs, with "modules over 1500 lines" filed seven times in three days. Card titles also carried the item count, so one finding appeared as "(6)" and "(7)" and no title match could tell they were the same. Findings are now fingerprinted per item and only unreported items are filed, titles carry no count, and a new --report-all mode empties the ledger for the run so a card's recorded reproduce command prints the finding instead of "0 card(s) raised". Operator-local board automation only; no shipped code was touched. - DD-733: the repository's internal "what next" helper encoded a picture of the board that had drifted in four measured ways: it offered a card the owner had explicitly deferred through two answered-and-marked questions, never proposed work from the buglist, new-enhancements or backlog columns, was blind to the researching, analysing and documentation columns so its own "never start something new while something is claimed" rule could not fire, and asserted every backlog card was parked on a condition when 24 of 27 carried none. It now consults per-card policy declines when judging whether a candidate is workable, covers those columns in both its in-flight and candidate loops, and drops the false parked claim; its accompanying spec grew from 15 assertions to 35. - DD-742: the operator next-action guard listed four columns as in-flight (in-progress, researching, analysing, documentation) while the board declares eleven working columns, so a card at a later gate did not suppress a new recommendation: observed 2026-09-02 with a card in unit-test, the guard answered "DD-659 is waiting to be started. Claim it and work it". It now asks the board, where required_actions > 0 selects exactly the eleven working columns, so a twelfth column needs no code change, and an empty result fails into the existing could-not-look path rather than reporting that nothing is in flight. - DD-747: the next-action helper walked the pick-next column order but applied no ordering within a column: its candidate listing returns refs in ascending order, so the loop always took the oldest workable card. Measured on 2026-09-02, it named DD-637 (priority 2) as the next thing to do while DD-744 (priority 4) sat unworked in the same column. The tool now fetches ref and priority once per run and sorts each column's candidates by descending priority before picking. - DD-756: the hourly policy sweep compares the board's live policy set against the project's policy manifest, and the two records had drifted across the board upgrades: 61 policies were declared on the board while the manifest held 32 declared and 2 declined, with ten rules (card-changed-by-owner, agent-still, card-agentless, task-unlinked, task-changed, job-due, task-card-mismatch, monitor-dead, monitor-output, monitor-silent) present on the board and absent from the manifest, so the sweep exited 1 every hour asking for judgements that had already been made. Each of the ten is now judged and recorded in the manifest with its reason, and the sweep returns to exit 0. The manifest and the sweep are operator-local tooling; nothing in the installed distribution changes. - DD-679: nothing walked the working copies on the development host, so work sitting on disk that no card knew about went unnoticed: three checkouts were found holding content not on master, two of them belonging to cards already in terminal columns. A new developer-documentation page records the measured layout - 169 registered worktrees split 32/100/36 across three roots, one of them inside the main checkout - and the two identity tests a checkout must satisfy before anything reports on it, since a clone of this repository fails a git-common-dir check while a correctly-registered worktree of an unrelated project can sit in our own superseded sandbox root. It also records why "this directory differs from master" is the wrong question: scored by hand against three cases it gave one true finding and two false alarms, because a stale tree routinely holds a superseded draft, so the sweep asks whether the commit carrying the card's ref landed instead. - DD-659: the development host-exclusivity rule was written down but not scoped, and a hand-written per-session wrapper had held a host lock across an entire gate chain - 2529 seconds, with its prove child long since exited, while another session's twelve-minute suite waited over forty minutes. A new development-process page records the convention that such a lock is acquired immediately before the work and released when that process exits, notes that run-suite and coverage-gate already scope this way (per run and per coverage database respectively), and states the two limits run-scope does not remove: a waiter cannot tell a deliberate hold from a hang, and it does not prevent starvation between waiters using different strategies. No code changed - the offending wrapper was per-session and no longer exists. - DD-757: two places in the repository explained that docs/ never reaches the release tarball because dist.ini's [GatherDir] gathers under doc/ (singular) and never looks at docs/ (plural). That reason is false: [GatherDir] declares no root, so it gathers the whole distribution root, and docs/ is kept out by one explicit line, dist.ini's `exclude_match = ^docs/`, which neither artifact mentioned. The conclusion itself was never wrong and had been checked by building the distribution before and after and finding zero docs/ entries both times; only the attribution was wrong, and a reader trusting it could delete that exclude line as redundant, after which docs/ would ship. Both now name the line and state plainly that removing it ships docs/, cross-referencing the earlier case (DD-401) where a git-ignored file still reached a local 4.22 tarball because dzil gathers from disk rather than git. - DD-776: since Tira 5.77 the board tool raises its own card whenever it is upgraded, but no written procedure existed for working one, so each review invented its own checklist. An internal documentation page now records what that ticket actually asks - that nothing is left undeclared, and that no already-declared policy would be refused by a validation the upgrade added - and what it does not ask, since there is no per-version manifest to diff against. The 5.56 to 5.77 review it was written from found `tira.policy.undeclared` empty and none of this project's 61 declared policies setting --age on the eighteen rules 5.61 gave forbids => ['age'], so nothing needed correcting. 4.29 2026-08-18 - DD-585/589/590/591/592/593: six query functions across CollectorRunner.pm, IndicatorStore.pm, RuntimeManager.pm, ActionRunner.pm, SkillManager.pm (three functions) and PageRuntime.pm each shelled out (via system(), Capture::Tiny, or a waitpid reap) and left Perl's global $? holding their own last subprocess's exit status after returning, without ever localizing $? at sub entry. A caller reading bare $? afterward for an unrelated reason - the shape that first surfaced this in DD-585, an END block in a test file - silently inherits the wrong value. Found by a systematic sweep of lib/ once the first instance was fixed, and independently by the autonomous hourly bug-hunt round, which found and fixed DD-590 and DD-593 on its own (both resumed here after the round's own session ended mid-verification, with nothing lost either time - its park mechanism correctly left the tree untouched while a suite was live). Fixed uniformly with `local $?;` at each sub's entry. One subtlety verified rather than assumed: SkillManager's three functions never read $? explicitly, but Capture::Tiny returns system()'s own return value automatically while system() ALSO mutates the global $? as an unavoidable side effect - confirmed with a standalone repro before writing any fix. A fourth possible instance (ActionRunner's own sibling query pattern) was checked and is genuinely absent. - DD-594: dd-round-park.sh's automatic end-of-round park comment defaults to the shared, retired 'DD Bot' identity (DD-527 retired it project-wide) because nothing in the automation chain ever sets DD_TIRA_PARK_AUTHOR. Found by the autonomous round tracing the root cause of a finding that had been recurring on every card it touched. - DD-586: t-dd-round-park.sh's specs-guard timeout (600s) was too tight for a spec that deliberately spawns real long-lived background processes to model a genuinely-alive concurrent round; raised to 1200s. - DD-587: the hourly bug-hunt and 10-minute blocked-resolver systemd timers had been silently disabled for 9 days. Re-enabled and verified both correctly defer to an active interactive session before doing real work. - DD-562: master's coverage gate read 100.0 locally but 99.9 on CI. - DD-567: a perl-interpreter CVE advisory blocked the CI step immediately before tests and coverage, on 5 consecutive pushes. - DD-568: an early CI step failing silently skipped the test and coverage steps that followed it; those steps now run with if: always(). - DD-572: the pre-push security grep matched 'brewfile' against the retired-system marker pattern 'ewf', making a required gate return permanent noise. - DD-573: 13 operator-tool specs existed under dd-tg with nothing ever running them, so a broken spec could never fail loudly. - DD-574: the audit gate could fail without ever saying which check failed it, a defect DD-567's own fix left behind. - DD-581: nothing recorded a coverage verdict anywhere the board's own gate indicator could read, so it could never show green even on a clean pass. - DD-582: gate-status reported RUNNING for a log file it had no way to know was actually still being written. - DD-583: the Telegram ack watcher answered a direct question about its own silence with more silence. - DD-584: two dd-tg operator specs had been failing with nobody told. 4.28 2026-08-15 - The coverage gate's own regression test no longer asserts that this machine's library tree exists everywhere. It runs the gate in an environment stripped of PERL5LIB and expected it to resolve its dependencies anyway, which is true only where those dependencies sit in the one directory the gate repairs. Where they do not, the gate correctly reports a missing module and the test called that a failure. It now skips those two cases with the reason printed, keeps every structural assertion, and still goes red if the repair is removed. - 4.27 was tagged but never published: this test failed in the release workflow, and a job that fails early makes every later step skip, so the tag exists with no release behind it. Nothing in 4.27 is lost - it is all here. 4.27 2026-08-15 - Browser smoke tests no longer hang a machine whose browser cannot reach its own loopback address. A coverage run sat on one such fetch for one day and twenty hours, holding a web server, a starman master and worker and a whole Chrome tree, while producing no failure, no exit status and no last line - a stalled run looks exactly like a slow one. Every external command those tests run is now bounded, and the bound kills the command's whole process group rather than the single process it can see, because the wedge left seven processes alive and killing the visible one would have reaped one of them. - Those tests now ask, before doing anything else, whether the browser can fetch a page that is definitely being served. If it cannot, they skip and print why - the probe URL, what the browser did, and that the product is not implicated. The probe serves that page over plain HTTP from a nine-line socket server rather than from the product, which is what makes it a diagnosis instead of a second copy of the test: if plain HTTP works and the real fetch then fails, that is a genuine failure and is not skipped. - The workspace command hands the terminal to tmux with exec instead of capturing the attach. It used to funnel a full-screen interactive program through a capture buffer nobody reads until it exits, and leave a perl process parked as its parent for the life of the session - possibly days. - Collectors: a lost pidfile no longer multiplies supervisors. Start, stop and status all treated that file as the authority on what was running, so once it was gone every start forked another loop that nothing could see, stop or count; twenty-seven were alive at once for a collector declared singleton, each firing every fifteen minutes. They now ask the process table, adopt the loop already running, and repair the record. - A collector stopped by a signal now records which signal stopped it and which process received it. A 'stopped' state with no pidfile is otherwise identical whether the loop shut down in an orderly way, was stopped by a watchdog, or was killed by something else on the machine - and that ambiguity sent four separate investigations down the wrong path. - The coverage gate carries its own library path. Launched from cron or a transient systemd unit - environments that read no shell profile - it died naming a dependency that is declared in three manifests and installed. It resolves that tree itself now, and still fails loudly when a dependency is genuinely absent, because it adds a directory only when the directory exists and runs no external command. - The collector start path no longer re-checks a pid it has already guaranteed. When the recorded pid is missing or empty it is replaced by a search of the process table, which returns a real pid or nothing, so the later test for a usable pid could never fail. The guarantee is now stated once where it holds instead of being re-checked where it cannot, which is one fewer piece of code that looks like it protects something. 4.26 2026-08-10 - Packaging: stopped the release tarball carrying two things it never should have. logs/ shipped the board sweep's own runtime output - findings about this project's cards - to anyone who installed the product, because dzil gathers from disk and cron recreates that directory hourly. SECURITY_CHECKS.md shipped the internal security-audit protocol, which belongs with the operator files that are deliberately never released. Both are excluded now and t/15 fails if either exclusion is removed. - Board tooling: the FT99 sweep decides from the cards themselves instead of a retired markdown page, so a card parked on the owner with nothing left to answer is reported rather than silently accepted, and a card holding an unanswered question anywhere else is reported too. It also reports a card claimed in a working column that has stopped moving, and one left untouched in the backlog. - Coverage gate: refuses to grade a database written by a different Devel::Cover install, and says which two disagree, instead of exiting with an unexplained status. 4.25 2026-08-09 - CI: restored a pipeline that had certified nothing for ten days. Three GitHub Action pins sat on the node20 line while their version comments claimed node24, so every run died at Setup Perl and the suite, the coverage gate and both CVE audits never executed (DD-449). The pin comments are now resolved against the upstream tag rather than trusted (DD-449), the environment export no longer swallows its own failure inside a command substitution (DD-483), and two suite files no longer assume tools and libraries CI installs elsewhere (DD-485). - Security: helper session ids are drawn from the operating system CSPRNG instead of a SHA over the pid, the wall-clock second and built-in rand - the construction CVE-2026-13577 describes in Dancer2, reimplemented here in the code guarding the web interface. Crypt::URandom is now a declared dependency floored at 0.55, because CVE-2026-2474 affects earlier releases (DD-452). - Security: dispositioned CVE-2026-13577 for Dancer2, which has no fixed release, behind a guard that fails the audit if any production code ever activates a Dancer2 session (DD-451). - Security: closed a Zip Slip in dashboard open-file, where a sources jar could write attacker-controlled paths anywhere the user can write (DD-498). - Security: corrected the CVE gate's subject so it judges the product's declared chain rather than the host interpreter (DD-499). - Fix: a collector's loop state is written before its pidfile, so a concurrent running_loops can no longer delete the pidfile of a healthy loop and leave it unstoppable by name while the supervisor forks a duplicate (DD-488). - Fix: Housekeeper's temp-file cleanup lists its directory instead of globbing a pattern built from the path, so a directory whose path contains a space stops hiding every real temp file and unlinking unrelated ones (DD-502). - Test reliability: the coverage gate, the empty-environ probe and several collector lifecycle fixtures no longer fail purely because the host is CPU-starved, which is the shape of every CI runner (DD-482, DD-486, DD-489). Every timeout changed here is backed by a measurement rather than by the fact that a larger number passes. 4.24 2026-08-08 - Security: closed the open-redirect class in the login redirect sanitizer and on both HTTPS-enforcement Host-header redirect paths (DD-415, DD-419), rather than only the single reported byte. - Security: closed three path-traversal classes - saved-page writes outside the dashboards root (DD-395), parent-directory traversal in every skill-namespaced web route with static-file containment asserted (DD-416), and skill installs whose repo name escapes the skills root (DD-426). - Security: every page-derived value the web layer places inside an HTML attribute is now escaped for attribute context (DD-421), and saved bookmark link segments are percent-encoded for URL context (DD-423). - Security: state-changing requests are rejected when Origin or Referer is foreign, on every web tier (DD-422). - Security: the transient-token denial now depends on the token alone, so a file value riding along can no longer bypass the 403 (DD-425). - Security: release tarballs no longer carry dogfood QA evidence, ticket worktrees (DD-414), or the Hermes runtime state root and its credentials (DD-432). - Feature: first-class self-upgrade - dashboard upgrade and d2 upgrade run the canonical platform installer (DD-386). - Feature: a real /favicon.ico is served instead of a 404 on every page load (DD-407). - Collectors: a timed-out command's full process subtree is terminated (DD-388); blocked Windows command collectors are interrupted at their timeout (DD-389); the atomic writer uses a per-process pending name so concurrent workers stop clobbering each other (DD-418); log append and rotation share a per-log lock (DD-399) and rotation cuts on entry boundaries so a combined lines+days rule cannot orphan an entry body (DD-427); collectors keep running when the updates directory is absent (DD-398). - Runtime: saved-Ajax disconnect cleanup terminates descendant processes (DD-396) and gives them a real elapsed SIGTERM grace window (DD-428); runtime state readers survive a transient empty state file (DD-411) and the web lifecycle no longer acts on incomplete runtime state (DD-412). - Windows: the SSL certificate home resolves through PathRegistry so the HTTPS server starts (DD-417); every passwd lookup is guarded so non-interactive sessions resolve a user and a home (DD-413); PERL5LIB generation is cross-platform (DD-397); PowerShell startup caches are anchored to the home layer and no longer clobber live PATH state (DD-390). - Web UI: every generated HTML document carries a lang attribute (DD-405); logout and an authorized /login route by trust tier (DD-408); page-editor textareas carry programmatic accessible names (DD-409); rendered page chrome no longer overflows 320px phone viewports (DD-410). - Performance: PathRegistry working-directory lookups no longer fork /bin/pwd repeatedly (DD-393). - Gates: the POD syntax gate reads the source tree instead of a stale copy and the three reds it was hiding are fixed (DD-429); the coverage gate grades lib/ instead of the stale build tree (DD-431) and a failed exec in the harness process can no longer blind it (DD-428); the Windows collector-timeout E2E is proven on a real QEMU Windows guest and made repeatable (DD-404). - Metadata: MakeMaker license metadata aligned with MIT (DD-400). 4.23 2026-07-29 - Coverage: every module under lib/ now reaches 100.0 on all four Devel::Cover metrics - statement, subroutine, branch, AND condition - driven by the new per-module hermetic coverage suites (t/59 through t/106) with genuinely-unreachable outcomes annotated inline and dead guard operands removed where redundancy caused unreachable branches. - CI: all three workflows (test, release-github, release-cpan) enforce the all-metric standard through script/check-all-metric-coverage, which fails closed on any metric below 100.0 or any malformed report (t/107-all-metric-coverage-gate.t). - Security: helper host-trust no longer accepts arbitrary Host headers that merely resolve to loopback, closing a DNS-rebinding vector; exact loopback literals and configured alias hosts remain trusted. - Test isolation: the fresh-runtime API assertions in t/15-cli-module-coverage.t now chdir into their isolated HOME so a dev-runtime layer in the working checkout cannot capture their writes. - Installability: the dependency hardening initially raised the Perl floor to 5.44, which cpanm rejects on every mainstream perl (perl:latest is 5.42); the floor is restored to 5.38 (the code's real requirement) while CI continues to run on Perl 5.44 and the dependency version pins keep the security gate. 4.22 2026-07-15 - the housekeeper now sweeps expired helper-session files each run (wiring the SessionStore expired-session sweep added in 4.21 into the periodic cleanup), so stale session records no longer accumulate unbounded between logins 4.21 2026-07-15 - batch of fixes from the automated bug-hunt (round 1), each test-covered: - config: the merged config now always dedupes the `collectors` array by name and `providers` by id, even when only one layer defines them (a single layer repeating a collector name previously leaked duplicates); config.json / api.json are now written atomically (temp file + checked close + rename) instead of truncated in place - security: the encoded-action transient path no longer trusts a payload-supplied `source`/trusted flag, closing a forged-token arbitrary command execution; the session id from the cookie is charset-sanitized before path use, closing a session-file path traversal; the session cookie gains `Secure` over HTTPS; helper passwords are now stretched with PBKDF2-HMAC-SHA256 while still verifying existing hashes - collectors: `timeout_ms` now round-trips correctly (no more x1000 blow-up on Windows worker restart); a `* * * * *` cron collector no longer bypasses per-minute dedup; signal-killed collector/action commands are recorded as failures instead of exit 0; background actions guard setsid on Windows - state writes: Collector and IndicatorStore atomic-write helpers now check close() and no longer unlink before rename; a git error is no longer reported as a dirty tree - added SessionStore::sweep_expired for expired-session reclaim (wiring it into the housekeeper, plus the DNS-rebinding host-trust tightening and the timed-out-command subtree kill, are deferred as follow-ups) 4.20 2026-07-15 - security: in `dashboard serve --ssl` the public front-proxy relays raw TLS to the internal loopback backend, so every backend connection arrived from 127.0.0.1; combined with a `Host: 127.0.0.1` header a remote HTTPS client was classified as trusted loopback admin and got unauthenticated admin. The internal SSL backend now marks itself proxied and never grants the loopback-admin shortcut, so remote HTTPS clients must authenticate. 4.19 2026-07-15 - ship `d2` as a real, installed command instead of only a shell-bootstrap alias: `bin/d2` is a first-class entrypoint that re-execs its sibling `dashboard` with the same arguments, so `d2 ` behaves exactly like `dashboard ` and now works in scripts, cron jobs, and fresh shells that never sourced the dashboard shell integration - the shell bootstrap keeps completion wiring for `d2`, and the generated shell shortcut remains as a convenience where the bootstrap is loaded 4.18 2026-07-15 - add `dashboard ask `: a uniform AI-assistant command that answers from the Anthropic API when an `ANTHROPIC_API_KEY` (or the `claude` config domain) is set, and otherwise falls back to the local `claude` CLI - support pluggable backends `--claude` (default), `--codex`, `--copilot`, and `--gemini`; the chosen backend becomes sticky per workspace, and each shells out non-interactively in a forced read-only mode - remember the conversation per workspace (keyed by WORKSPACE_REF or the active project root) so follow-up questions carry prior turns; `--new` starts fresh and `--no-memory` skips the transcript for one turn - inline `--file` text attachments into the prompt and pass image attachments natively (Anthropic image blocks, codex `-i`, copilot `--attachment`); the transcript is stored owner-only under the runtime state root 4.17 2026-07-15 - make `dashboard collector stop ` actually stop the collector: it now routes through the supervisor-aware stop path so the collector watchdog is deregistered and cannot immediately respawn the loop (previously it called the low-level loop stop directly, so the supervisor restarted the collector and its status snapped back to running) - stop the collector supervisor from creating a DUPLICATE loop when it races a freshly started loop before that loop has set its process title, by recognizing an already-running loop from its recorded state as well as by process identity - reset the consumer-facing collector `status.json` (running / active_runs) when a loop is stopped, so prompt, web, and CLI status readers reflect the stop immediately - reliably terminate long-running collector command subtrees on stop: send the worker process-group SIGKILL unconditionally so a SIGTERM-ignoring child is still reaped, kill the loop first and scale the TERM grace to the worker count so the loop is not KILLed mid-cleanup, persist active worker pids on spawn, and sweep orphaned workers left behind by a crashed loop - recognize managed collector loops from recorded state on hosts without `/proc` or `ps` (Windows), so stop can still terminate them 4.16 2026-06-11 - teach the built-in `dashboard workspace` command a `-c` flag, accepted before or after the workspace name, that changes into the registered dashboard path for that name before planning the session, so `dashboard workspace -c foobar` behaves like `cdr foobar` followed by `dashboard workspace foobar` and the tmux session plus its layered `.env` refresh start from the registered project directory - fail `-c` explicitly when the workspace name is not a registered dashboard path, when the registered target is not a directory, or when the directory cannot be entered, instead of silently starting the workspace from the wrong directory 4.15 2026-06-11 - keep the `install.sh` post-install activation runner on the same shell dialect as the bootstrap target, so blank hosts without `SHELL` exported (fresh Docker containers, `curl ... | sh` runs) no longer source the bash-flavoured activation files through plain `sh` and die on the first bash-only construct 4.14 2026-06-11 - align every module under `lib/` back onto the single repository release version after the `4.13` bump only advanced `Developer::Dashboard`, leaving the other 53 packaged modules still declaring `4.12` - add a release-metadata gate that walks every `lib/**/*.pm` and asserts its `$VERSION` matches the `dist.ini` release version, so a partial version bump can never ship again - validate IPv4 loopback octets strictly in `Auth::_ip_is_loopback`, so malformed literals such as `127.0.0.999` are no longer classified as loopback addresses - restore the `4.12` entries in `Changes` and `FIXED_BUGS.md` that the `4.13` update overwrote instead of prepending 4.13 2026-06-11 - harden the GitHub-hosted `Devel::Cover` gate again by exercising the installed-dist `File::ShareDir` asset lookup branch in `Web::App::_bundled_public_asset_path`, so clean Ubuntu runners close the final `99.9` statement gap and keep the reviewed `lib/` totals at `100.0 / 100.0 / 100.0` 4.12 2026-06-10 - harden the GitHub-hosted `Devel::Cover` gate by exercising the low-traffic `Web::App` compatibility helpers directly in the focused web coverage tests, so route fan-out timing on CI runners cannot leave the reviewed `lib/` totals below `100.0 / 100.0 / 100.0` 4.11 2026-06-10 - decouple the GitHub-hosted CPAN upload workflow from ordinary `vX.XX` tag pushes so signed GitHub release tags can be published without triggering an unasked PAUSE upload - broaden the CodeQL push trigger to every branch so repository-side SAST coverage is visible on more commit paths instead of only `master` 4.10 2026-06-10 - move project-local isolated Docker Compose service discovery and disable markers under `./.developer-dashboard/config/docker/...` so the implementation matches the documented config-root contract across resolver lookup, toggle writes, and exported `DDDC` paths 4.09 2026-06-06 - keep smart-routed skill page browser edits on their canonical `/app/` and nested `/app//` aliases by teaching the edit/source routes and Play handoff to load skill index pages through the same smart resolver instead of collapsing to the underlying `BOOKMARK: index` id 4.08 2026-06-06 - split the Web UI bookmark editor into Jupyter-like section blocks while keeping the saved bookmark file format canonical, including block-aware syntax overlays, Tab-created sections, hidden source recomposition, and Play-mode save-and-render handoff - standardize operator-facing built-in CLI output so path, file, api, and skill inventory or mutation commands default to readable table summaries while `-o json` returns the full raw machine payload 4.04 2026-06-05 - restore the public switchboard helper path contract for dotted `dashboard .` dispatch, keep Windows core-backed built-ins routed through the shared staged `_dashboard-core` helper, and fix the staged private core import drift that broke `dashboard init` and later helper refreshes - harden Windows runtime helper and state-file flows so detached web, collector-loop, collector-worker, and watchdog launches use the staged foreground helper entrypoints and tolerate Windows rename collisions through explicit replace fallbacks - teach the Windows checkout bootstrap to seed `HOME` from PowerShell's own `HOME`, create a stable user-space `make.cmd` shim for Strawberry GNU make, and keep the fuzz gate lightweight under `Devel::Cover` - force non-interactive CPAN environment defaults for skill `cpanm` installs so Windows fresh-session bootstrap checks do not stall behind dependency configure prompts 4.03 2026-06-05 - ship an explicit OWASP compliance SOW and evidence matrix, wire it into the OWASP and release-metadata tests, and tighten the manuals so the safe public wording stays `OWASP-aligned` / `OWASP-gated` until the remaining governance blockers are truly closed 4.02 2026-06-04 - fix `_helper_file_supports_internal_command` substring detection so runtime helper refresh probes return true for matching staged helpers and stay stable under the plain and covered harnesses - harden `t/47-zombie-coverage-closure.t` with a persistent helper fixture and direct runtime-object assertions so the final gate loop stays deterministic 4.01 2026-06-02 - add the built-in `dashboard api` management command for layered `config/api.json` machine auth, including merged registry listing, per-key inspection, SHA-256 hashing of raw secrets before persistence, optional `--maybe-secret` route-oriented secret input, exact saved `/ajax/...` route add/remove controls, duplicate-route no-op handling, and child-layer tombstones that hide inherited API groups without rewriting parent config 4.00 2026-06-02 - add layered `config/api.json` machine auth for selected saved `/ajax/...` routes, including installed skill `config/api.json` fragments, exact route allowlists, and `X-DD-API-Key` plus `X-DD-API-Secret` header verification against stored SHA-256 digests - keep helper-session auth working on those same API-registered ajax routes while failing unauthenticated or wrong-secret machine callers closed with `403 {"status":"forbidden"}` - pass saved ajax API auth headers through the PSGI/Dancer adapter so browser-facing and direct HTTP entrypoints enforce the same machine-auth contract 3.99 2026-05-26 - keep the active shell directory in dashboard-managed child `PATH` repairs alongside the current Perl interpreter bin so collector shell commands still execute cleanly during blank-environment tarball installs and any runtime that inherits a stripped `PATH` 3.98 2026-05-20 - treat collector `disable` as a hard stop-and-skip flag so disabled collectors are not started, explicit named starts reject them, and any already-running managed loop is stopped during the next lifecycle action - remove managed collector indicators for disabled collectors instead of leaving stale active indicator state behind - normalize dashboard-owned `PERL5LIB` ordering so core/site/vendor Perl dirs stay ahead of inherited user-local shadow copies, preventing stale dual-life XS modules such as `Encode` from breaking helper startup, collector child commands, saved Ajax subprocesses, and skill hooks on hosts with older local-lib artefacts - keep the current Perl interpreter directory at the front of `PATH` for dashboard-managed child commands so collector loops, saved Ajax subprocesses, and skill hooks do not fall back to a mismatched system `perl` when they execute `dashboard` through `/usr/bin/env perl` - run collector shell commands through a non-login shell so macOS shell session restore output and other shell-init chatter do not get prefixed onto JSON collector output - slow tmux ticket/workspace status refresh from a 2-second hot loop to a 15-second cadence so dashboard-managed tmux sessions do not burn CPU just to repaint status fragments - apply a 30-second default safety floor to shell collectors that recurse back into `dashboard` or `d2`, with explicit opt-outs and an environment override for intentionally fast dashboard polling - pause the collector watchdog supervisor during explicit named collector stop and restart actions, then restore supervision for the remaining watched fleet afterwards so watchdog restarts do not race manual lifecycle commands - preserve the saved managed web pid during stop flows even when shutdown has to fall back to persisted-state listener pids, and still escalate the saved pid to KILL when process discovery lags after TERM - scope web stop ajax cleanup to the current dashboard runtime root instead of sweeping every `dashboard ajax:` process owned by the user - make collector loop shutdown kill active worker process groups so long-running singleton commands stop with the collector loop instead of surviving as orphaned subprocesses - reap exited collector workers and watchdog-adopted dashboard children immediately on `SIGCHLD` instead of waiting for the next scheduler or watchdog tick, so long-interval collectors and orphaned supervisors do not leave visible `` dashboard processes behind for minutes or days - persist active worker pids in collector loop state and lock in a live singleton regression where a long-running due run must be skipped instead of overlapped 3.92 2026-05-20 - keep `dashboard ps1` on a lighter switchboard path by lazy-loading the unknown-command suggestion runtime and helper-staging-only modules instead of paying for them on every prompt render - stage only the requested built-in helper on the hot path and reuse one `PathRegistry` object across the switchboard invocation, instead of rescanning and restaging the entire helper tree for every prompt - load installed skill config fragments directly from layered `config/config.json` files in `Config` so prompt-time config reads do not pull in the full skill dispatch runtime 3.91 2026-05-20 - make `dashboard ps1` skip `tmux show-environment` when `TMUX` is unset, stop constructing an unused collector object, and refresh only prompt-visible core indicators on the prompt path - read the prompt branch directly from `.git/HEAD` and worktree `gitdir:` metadata instead of spawning `git branch`, keeping branch rendering fast on slower hosts such as iSH - add a collector-sync fast path so repeated prompt renders avoid the full collector indicator rewrite path when the stored config-backed indicator state already matches 3.90 2026-05-20 - make `PathRegistry` actually reuse a precomputed invocation cwd and memoize repeated DD-OOP-LAYERS path derivation so thin helper commands stop recalculating the same runtime chain during one process - make `EnvLoader` plain-directory traversal reuse the registry cwd cache instead of re-running `cwd()` while it walks root -> project -> leaf `.env` layers - tighten the nested skill env/compose fix set with the final blank-host and coverage gate closure work 3.89 2026-05-19 - expand nested skill env loading from root to leaf for dotted skill commands and participating docker compose skill services, preserving overwritten parent values under cumulative aliases such as `foo_VERSION` and `foo_bar_VERSION` - discover nested installed-skill docker compose roots so participating leaf services such as `skills/foo/skills/bar/skills/zzz/config/docker` contribute both leaf `zzz_DDDC` and cumulative `foo_bar_zzz_DDDC` aliases during compose resolution - cover the remaining action-runner defensive and background-child startup branches so the final tree can close the 100% coverage gate again 3.88 2026-05-19 - load participating skill-root `.env` files into `dashboard docker compose` resolution and export normalized `_DDDC` variables for each participating skill docker root - run detached background page command actions through a reaped supervisor path so blank-container tarball installs and root-owned hosts do not leave stale background wrappers behind or fail the tarball gate 3.86 2026-05-19 - fixed packaged shell bootstrap generation so staged helpers always re-enter the active dashboard entrypoint instead of leaking a stale inherited source-tree path into extracted tarball installs and tests 3.85 2026-05-19 - export one skill-specific _DDDC compose env variable for each participating skill docker root - keep participating skill .env loading isolated to docker compose resolution without executing skill .env.pl files 3.84 2026-05-19 - load participating installed skill .env files into docker compose resolution without executing skill .env.pl files 3.83 2026-05-18 - fixed interactive `dashboard skills install` progress so dependency rows are only added when the matching manifest file actually exists in the fetched skill, instead of printing `[OK] ... skipped: ... not present` noise for absent files - filtered operating-system-specific dependency rows so `aptfile`, `apkfile`, `dnfile`, `wingetfile`, and `brewfile` only appear on their matching host families and stay hidden on unrelated platforms even when those files exist in the skill - kept cross-platform manifest rows such as `package.json`, `requirements.txt`, `cpanfile`, `cpanfile.local`, `Makefile`, `ddfile`, and `ddfile.local` visible only when those files are present, which leaves the progress board focused on real work instead of skipped probes 3.79 2026-05-16 - replaced the `tomq` backend dependency with `TOML::Parser`, inflating TOML booleans to plain Perl `1` and `0` scalars so query output stays stable while the blank-container `cpanm` install gate no longer trips over the failing upstream `TOML::Tiny 0.21` test suite on Perl 5.38 3.78 2026-05-16 - fixed the tag-driven GitHub release workflow so it installs `Devel::Cover` before running the numeric coverage gate; this avoids the exit-127 `cover` failure that previously blocked signed GitHub release publication and kept Scorecard from seeing any releases 3.77 2026-05-15 - aligned every shipped Perl module under `lib/` with the distribution version after the blank-host installer fix so the source tree, release metadata, and CI coverage gates all read one consistent `3.77` version instead of a mixed `3.75`/`3.76` module set 3.76 2026-05-15 - fixed blank Ubuntu `curl ... | sh` bootstrap installs so `install.sh` seeds `File::ShareDir::Install` into `~/perl5` before `cpanm --notest .` runs against the cloned checkout, avoiding the configure-time `Can't locate File/ShareDir/Install.pm` failure on fresh hosts - aligned `install.ps1` with the same configure-prerequisite bootstrap so the checkout install path does not drift between Unix-like and Windows operators 3.75 2026-05-15 - fixed Windows background web helper lookup so source-tree and CI test runs no longer die when `RuntimeManager` asks `File::ShareDir` for an installed dist share that does not exist yet; helper fallback now reuses the same guarded helper-asset resolver used elsewhere in the runtime - replaced the built-in `/js/jquery.js` shim with a bundled local copy of jQuery 4.0.0, while keeping `/js/jquery-4.0.0.min.js` as a compatibility alias for the same shipped asset - taught the browser bookmark editor to expand `:---` into the full separator line and insert the next sensible unique directive so common `TITLE -> HTML -> CODE` authoring flows are smoother - added release and route coverage for the bundled jQuery asset and the missing-dist-share helper fallback path 3.74 2026-05-15 - added direct `python` dispatch for executable `cli/*.py` commands and hook files so Python-backed skills now run through the same logical command and hook resolution path as Perl, Node, Go, Java, and shell helpers - added `requirements.txt` to the skill dependency chain after `package.json`, installing Python dependencies through `python -m pip install --user --requirement requirements.txt` - regenerated README.md from the canonical POD after the runtime and dependency-chain changes so release metadata stays in sync 3.73 2026-05-15 - added direct `node` dispatch for executable `cli/*.js` commands and hook files, so JavaScript-backed skill commands now run through Node in the same extension-resolution path already used for Perl, Go, Java, and shell helpers - fixed the platform runnable-file resolver so logical command names can discover matching `.js` files on Unix and Windows without breaking the existing `package.json` dependency-install chain used by `dashboard skills install` - fixed the prompt tmux-status unit coverage to clear `WORKSPACE_REF` explicitly so ordinary tmux sessions still test the non-dashboard path 3.72 2026-05-14 - fixed collector watchdog supervision so a managed loop that stays alive but stops making progress is treated as stalled, restarted automatically, and reported explicitly instead of sitting silent until an operator notices - renamed the primary tmux workflow from `dashboard ticket` to `dashboard workspace`, while keeping the older `dashboard ticket` spelling plus `TICKET_REF` session state as compatibility fallbacks - added layered plain-directory `.env` refresh for workspace sessions, so the highest ancestor `.env` acts as the base, parent `.env` files merge next, the current directory `.env` overrides last, and resumed tmux workspace sessions are refreshed in place with dropped keys unset 3.71 2026-05-14 - fixed live collector status writes so managed indicator ordering now keeps the collector array order from `config/config.json` even after one collector refreshes its own state, instead of dropping back to alphabetical order in `/system/status`, page-header indicators, and `dashboard ps1` - fixed runtime `config/routes.json` aliases so normal saved bookmarks such as `/app/learn.ai` can be reached through shorter custom paths like `/java`, and widened that runtime alias loading across `/app`, `/ajax`, `/js`, `/css`, and `/others` instead of limiting custom paths to installed skill route metadata only 3.70 2026-05-13 - moved skill route metadata to `config/routes.json` and changed the schema to the flat custom-path form, where each public path maps to one local smart route string or an object with `to` and optional `type` - expanded custom skill route support beyond Ajax so installed skills can publish fallback custom paths for `/app`, `/ajax`, `/js`, `/css`, and `/others` without breaking the smart longest-prefix parent routing - kept saved skill-page Ajax URL generation on the canonical custom path from `config/routes.json`, with Ajax custom routes defaulting to `json` unless an explicit route-level `type` is supplied - fixed browser `/system/status`, page-header status, and `dashboard ps1` indicator ordering so managed collector indicators now follow the collector array order from config instead of drifting alphabetically - fixed `dashboard serve logs -f` so a line appended immediately after the initial tail output is no longer skipped by a seek-to-end race before follow mode starts streaming new content 3.69 2026-05-13 - added skill-local `dashboards/routes.json` metadata so installed skill Ajax handlers can publish canonical custom paths, optional fallback aliases, and default response types such as `json`, `html`, or raw mime strings without breaking the smart `/ajax//...` resolver - fixed saved skill-page Ajax URL generation so `Ajax(file => ...)` emits the declared canonical custom route when route metadata exists, while the longest-prefix smart `/ajax/...` route remains the primary resolver and custom aliases are fallback-only before a normal `404` - fixed browser `/system/status`, page-header status, and `dashboard ps1` indicator ordering so managed collector indicators now follow the collector array order from config instead of drifting alphabetically 3.68 2026-05-13 - added collector overlap control with explicit `mode` and `multiple` config fields, defaulting collectors to singleton scheduling and allowing bounded parallel runs only when a collector opts into `mode = multiple` - fixed collector loop scheduling so long-running jobs no longer block the next interval tick for opt-in multiple-mode collectors, while singleton collectors still suppress overlap by design - made collector status updates lock-safe so active-run counts and the `running` flag stay correct when overlapping collector workers finish concurrently 3.67 2026-05-13 - fixed managed runtime child cleanup so detached web startup helpers, collector stop paths, the collector watchdog supervisor, SSL frontend connection workers, and background page actions no longer leave zombie processes behind on hosts such as macOS and WSL - fixed the SSL frontend accept loop so child-reaping `SIGCHLD` traffic no longer makes the public listener shut its backend down early, while TERM/INT/HUP now request a clean frontend shutdown explicitly instead of depending on interrupted `accept()` side effects - tightened regression coverage so collector stop and watchdog shutdown tests fail if the runtime leaves direct child processes unreaped, and action/background SSL runtime tests now enforce the no-zombie contract 3.66 2026-05-08 - added a collector watchdog supervisor that restarts unexpectedly-dead managed collector loops after startup instead of leaving them silently stopped until an operator notices - persisted watchdog restart counters, timestamps, error text, and `attention_required` collector status metadata so repeated crash loops are explicit in `dashboard collector status ` and collector logs - stopped the watchdog from thrashing forever by raising an explicit attention-required state after too many crashes inside the watchdog window, forcing a visible operator follow-up instead of silent churn 3.64 2026-05-08 - fixed macOS Terminal.app shell bootstrap to source /etc/bashrc_Apple_Terminal when update_terminal_cwd is missing, preventing "command not found" errors on fresh macOS bash sessions 3.63 2026-05-08 - added dockerfile manifest support to skill installation, allowing skills to declare Docker images that get built automatically during install - fixed Progress.pm to support unlimited streaming output for long-running operations like Docker builds, with optional max_detail_lines cap - added has_dockerfile metadata to skill list and usage output - fixed macOS Terminal.app shell bootstrap to source /etc/bashrc_Apple_Terminal when update_terminal_cwd is missing, preventing "command not found" errors on fresh macOS bash sessions 3.62 2026-05-07 - remove matching root ddfile skill source entries when uninstalling a skill 3.61 2026-05-06 - switched the project license from Perl_5 to MIT across the repository metadata, shipped LICENSE text, README, canonical POD, and Scorecard guardrails so the published license state is explicit and consistent - removed the old Artistic sidecar license file and Perl dual-license wording so the repo no longer advertises conflicting licensing terms 3.60 2026-05-06 - documented the open-source license disclaimer more explicitly in the canonical POD and synced README, making it clear that the software is provided "as is", no warranty is given, and normal free-software use is covered by the project's baseline liability disclaimer 3.59 2026-05-06 - fixed shared skill-nav discovery so nested installed skill trees such as `skills/ho/skills/coverage/dashboards/nav/index.tt` now render on the nested skill route itself and also join the shared nav strip above normal saved pages such as `/app/index` - taught the skill dispatcher to recurse through nested installed `skills/` trees when collecting skill nav pages for the web UI, while preserving deterministic order and disabled-skill filtering - fixed layered skill nav route-id discovery so `dashboards/nav/*` recursively includes nested fragment paths instead of only direct files in the first `nav/` directory 3.58 2026-05-06 - stamped dashboard-managed helper bodies with an explicit `developer-dashboard-managed-helper-version` marker so installed hosts can prove which helper build they actually have when staged helper behavior drifts from the active tarball - fixed Debian-family bash bootstrap installs and repairs so dashboard-managed shell lines are kept above the standard non-interactive `return` guards in `~/.bashrc`, including the Ubuntu single-line `[ -z "$PS1" ] && return` form, which keeps `dashboard ticket` tmux status commands working on installed hosts such as `hp.local` - taught `dashboard doctor` to audit and repair that misplaced `~/.bashrc` bootstrap shape with `dashboard doctor --fix` - fixed Windows `.cmd` and `.bat` command dispatch on Linux, WSL, and packaged-install hosts so an extensionless local `cmd` helper found in `PATH` is normalized back to `cmd.exe` instead of being treated as a custom command processor - taught `dashboard doctor` to audit staged helper drift under `~/.developer-dashboard/cli/dd/`, report stale or missing dashboard-managed helpers such as `_dashboard-core`, and restage them with `dashboard doctor --fix` when the currently shipped helper assets can repair the runtime - added direct regression coverage for extensionless `cmd` shims, stale helper staging, and shell bootstrap drift through both source-tree and packaged-install paths - fixed packaged helper asset discovery so tarball, PAUSE, and `cpanm` install-test trees keep resolving `share/private-cli` from a stable absolute module source path even after later `chdir` calls in long-running test processes - hardened the post-build smart-router two-stage Docker guard so one transient upstream `cpanm` fetch or unpack failure is retried once before the repository packaging gate is treated as broken - fixed the installer docs so they match the real checkout/master bootstrap behavior and the current shell-bootstrap repair workflow 3.45 2026-05-05 - fixed Unix-like `install.sh` so blank streamed installs no longer fall back to the stale `Developer::Dashboard` CPAN target; when no explicit `DD_INSTALL_CPAN_TARGET` override is set they now install the current checkout directly or clone GitHub `master` into a temporary checkout for `curl ... | sh` style bootstraps - added `tmux` to the shipped `aptfile`, `apkfile`, `dnfile`, and `brewfile` bootstrap package sets because `dashboard ticket` is a first-party tmux workflow - fixed blank Ubuntu bootstrap coverage so Debian-family installer docs and tests now prove the extra XS build header requirement for the live `cpanm` dependency chain instead of silently assuming the base image already ships it 3.43 2026-05-05 - fixed managed helper staging so upgraded home runtimes remove dashboard-owned older flat helpers from `~/.developer-dashboard/cli/` and keep the active built-in helper surface converged on `~/.developer-dashboard/cli/dd/` - added direct staged-helper regression coverage so the managed `dashboard shell bash` helper itself must emit the ticket tmux status bootstrap after staging, not just the repo checkout command path - fixed the blank-environment integration image so host-built tarball installs now include the native CPAN build packages required by XML::Parser, Net::SSLeay, and related transitive prerequisites - switched the blank-environment integration image to a real Debian Chromium runtime so browser smoke checks no longer die on the Ubuntu snap-wrapper launcher stub - fixed the host-side blank-environment launcher so it rebuilds the integration image from the current Dockerfile before running the installed-runtime smoke, preventing stale cached images from hiding browser-runtime fixes 3.42 2026-05-05 - fixed blank-mac `curl ... | sh` bootstrap so `install.sh` now bootstraps Homebrew automatically before reading the repo `brewfile` and continuing the normal macOS package flow - fixed Linux runtime lifecycle isolation so `dashboard restart` and `dashboard stop` no longer adopt or kill Developer Dashboard web and collector pids that belong to a different pid namespace such as a sibling Docker container - added Docker-style rolling detail lines to `dashboard skills install` so long-running dependency manifests keep the epic task board visible while streaming the newest manifest output under the active step - fixed tmux ticket-session prompt/status behavior so existing and new `dashboard ticket` tmux sessions suppress inline prompt indicators, move the full indicator strip into the first row of a two-line bottom tmux status block, keep tmux's normal indexed session/window row beneath it, preserve TT-backed percentage indicators and live collector values, refresh automatically through tmux status updates, and leave ordinary tmux sessions on the normal inline prompt path 3.40 2026-05-05 - moved prompt indicators out of inline shell prompts and into tmux `status-right` whenever the generated shell bootstrap runs inside tmux, while keeping the existing prompt indicators outside tmux - added a dedicated `dashboard ps1 --mode tmux-status` formatter and a `--no-indicators` prompt path so bash, zsh, POSIX sh, and PowerShell all share the same tmux-aware indicator behavior 3.37 2026-05-02 - added an explicit InternalCLI coverage guard for installed layouts where File::ShareDir already returns the private-cli root, keeping the numeric Devel::Cover gate at 100 percent after the Windows bootstrap fixes - added a Windows smoke guard that re-enters a fresh profile-loaded PowerShell session after install.ps1 so the streamed bootstrap must resolve `dashboard`, print `dashboard version`, and run `dashboard logs` 3.36 2026-05-02 - fixed the generated PowerShell profile bootstrap so fresh Windows sessions no longer try to pass the multi-line `dashboard shell ps` output array straight into `Invoke-Expression` - fixed the streamed Windows installer so `irm .../install.ps1 | iex` now writes a self-contained profile block that can load `dashboard`, `local::lib`, and the PowerShell prompt bootstrap in future sessions 3.35 2026-05-02 - fixed the installed private helper asset lookup on Windows so `dashboard init` no longer stops at an empty `MSWin32-x64-multi-thread/auto/Developer/Dashboard/private-cli` directory when the real shipped helper assets live under `auto/share/dist/Developer-Dashboard/private-cli` - fixed the streamed Windows checkout bootstrap so a blank host can now finish `dashboard init` after `irm .../install.ps1 | iex`, stage the home helper runtime, and continue into the generated PowerShell shell bootstrap cleanly 3.34 2026-05-02 - fixed the installed private helper asset lookup so Windows local::lib installs no longer look for `_dashboard-core` under the wrong arch-auto path when the shared dist asset root actually lives under `auto/share/dist/Developer-Dashboard/private-cli` - fixed the streamed PowerShell bootstrap so `install.ps1` now runs `dashboard init` before activating `dashboard shell ps` in the current shell, ensuring the staged home helper runtime exists before the bootstrap asks for PowerShell shell wiring - fixed the generated PowerShell profile guard so future sessions only ask `dashboard shell ps` for bootstrap output after the staged home helper runtime exists under `~/.developer-dashboard/cli/dd/_dashboard-core` 3.33 2026-05-01 - fixed the packaged install metadata so blank Windows hosts no longer pull test-only dependencies such as `Plack::Test` and `Test::Pod` during `cpanm --notest Developer::Dashboard`, which previously dragged the `Test::SharedFork` chain into the streamed PowerShell bootstrap and broke fresh installs - fixed the streamed Windows checkout bootstrap so it now installs from the current GitHub checkout shape cleanly on Windows, including the Windows-safe `Makefile.PL` version path and the self-healing private helper staging path used by lightweight commands such as `dashboard encode` - fixed the PowerShell bootstrap so `install.ps1` now sets the CurrentUser execution policy to `RemoteSigned` before writing the generated profile, allowing new PowerShell sessions to load the `dashboard shell ps` bootstrap instead of failing with `running scripts is disabled` - fixed the generated PowerShell profile block so `PERL_MB_OPT` is now written with format-string quoting instead of nested literal quotes that broke new sessions with `Unexpected token '$ddInstallRoot""'` - replaced the repository PSGI test dependency on `Plack::Test` with a local harness under `t/lib` so the release metadata can stay focused on real runtime requirements while the web route coverage still stays intact - corrected the install bootstrap docs so the Windows examples use the `install.ps1` entrypoint and describe the lighter packaged install path 3.30 2026-05-01 - fixed the streamed Windows bootstrap so native `winget` command output is written to the host terminal instead of leaking into typed helper return values, which previously broke the Strawberry Perl path hand-off during `irm .../install.ps1 | iex` - fixed the Windows Perl bootstrap so `install.ps1` no longer tries to self-install `App::cpanminus` while the downloaded `cpanm` bootstrap script is still running, avoiding the Windows file-replacement failure that broke the `local::lib` setup step on blank hosts - documented the exact streamed PowerShell bootstrap command as `irm https://raw.githubusercontent.com/manif3station/developer-dashboard/refs/heads/master/install.ps1 | iex` and updated the Windows install docs to describe the standalone `cpanmin.us` plus `local::lib` bootstrap flow accurately 3.28 2026-05-01 - renamed the Windows checkout bootstrap entrypoint from `install.ps` to `install.ps1` so the shipped filename matches normal PowerShell script expectations while keeping the streamed `irm ... | iex` flow explicit - hardened the Windows bootstrap winget path so package installs are pinned to the community `winget` source and the installer performs one `winget source reset --force` plus source refresh retry before failing with a clear HRESULT-style exit code 3.27 2026-05-01 - added a repo-root `install.ps1` bootstrap entrypoint for Windows PowerShell hosts so blank-machine installs can bootstrap Git, Strawberry Perl, Node.js, user-space Perl tooling, and the dashboard runtime from a checkout or a streamed `irm ... | iex` flow - updated the install bootstrap docs, release metadata guards, and shipped tarball asset checks so the Windows bootstrap path is tracked alongside the existing Unix-like `install.sh` flow 3.26 2026-04-30 - changed the blank-container tarball installation gate to use `cpanm --notest` after the normal source-tree test and coverage gates have already passed, so packaged dependency resolution and installed runtime behavior stay verified without rerunning the full tarball test suite - updated the blank-environment integration runner, release docs, and integration guardrails so the post-build `t/44-smart-router-two-stage.t` check and the broader tarball integration flow both follow the same `cpanm --notest` policy 3.25 2026-04-29 - fixed the installed Dancer web stack so the public `/app`, `/ajax`, `/js`, `/css`, and `/others` routes now reuse the backend smart route dispatcher instead of bypassing skill-local namespace resolution - added PSGI coverage that proves top-level and nested skill-local app, ajax, js, css, and others routes all work through the real installed web route layer - added the post-build `t/44-smart-router-two-stage.t` guard and wired the blank-environment host integration launcher to run it immediately after `dzil build` so extracted-dashboard routing regressions are caught at the tarball stage instead of only in source-tree tests - fixed helper-login blank-editor rendering so a helper user who lands on the root editor path still sees helper chrome, including the logout link and helper username, during installed integration runs 3.24 2026-04-29 - added skill-local web namespaces for `dashboards/ajax/*` and `dashboards/public/{js,css,others}/*` so installed skills can serve saved Ajax handlers and public assets without copying them into the shared dashboard roots - added longest-prefix route resolution so `/app`, `/ajax`, `/js`, `/css`, and `/others` can distinguish skill prefixes from normal nested global paths and can also resolve nested child skills under `skills//...` - kept nested global saved-bookmark assets and saved Ajax files working even when their leading path segments match an installed skill name 3.23 2026-04-29 - fixed the README source-of-truth process so the checked-in README.md is regenerated from the canonical Developer::Dashboard POD instead of being hand-edited independently - added a checkout sync helper and release-metadata guard so README.md must exactly match the generated Markdown output from the canonical POD - fixed the JS fast-check wrapper so npm update-notifier chatter does not break the stderr-clean fuzz gate on newer npm releases 3.22 2026-04-29 - regenerated README.md as real Markdown instead of accidental POD content - added a release-metadata guard so README.md cannot silently regress back into POD format 3.21 2026-04-29 - removed the final stale API Dashboard POD example from the public dashboard entrypoint so the extracted API and SQL dashboards are now fully pruned from core code, docs, POD, tests, and shipped assets 3.20 2026-04-29 - fixed bare dashboard skills install so a first successful install from the home root ddfile is reported as installed even when the skill ships no .env VERSION metadata, instead of showing unknown with blank version markers - fixed named collector stop summaries so dashboard stop collector NAME still reports the stopped collector row when the managed loop is alive but its process title has not become observable yet 3.19 2026-04-29 - extracted optional browser workspaces out of the core distribution, leaving the core runtime seeded-page set and manuals focused on the features that still ship in Developer Dashboard itself - removed core seeded-page handling, runtime bootstrap wiring, release metadata assertions, and shipped browser tests for the extracted optional browser workspaces - moved the extracted page assets, dedicated tests, and supporting notes out of the core repository tree so the default core tarball no longer carries that optional feature set 3.19 2026-04-29 - fixed dashboard stop and restart so they follow the saved managed listener port back to the real serving pid when the live web process renames itself into the underlying starman master listener shape - fixed minimal Docker lifecycle control so container stop/restart runs no longer lose ownership of the active listener after startup