Skip to main content

devctl-plugin-authoring

Write, update, and troubleshoot devctl plugins that speak the NDJSON stdio protocol v2 (handshake + request/response/event frames). Use when creating a new devctl plugin, converting repo scripts into devctl pipeline ops, adding dynamic commands, wiring .devctl.yaml, or debugging protocol contamination/timeouts and other plugin failures.

Datos de origen

Repositorio
go-go-golems/devctl
Última actividad en el origen
13 de septiembre de 2026 a las 11:05
Idioma detectado de SKILL.md
inglés
Estrellas
1
Forks
1

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
3 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
devctl-plugin-authoring
description
Write, update, and troubleshoot devctl plugins that speak the NDJSON stdio protocol v2 (handshake + request/response/event frames). Use when creating a new devctl plugin, converting repo scripts into devctl pipeline ops, adding dynamic commands, wiring .devctl.yaml, or debugging protocol contamination/timeouts and other plugin failures.
# Devctl Plugin Authoring ## Overview Build repo-specific dev environment logic as a devctl plugin while devctl handles orchestration, supervision, and logs. ## Workflow Before implementing, discover the installed interface with `devctl help --all`. Read the listed user, scripting, and plugin-authoring topics (normally `devctl help user-guide`, `devctl help scripting-guide`, and `devctl help plugin-authoring`). Use `devctl <verb> --help` to check the flags for each lifecycle command you intend to document or execute. Use the installed protocol schemas and verify lifecycle semantics when selecting build, prepare, validation, or restart behavior. ### 1. Collect repo context - Identify services to run and their commands, env vars, ports, and health checks. - Identify prerequisites (node, docker, db migrations) and build/prepare steps. - Decide which ops you will implement. - Identify whether services should run directly or through a small shell wrapper for setup such as `mkdir -p var/devctl`. - Identify which settings should be committed defaults and which should be environment overrides (ports, profile names, registry paths, credentials, backend targets). ### 2. Choose ops and config keys - Start small: `config.mutate`, `validate.run`, and `launch.plan` are usually enough. - Add `build.run` and `prepare.run` when you need ordered steps with artifacts. - Add `command.run` only for dynamic helper commands (db reset, seed, etc.). - Keep config keys stable and descriptive: `env.*`, `services.<name>.port`, `services.<name>.url`, `artifacts.*`. For repositories with several demos or deployment shapes, prefer one small, committed environment manifest per profile. The plugin should load and validate the manifest, then derive config, preparation, launch, and dynamic commands from the same source. Keep topology and secret *schemas* in the manifest; never put secret values there. ### 3. Implement the protocol skeleton - Emit a handshake as the very first stdout line. - After the handshake, stdout must be *only* NDJSON frames (one JSON object per line). - Send all logs to stderr; flush after every stdout write. - Return `E_UNSUPPORTED` for unhandled ops to keep behavior explicit. See `references/protocol-quickref.md` for minimal Python and bash skeletons. ### 4. Implement ops - `config.mutate`: return a config patch with dotted keys; avoid side effects. Include useful discovered URLs/profile/config facts so `devctl plan` is informative. - `build.run` / `prepare.run`: run named steps and return step results; respect `ctx.deadline_ms`. - `validate.run`: return actionable errors/warnings; do not hide failures. Check executables, repo-relative paths, profile/config files, and whether dependency directories such as `web/node_modules` are missing. - `launch.plan`: return services for devctl to supervise; do not start processes yourself. - `command.run`: execute a named command from `capabilities.commands`; return `exit_code`. #### Dynamic-command registration A plugin advertises custom commands in its handshake; devctl uses a persistent command catalog to expose them as CLI subcommands. Plugin configuration and command registration are separate steps: 1. Add `command.run` and deterministic command specifications to the handshake. 2. Run `devctl plugins refresh` for the intended repository and profile. 3. Invoke the advertised command and test its argument handling. 4. Refresh the catalog after changing command specifications or when devctl requests a refresh because its catalog is missing or stale. `devctl plugins list` inspects plugin configuration; it is not a substitute for registering custom commands in the catalog. Dynamic command names are part of the handshake and therefore must be deterministic before a request is read. If commands vary by profile, advertise the stable union and return an actionable error when a command is unavailable for the active profile. Test the exact CLI flag forwarding supported by the installed devctl rather than assuming arbitrary arguments are passed through. For services that need shell setup, prefer a non-interactive shell wrapper such as `bash --noprofile --norc -lc 'mkdir -p var/devctl && exec ...'` so user shell startup files do not pollute logs or emit interactive-shell warnings. Still keep protocol stdout clean in the plugin itself. For a backend + Vite frontend setup, return two services: the backend with an HTTP health check, and Vite with `VITE_*_BACKEND_TARGET` (or the repo's equivalent) so the dev server proxies API and websocket requests to the backend. For Docker Compose, return `docker compose up` as a foreground supervised service. Do not use detached mode: devctl needs the foreground process to capture logs and determine whether the environment exited. Avoid fixed Docker subnets and IP addresses unless stable addressing is an actual requirement; they frequently overlap other development networks. Prefer service DNS names, and make health checks independent of fixed container IPs. When `prepare.run` materializes a set of secret files: - fetch the complete logical set before publishing any file; - validate encodings and minimum/exact lengths before writing; - write a new private staging directory with restrictive modes; - atomically switch a plugin-owned symlink to the completed directory; - compare the target path lexically when deciding whether it is a managed symlink—calling `resolve()` first dereferences the symlink and breaks repeat preparation; - reject unrelated existing directories instead of overwriting them; - keep secret values out of argv, environment variables, stdout, and logs. Use Vault KV v2 check-and-set writes when initializing missing records. Treat runtime, bootstrap, and integration material as distinct records so permissions and rotation procedures can differ. Always honor: - `ctx.repo_root` for relative paths. - `ctx.dry_run` by avoiding side effects. - `ctx.deadline_ms` by enforcing timeouts in subprocesses. #### Subprocess lifetime and cancellation Build and preparation commands are temporary children of the plugin, unlike long-running services returned by `launch.plan`. The plugin must own their complete lifetime: - Convert each request's `ctx.deadline_ms` into a monotonic deadline. Give each subprocess only the remaining time; do not restart the full budget per step. - Route subprocess output to stderr and bound both output collection and process waiting. - On timeout or cancellation, terminate and reap all owned descendants. If a subprocess uses a separate process group, arrange explicit cleanup of that group rather than relying on termination of the plugin's group. - Make shutdown safe during active work, idle request handling, and normal EOF. Cleanup must be idempotent and must not signal unrelated processes. - In Python, use a deliberate child-cleanup and exit path for SIGTERM. Avoid raising exceptions through interpreter teardown. Test termination during a running subprocess and immediately after normal completion. A streaming-output helper is not a complete subprocess supervisor unless it also implements timeout, cancellation, and descendant cleanup. For Python plugins, vendor the supported standalone helper from `sdk/python/devctl_runner.py` and share one `Budget` across every step in a request. Test these properties with a slow fixture child before using expensive build commands. ### 5. Wire the repo config Add `.devctl.yaml` at repo root, e.g.: - `id`: stable plugin identifier. - `path` + `args`: how to run the plugin. - `env`: optional plugin env vars. - `priority`: merge precedence. ### 6. Test the loop Use the tight feedback loop: 1) `devctl plugins list` 2) `devctl plan` 3) `devctl up` for a new environment; use replacement/force options only after inspecting installed help and confirming ownership 4) `devctl status` 5) `devctl logs <name> --tail 50` or `devctl logs <name> --stream stderr --tail 50 --follow` 6) project-specific smoke test against the devctl-managed ports/DB/log paths 7) `devctl down` #### Isolated lifecycle smoke tests Use a dedicated test environment with known process ownership and nonconflicting ports. Register cleanup before starting services so a failed assertion does not leave test processes behind: ```bash # Only in a dedicated environment owned exclusively by this test. set -e trap 'devctl down' EXIT devctl up devctl status # Run the project's readiness and behavior assertions here. ``` Pass the same repo/config/profile arguments to launch and cleanup. Do not use a broad `down` trap in a shared environment or against pre-existing live services. Verify stopped state and retained exit evidence after cleanup. #### Build and restart contracts Building changes artifacts on disk; restarting replaces a running service attempt. Document them separately. In implementations that run the build phase during restart, an explicit build followed by a restart that skips rebuilding makes the intended artifact boundary visible: ```bash devctl build --timeout 5m devctl restart <name> --skip-build ``` Check prepare and validation behavior too: `--skip-build` only skips the build phase. If preparation can modify the executable or its dependencies, the sequence alone does not establish an immutable artifact. Prefer atomically published, versioned artifacts, record their hashes, and associate the selected artifact with the new service run. A hash of the current on-disk binary does not identify an already-running executable. Keep restart planning idempotent and avoid recursive lifecycle commands inside plugins. Use devctl's built-in start/restart/down operations for supervision. For hardware or stateful services, document domain-specific quiescence and handoff prerequisites; process termination is not proof that external effects have stopped. #### Interpreting status Read these independently: - **Desired state:** what the operator requested. - **Process/run state and exit evidence:** whether the service is running and how a completed attempt ended. - **Health result:** what the last readiness probe observed, including its time. An exited service can retain a successful historical health result. Health checks should test the intended readiness contract without mutating external systems; they are not evidence of current liveness after process exit. If protocol issues appear, retry with `devctl --log-level debug plugins list`. ### 7. Troubleshoot common failures - **stdout contamination**: non-JSON output on stdout; move logs to stderr. - **missing handshake**: first stdout frame not a handshake; emit immediately. - **timeouts or orphaned build children**: verify the subprocess lifetime contract above, including remaining request budget and descendant cleanup. - **custom command unavailable**: refresh the command catalog for the intended repo/profile and verify the advertised name and arguments. - **shutdown errors**: exercise cancellation while busy and idle; verify cleanup is idempotent and terminates only owned processes. - **health failures**: check ports/URLs and health config in `launch.plan`. - **Compose exits during startup**: inspect both stdout and stderr with separate `devctl logs` calls; BuildKit progress usually appears on stdout, while daemon and service failures may appear on stderr or in `devctl status`. - **repeat prepare rejects its own secret target**: keep the target path lexical while checking the managed symlink; resolve only the symlink destination. - **Docker network pool overlap**: remove unnecessary fixed subnets/IPs and use Compose service discovery. ## References - Use `devctl help user-guide` for the user workflow. - Use `devctl help scripting-guide` for practical plugin patterns. - Use `devctl help plugin-authoring` for full protocol schemas.
Ver en GitHub