skills/redeploy-local/SKILL.md
After code changes, auto-detect the project's build system and local deployment method for a given directory, then build the project and restart its locally-deployed environment (Docker Compose / systemd / process manager). Never assumes — asks only when detection is ambiguous. Caches detected commands per project in .cortex/redeploy-local.yaml; re-invocations on the same project skip re-scanning until signal files change, the cache expires (30 days), or the skill version bumps.
npx skillsauth add nesnilnehc/ai-cortex redeploy-localInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
After an Agent modifies code, execute the project's build and restart its locally-deployed environment — without requiring the user to know the project's tech stack. Covers projects that deploy locally via Docker Compose, systemd units, or process managers (pm2, supervisorctl). Intentionally excludes hot-reload dev servers; the target is a running local environment, not a development watch mode.
Primary goal: Given a directory, produce one successful build and one successful local deployment restart, and report what was run.
Success Criteria (all must be satisfied):
Acceptance Test: After the skill completes, the locally deployed service reflects the latest code changes and its status check reports healthy/running.
This skill handles:
.cortex.yaml build_command / deploy_command).cortex/redeploy-local.yaml and reusing them on next runThis skill does not handle:
Handoff point: When the run report shows all steps exit 0, skill is done. If any step fails, the skill reports the failure and stops — it does not auto-retry or patch the build error.
.cortex.yaml — same commands every time, no detection varianceThis skill does NOT carry a hardcoded mapping of "file X → command Y". Build and deploy commands are project-specific — two Go projects can have entirely different build conventions (custom ldflags, output paths, cross-compile targets), and a Node project's build script may not be what deploy needs (it might want build:prod instead). The agent's job is to scan the directory, read the relevant files, and infer the project's actual commands — then surface those inferences for user confirmation.
Check for .cortex.yaml in the target directory:
build_command: make release
deploy_command: docker compose up -d --build
If both fields are present, use them verbatim and skip Steps 1–4 (no confirmation needed either). If only one is present, run inference for the other and still go through Step 4 for confirmation.
After Step 0 (override) but before Step 1 (full scan), check .cortex/redeploy-local.yaml in the target directory. If valid, reuse the cached inferences and jump to Step 4 (Present inferences for confirmation — the user still confirms the report, annotated (from cache, scanned YYYY-MM-DD; <N> signal files unchanged)). If absent, malformed, or stale, fall through to Step 1 — never auto-delete a stale cache file; the next successful run overwrites it.
.cortex.yaml override (Step 0) wins over the cache. If Step 0 set one of the two commands, that command is taken from .cortex.yaml and the cache supplies only the other (if its validation still passes for the remaining field).
NEVER commit .cortex/redeploy-local.yaml — it encodes host-local absolute paths and mtimes; add it (or the whole .cortex/ directory) to .gitignore. The file is regenerable on first successful run.
Cache file schema (.cortex/redeploy-local.yaml):
# Auto-generated by redeploy-local skill; do not edit by hand
skill_version: 3.1.0
project_path: /Users/alice/work/api-service
written_at: 2026-05-20T14:32:11Z
build:
command: pnpm run build
evidence:
- file: package.json
ref: scripts.build
- file: Dockerfile
ref: "COPY dist/ (cross-ref)"
deploy:
command: docker compose up -d --build
evidence:
- file: docker-compose.yml
ref: "services: api, worker, db"
signal_files:
- path: package.json
mtime: 2026-05-20T11:02:44Z
- path: pnpm-lock.yaml
mtime: 2026-05-18T09:14:02Z
- path: Dockerfile
mtime: 2026-05-19T16:55:31Z
- path: docker-compose.yml
mtime: 2026-05-20T10:48:09Z
evidence records only file + ref (e.g. scripts.build) — the command body itself is not duplicated, to keep the cache small and free of drift. signal_files records every file READ during inference (including cross-references), not just the file the chosen command came from.
Validation pseudo-logic — check in order, first failure is a cache miss:
skill_version, project_path, written_at, build, deploy, signal_files).skill_version string-equals the current skill's frontmatter version value.project_path equals the resolved absolute target directory (symlinks resolved via realpath).now() - written_at < 30 days.signal_files: path still exists AND current mtime equals the recorded mtime.build.command and deploy.command are non-empty strings.All pass → cache hit (jump to Step 4 with annotation). Any failure → cache miss; log cache invalid: <reason>; falling back to full scan and continue to Step 1.
Walk the target directory (depth 1, plus deploy/, .github/workflows/, docs/) and collect the presence of:
Makefile, Taskfile.yml, justfile, package.json, scripts/build*Dockerfile, docker-compose.yml, compose.yaml, compose.*.yamlgo.mod, Cargo.toml, pom.xml, build.gradle*, *.csproj, *.sln, pyproject.toml, setup.pyecosystem.config.{js,cjs,json}, supervisord.conf, supervisor/*.conf, deploy/*.service, ProcfileREADME.md, CONTRIBUTING.md, docs/development*.md, docs/build*.md.github/workflows/*.yml, .gitlab-ci.yml, Jenkinsfile — these often pin the team-blessed build commandFor each signal found, read its contents (do not assume defaults):
Makefile: enumerate targets (grep -E '^[a-zA-Z_-]+:' Makefile); look for build, release, compile, dist, all. Read the body of the candidate target to confirm it actually builds (not just echoes). If multiple plausible targets exist, list them and ask the user which.package.json: read the scripts object. Look for build, build:prod, compile, bundle. If Dockerfile copies dist/, prefer the script that produces dist/. Detect package manager via lockfile (pnpm-lock.yaml → pnpm; yarn.lock → yarn; package-lock.json → npm).Taskfile.yml / justfile: enumerate tasks/recipes the same way as Makefile.Dockerfile: inspect COPY, RUN, CMD lines to understand what artifacts the image expects and what builds happen inside the container. Use as a cross-reference (e.g., COPY dist/ means the host build must produce dist/) — Dockerfile is rarely the host build command itself.README.md / docs/: scan for "Build" / "Development" / "Getting Started" sections; look for fenced code blocks with shell commands.go build ./...; Rust → cargo build --release; Python → pip install -e .; Java/Maven → mvn package; Java/Gradle → ./gradlew build; .NET → dotnet build.Priority of evidence (highest first):
Makefile / Taskfile / justfile target with explicit build semanticspackage.json script (for Node projects)go build, cargo build, etc.) — only when above are absentConflict resolution: if Makefile says make build and CI says make release, surface both to the user with their sources and ask which to run. Never silently pick.
Same principle — read the deploy-related files, don't assume:
docker-compose.yml: list defined services; the standard command is docker compose up -d --build. If a Makefile has deploy/up/run targets that wrap Compose, prefer the Makefile target — it captures project-specific flags (profiles, env files).ecosystem.config.{js,cjs}: read the file and extract apps[].name. If exec_mode: cluster, prefer pm2 reload <name> (zero-downtime); otherwise pm2 restart <name>.supervisord.conf: enumerate [program:<name>] sections. If multiple programs, list them and ask which to restart (or supervisorctl restart all if user confirms).deploy/*.service: derive unit name from filename. Check file ownership vs current uid to decide if sudo is needed.Makefile with deploy / restart / up targets: these typically encode the project's true restart procedure (env vars, pre-hooks). Prefer them over raw docker compose / systemctl.docker compose up -d --build — do not run a separate build, mark the build step as "skipped — subsumed by deploy".Priority of evidence (highest first):
Makefile deploy/restart/up target (project-specific wrapper)Show the user a structured inference report before executing anything:
Detected build:
Source: Makefile target `build` (line 12, calls `go build -ldflags ...`)
Cross-ref: README "Building" section confirms `make build`
Command: make build
Detected deploy:
Source: docker-compose.yml (services: api, worker, db)
Cross-ref: Makefile `up` target wraps it with --env-file
Command: make up ← Makefile wrapper preferred over raw compose
Proceed only after the user confirms. Skip confirmation only when both commands come from .cortex.yaml.
Resolve target directory
test -d <path>)Read config override (if .cortex.yaml present)
build_command and/or deploy_commandCheck inference cache (Detection Approach Step 0.5)
.cortex/redeploy-local.yaml if present(from cache, scanned YYYY-MM-DD; <N> signal files unchanged).cortex.yaml (step 2) are dropped from cache reuse; the cache may still supply the remaining fieldInfer build command (if not overridden and not cached)
build_commandInfer deploy command (if not overridden and not cached)
Present inferences & confirm
.cortex.yamlExecute build (if not skipped)
{ start=$(date +%s); <cmd>; echo $(($(date +%s)-start))s; }Execute deployment restart
Health check
docker compose ps — confirm all containers show Upsystemctl is-active <unit>pm2 list | grep <name>supervisorctl status <program>Emit run report
Step Command Exit Duration
─────── ────────────────────────────── ──── ────────
Build pnpm run build 0 18.2s
Deploy docker compose up -d --build 0 6.3s
Health docker compose ps 0 0.2s
Write cache on success
.cortex.yaml (cache adds no value).cortex/ (mode 0755) if missing, then write .cortex/redeploy-local.yaml with the # Auto-generated by redeploy-local skill; do not edit by hand headerskill_version, resolved absolute project_path, ISO 8601 UTC written_at, the chosen commands with their evidence, and every signal file READ during inference plus its current mtimecache write skipped: <reason> and continue — the deploy already succeeded; the cache is only an optimization layer.cortex.yaml provides both commands.cortex.yaml with build_command and/or deploy_commandProvides:
.cortex/redeploy-local.yaml on fully successful run (build + deploy + health all pass)rm -rf or destructive cleanup commands as part of build without explicit user configsudo unless evidence justifies it (e.g., systemd unit file owned by root vs. current uid); ask the user to confirm elevation before running| Failure | Behavior |
|---|---|
| Directory not found | Abort immediately; show exact path checked |
| No build evidence found | Stop before build; ask user for build_command or to populate .cortex.yaml |
| No deploy evidence found | Stop before deploy; ask user for deploy_command or to populate .cortex.yaml |
| Evidence conflicts (e.g., Makefile vs CI disagree) | Stop; surface both options with sources; let user choose |
| Name/unit extraction fails | Stop; ask user to provide the name explicitly |
| Build exit ≠ 0 | Show last 20 lines; stop; do not run deploy |
| Deploy exit ≠ 0 | Show last 20 lines; stop; report partial state |
| Health check fails | Warn; do not fail the skill run; do not write cache |
| Cache file malformed or missing required keys | Treat as cache miss; fall through to full scan; do not delete; successful run overwrites |
| Recorded signal file deleted / mtime changed | Cache miss; re-scan |
| Cache skill_version differs from current | Cache miss; re-scan |
| Cache write fails (permission / disk full) | Log warning; do not fail the skill run — deploy already succeeded |
--build in deploy command handles it.cortex.yaml).cortex/redeploy-local.yaml read before scanning; written only after a fully successful run (build + deploy + health all pass)Directory contents: package.json, pnpm-lock.yaml, Dockerfile, docker-compose.yml, README.md
Inference process:
package.json scripts: found build, build:prod, test, lintDockerfile: COPY dist/ /app/ — image expects dist/ to existpackage.json scripts.build: tsc && vite build --outDir dist — produces dist/, matchesscripts.build:prod is NODE_ENV=production npm run build — would also work, but build is the default referenced in READMEpnpm-lock.yaml → pnpmInference report shown to user:
Detected build:
Source: package.json scripts.build (tsc && vite build --outDir dist)
Cross-ref: Dockerfile copies dist/, matches output path
Command: pnpm run build
Detected deploy:
Source: docker-compose.yml (3 services: api, worker, db)
Command: docker compose up -d --build
After user confirms, run report:
Step Command Exit Duration
─────── ────────────────────────────── ──── ────────
Build pnpm run build 0 18.2s
Deploy docker compose up -d --build 0 6.3s
Health docker compose ps 0 0.2s
Directory contents: go.mod, Makefile, deploy/myapp.service, .github/workflows/ci.yml
Inference process:
Makefile targets: build, test, lint, release, installMakefile body of build target: go build -ldflags "-X main.Version=$(VERSION)" -o bin/myapp ./cmd/myapp — project-specific ldflags and output path; do NOT default to go build ./....github/workflows/ci.yml: build step runs make build — confirms canonicaldeploy/myapp.service filename → unit myapp; check file owner (root) vs current uid → sudo systemctl restart myapp likely neededInference report:
Detected build:
Source: Makefile target `build` (line 8)
Cross-ref: .github/workflows/ci.yml uses `make build`
Command: make build
Detected deploy:
Source: deploy/myapp.service (unit name from filename)
Note: unit file owned by root → sudo required
Command: sudo systemctl restart myapp
Directory contents: docker-compose.yml, Dockerfile (no Makefile, no package.json, no language manifest at root)
Inference process:
Dockerfile does the build during docker compose up --buildRun report:
Step Command Exit Duration
─────── ────────────────────────────── ──── ────────
Build (skipped — subsumed by deploy) — —
Deploy docker compose up -d --build 0 9.1s
Health docker compose ps 0 0.2s
.cortex.yaml:
build_command: make release GOARCH=arm64
deploy_command: supervisorctl restart api-worker
Detection: Both commands read from .cortex.yaml — no heuristics applied, no confirmation prompt.
Run report:
Step Command Exit Duration
─────── ─────────────────────────────── ──── ────────
Build make release GOARCH=arm64 0 22.7s
Deploy supervisorctl restart api-worker 0 0.8s
Health supervisorctl status api-worker 0 0.1s
Scenario: pnpm run build exits with code 1
[build] FAILED — exit 1 after 4.2s
Last 20 lines of output:
...
Error: cannot find module 'express'
Deployment step skipped.
Suggested fix: run `pnpm install` to restore dependencies, then retry.
Directory contents: same as Example 1 (package.json, pnpm-lock.yaml, Dockerfile, docker-compose.yml, README.md) plus .cortex/redeploy-local.yaml written by a prior successful run.
Detection process:
.cortex.yaml override.cortex/redeploy-local.yaml; validation passes (same skill version, same project_path, 2 days old, all 4 signal_files mtimes unchanged)Inference report shown to user:
Detected build:
Source: .cortex/redeploy-local.yaml (cached 2026-05-20; 4 signal files unchanged)
Command: pnpm run build
Detected deploy:
Source: .cortex/redeploy-local.yaml (cached 2026-05-20; 4 signal files unchanged)
Command: docker compose up -d --build
After user confirms, run report (identical commands to Example 1, no scan overhead):
Step Command Exit Duration
─────── ────────────────────────────── ──── ────────
Build pnpm run build 0 17.8s
Deploy docker compose up -d --build 0 6.1s
Health docker compose ps 0 0.2s
The cache file is rewritten on success with a refreshed written_at and current signal-file mtimes.
development
Generate an LLM agent test suite (golden cases, mock-LLM unit tests, evaluator harness) from an agent implementation and its agent-test contract. Use when an agent has no tests, or a contract exists but the test code is missing.
tools
Publish a NATS message conforming to a cross-team contract, using NATS MCP tools. Authors the contract on first use if missing. Reads project-level cache (.cortex/nats.yaml) to avoid re-prompting basics across sessions.
tools
Drain pending NATS messages from a producer contract via NATS MCP tools (default batch / drain-style). Applies Tolerant Reader semantics and per-message ack/nak/term, returning aggregated stats. Reads project-level cache (.cortex/nats.yaml) to avoid re-prompting.
testing
Iteratively review changes, run automated tests, and apply targeted fixes until issues are resolved (or a stop condition is reached).