NAME
Developer::Dashboard::SkillDispatcher - execute commands from installed skills
SYNOPSIS
use Developer::Dashboard::SkillDispatcher;
my $dispatcher = Developer::Dashboard::SkillDispatcher->new();
my $result = $dispatcher->dispatch('skill-name', 'cmd', 'arg1', 'arg2');
my $hooks = $dispatcher->execute_hooks('skill-name', 'cmd');
my $config = $dispatcher->get_skill_config('skill-name');
my $path = $dispatcher->get_skill_path('skill-name');
DESCRIPTION
Dispatches commands to and manages execution of installed dashboard skills. Handles: - Command execution with isolation - Hook file execution in sorted order - Configuration reading - Skill path resolution - Command output capture
Every browser-facing resolver (skill names, route ids, ajax files, and static asset paths) validates its untrusted path input through the shared Developer::Dashboard::PathRegistry::validated_path_segments guard before joining it onto a skill root, so parent-directory, absolute, drive-qualified, backslash, and control-byte shapes resolve to nothing instead of escaping the layered skills trees, and skill_static_roots exposes the layered dashboards/public roots the web layer asserts containment against.
Installed-skill navigation discovery caches each sorted relative route list in a bounded per-dispatcher FIFO. Every request fingerprints each directory in a cached tree before reusing its route list; a changed or missing directory causes a rescan. This avoids repeated directory enumeration without caching resolved files or skipping the normal path containment and file-identity checks. The cache retains at most 128 roots, with each inventory limited to 4,096 directories, 8,192 file paths, and 256 KiB of route and fingerprint data.
When every installed skill nav page is a literal raw fragment, the dispatcher also keeps one aggregate page-array snapshot behind the existing optional cross-platform filesystem watcher. Its watch set includes runtime and skill roots, their creation parents, dashboards/nav directories, and every discovered nested nav directory. An event invalidates the aggregate and returns loading to the normal route-inventory and per-file stat checks. Dynamic or structured pages are never held in the aggregate snapshot. If the watcher is unavailable, fails setup, or fails while polling, aggregate reuse is disabled and the portable page-loading path remains active; watcher shutdown is explicit when the dispatcher is destroyed. Failed watcher closure is reported even when an optional watcher implementation does not provide a diagnostic method.
Skill command and hook environments are loaded through Developer::Dashboard::EnvLoader::load_skill_runtime_layers: home runtime files provide defaults, skill and skill-CLI files override those defaults, and deeper project runtime files retain final precedence.
PURPOSE
This module executes installed skill commands and serves skill bookmark routes. It resolves a skill command path, runs any sorted hook files under cli/<command>.d, prepares the isolated skill environment, hands hook state through Developer::Dashboard::Runtime::Result so oversized hook payloads spill into RESULT_FILE automatically, captures stdout/stderr, and can render bookmarks under /skill/<repo>/bookmarks/... through the main page renderer.
WHY IT EXISTS
It exists because the skill system needs a boundary between skill installation and skill execution. Dispatching commands, hook chaining, isolated environment variables, and bookmark routing all belong in one module instead of being hand-built in the web layer or CLI wrappers. It also answers <skill>.version natively (DD-1043) by reading a raw VERSION= line from the skill's own layered .env when the skill ships no cli/version script of its own, so no skill needs to hand-write one just to expose a version number.
WHEN TO USE
Use this file when changing skill command execution, hook order, the skill environment contract, or the bookmark route behavior exposed under the /skill/... URL space.
HOW TO USE
Construct it with a skill manager, call dispatch for one command invocation, or call route_response when the web app needs a bookmark response from a skill. Keep skill execution isolation here instead of folding it into generic command code.
WHAT USES IT
It is used by dotted dashboard <repo-name>.<command> dispatch routed through the skills helper, by the skill bookmark web routes, by skill installation flows that later need execution, and by skill-system and web-route regression tests.
EXAMPLES
Example 1:
perl -Ilib -MDeveloper::Dashboard::SkillDispatcher -e 1
Do a direct compile-and-load check against the module from a source checkout.
Example 2:
prove -lv t/19-skill-system.t t/20-skill-web-routes.t
Run the focused regression tests that most directly exercise this module's behavior.
Example 3:
HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lr t
Recheck the module under the repository coverage gate rather than relying on a load-only probe.
Example 4:
prove -lr t
Put any module-level change back through the entire repository suite before release.