Purpose
After the Agent modifies code, execute the project's build and restart the locally deployed environment—without the user needing to know the project's tech stack. Suitable for projects deployed locally via Docker Compose, systemd unit, or a process manager (pm2, supervisorctl). Hot-reload development servers are intentionally excluded; the target is a running local environment, not development watch mode.
Core Objective
Primary objective: Given a directory, successfully perform one build and one local deployment restart, and report what was run.
Success criteria(all must be satisfied):
- ✅ Directory confirmed: The target directory was verified to exist before any command execution
- ✅ Build command determined or configured: The command can be traced to a configuration file or detection heuristic—not invented
- ✅ Build succeeded: Exit code 0; build artifacts exist in the expected location
- ✅ Deployment method determined or configured: The deployment target was identified (Compose / systemd / pm2 / supervisord)
- ✅ Deployment restarted: The service/container was restarted and is reachable (health check or status check passed)
- ✅ Run report output: A table of command → exit code → duration for each executed step
Acceptance test: After the skill completes, the locally deployed service reflects the latest code changes, and the status check reports healthy/running.
Scope Boundaries
This skill is responsible for:
- Detecting the build system from project files (package.json, Makefile, go.mod, Cargo.toml, pom.xml, build.gradle, *.csproj, pyproject.toml)
- Detecting the local deployment method from project files (docker-compose.yml / compose.yaml, systemd unit, pm2 ecosystem file, supervisord.conf)
- Reading project-level configuration overrides (
.cortex.yamlbuild_command/deploy_command) - Persisting successful inferences to
.cortex/redeploy-local.yaml, reused on the next run - Sequentially executing build + restart
- Reporting results
This skill is not responsible for:
- Hot-reload development servers (npm run dev, vite, next dev)—these are not a "local deployment environment"
- Remote deployment (SSH, cloud, Kubernetes)—use a deployment skill targeting remote infrastructure
- Database migrations—run separately before invoking this skill
- Secret injection—environment variables / secrets must already be available in the deployment
Handoff point: The skill completes when the run report shows exit code 0 for all steps. If any step fails, the skill reports failure and stops—it does not automatically retry or fix build errors.
Use Cases
- Rebuild after editing: The Agent has finished modifying code; the user wants the local service to reflect the changes without manually running build + restart commands
- Multi-stack projects: The project mixes compiled languages (Go, Rust) with Docker Compose deployment; the skill automatically detects both
- Scripted overrides: Via
.cortex.yamlto achieve CI-style reproducibility—the same commands every run, with no detection variance - Team-shared environment: Different team members have different local configurations; skills adapt based on detected signals rather than requiring the same toolchain
Detection Approach
This skilldoes notmaintain a hardcoded "file X → command Y" mapping. Build and deploy commands vary from project to project—two Go projects may have completely different build conventions (custom ldflags, output paths, cross-compilation targets), and a Node project's build script is not necessarily what's needed for deployment (it may require build:prod). The Agent's job is toscan the directory, read the relevant files, and infer the commands the project actually uses—then present those inferences to the user for confirmation.
Step 0: Prefer configuration overrides
Check the target directory for .cortex.yaml:
build_command: make release
deploy_command: docker compose up -d --build
If both fields are present, use them directly and skip steps 1–4 (no confirmation needed either). If only one field is present, run inference for the other and still perform the step 4 confirmation.
Step 0.5: Prefer the inference cache
After step 0 (overrides) and before step 1 (full scan), check the target directory for .cortex/redeploy-local.yaml. If valid, reuse the cached inference and jump directly to step 4 (present the inference for confirmation—the user still needs to confirm the report, annotated with (from cache, scanned YYYY-MM-DD; <N> signal files unchanged)). If it does not exist, is malformed, or is expired, fall back to step 1—never automatically delete an expired cache file; the next successful run will overwrite it.
.cortex.yaml overrides (step 0) take precedence over the cache. If step 0 set one of the commands, that command comes from .cortex.yaml, and the cache is used only for the other field (if its validation still passes).
Never commit .cortex/redeploy-local.yaml—it encodes host-local absolute paths and mtimes; add it (or the entire .cortex/ directory) to .gitignore. This file can be regenerated after the first successful run.
Cache file schema(.cortex/redeploy-local.yaml):
# Auto-generated by redeploy-local skill; do not edit by hand
skill_version: 3.2.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(such as scripts.build)—the command body itself is not duplicated, keeping the cache small and drift-free.signal_files records every file that wasreadduring inference (including cross-references), not just the files that are the source of the selected command.
Validation pseudologic—check in order; the first failure counts as a cache miss:
- The file exists and parses as YAML; the top-level required keys are present (
skill_version、project_path、written_at、build、deploy、signal_files)。 skill_versionstring equals theversionvalue in the current skill's frontmatter.project_pathequals the resolved absolute target directory (resolving symlinks viarealpath).now() - written_at< 30 days.signal_filesFor each entry in : the path still exists, and the current mtime equals the recorded mtime.build.commandanddeploy.commandare non-empty strings.
All pass → cache hit (jump to step 4 with an annotation). Any failure → cache miss; record cache invalid: <reason>; falling back to full scan and continue to step 1.
Step 1: Enumerate signal files
Traverse the target directory (depth 1, plus deploy/、.github/workflows/、docs/), and collect the presence of the following files:
- Build orchestration:
Makefile、Taskfile.yml、justfile、package.json、scripts/build* - Containers:
Dockerfile、docker-compose.yml、compose.yaml、compose.*.yaml - Language manifests:
go.mod、Cargo.toml、pom.xml、build.gradle*、*.csproj、*.sln、pyproject.toml、setup.py - Local deployment:
ecosystem.config.{js,cjs,json}、supervisord.conf、supervisor/*.conf、deploy/*.service、Procfile - Documentation that usually encodes commands:
README.md、CONTRIBUTING.md、docs/development*.md、docs/build*.md - CI as the authoritative reference:
.github/workflows/*.yml、.gitlab-ci.yml、Jenkinsfile—these usually pin down the team-approved build commands
Step 2: Read and infer the build command
For each signal found,read its contents(do not assume defaults):
Makefile: enumerate targets (grep -E '^[a-zA-Z_-]+:' Makefile); look forbuild、release、compile、dist、all. Read the body of candidate targets to confirm that it actually performs a build (rather than just echo). If multiple reasonable targets exist, list them and ask the user which to choose.package.json: read thescriptsobject. Look forbuild、build:prod、compile、bundle. If the Dockerfile copiesdist/, prefer the script that producesdist/. Detect the package manager via the lockfile (pnpm-lock.yaml→ pnpm;yarn.lock→ yarn;package-lock.json→ npm)。Taskfile.yml/justfile: enumerate tasks/recipes in the same way as with a Makefile.Dockerfile: check theCOPY、RUN、CMDlines to understand what artifacts the image expects and what builds happen inside the container. Use as across-reference(e.g.COPY dist/means the host build must producedist/) — the Dockerfile itself is almost never the host build command.- CI workflow: search for build-type jobs/steps; the authoritative command will appear verbatim. Use as across-referenceto validate other signals.
README.md/docs/: scan the "Build"/"Development"/"Getting Started" sections; look for code blocks containing shell commands.- Language manifest only(no Makefile, no scripts): only in this case fall back to the language's conventional command, and only use it after scanning the README/CI and confirming there is no project-specific override. Examples (as a last-resort default only): Go →
go build ./...;Rust →cargo build --release;Python →pip install -e .;Java/Maven →mvn package;Java/Gradle →./gradlew build;.NET →dotnet build。
Evidence priority(high to low):
- with explicit build semantics, the
Makefile/Taskfile/justfiletarget package.jsonscript (Node projects)- build steps documented in the CI workflow
- code block in the README's "Build" section
- language manifest fallback (
go build、cargo buildetc.) — use only when none of the above exist
Conflict resolution: if the Makefile says make build while CI says make release, show both and their sources to the user and ask which to run. Never choose silently.
Step 3: Read and infer the deploy command
Same principle — read deployment-related files, do not assume:
docker-compose.yml: list the defined services; the standard command isdocker compose up -d --build. If theMakefilehas adeploy/up/runtarget that wraps Compose, prefer the Makefile target — it includes project-specific flags (profiles, env files).ecosystem.config.{js,cjs}: read the file and extractapps[].name. Ifexec_mode: cluster, preferpm2 reload <name>(zero downtime); otherwisepm2 restart <name>。supervisord.conf: enumerate the[program:<name>]sections. If there are multiple programs, list them and ask which to restart (or the user confirmssupervisorctl restart all)。deploy/*.service: derive the unit name from the filename. Check file ownership and the current uid to determine whethersudo。- containing
deploy/restart/uptargets, theMakefile: these targets usually encode the project's real restart procedure (env vars, pre-hooks). Prefer over rawdocker compose/systemctl。 - Compose-only project(no other build orchestration): the build step is
docker compose up -d --buildsubsumed— do not run the build separately; mark the build step as "skipped — subsumed by deploy".
Evidence priority(high to low):
Makefile'sdeploy/restart/upGoal (project-specific wrapper)- Compose / pm2 / supervisord / systemd configuration files (the deployment medium itself)
- Code blocks in the "Deploy"/"Run" section of the README
Step 4: Present inference for confirmation
Before performing any operation, present a structured inference report to the user:
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
Continue execution only after user confirmation. Both commands come from .cortex.yaml skip confirmation when .
Behavior
Workflow (Checklist)
-
Confirm target directory
- Default: CWD
- If the user provided a path: verify that it exists (
test -d <path>) - Abort with a clear error when the directory does not exist
-
Read configuration overrides(if
.cortex.yamlexists)- Parse
build_commandand/ordeploy_command - Record which fields are overridden; skip heuristic detection of overridden fields
- Parse
-
Check inference cache(detection method step 0.5)
- If present, read
.cortex/redeploy-local.yaml - Validate according to the 6-step pseudologic
- Cache hit: skip steps 4–5, proceed to step 6 with a report annotated with
(from cache, scanned YYYY-MM-DD; <N> signal files unchanged)Cache hit: skip steps 4–5, proceed to step 6 with a report annotated with - Cache miss / malformed / stale: continue to step 4 (do not delete the file)
.cortex.yamlFields overridden in (step 2) are excluded from cache reuse; the cache is used only for the remaining fields (if validation still passes)
- If present, read
-
Infer build command(if not overridden and not cached)
- Execute detection method steps 1–2: enumerate signals, read files, cross-reference
- Record evidence sources and the selected command
- If it is a Compose-only project (no other build orchestration): mark "build skipped — subsumed by deploy"
- If there is no evidence: stop and ask the user
build_commandor have them fill in.cortex.yaml - If evidence conflicts: present the options to the user, do not silently choose
-
Infer deploy command(if not overridden and not cached)
- Execute detection method step 3: read deployment configuration files, prefer a Makefile wrapper
- Record the source and the selected command (including the extracted service/unit name)
- If there is no evidence: stop and ask the user
-
Present inference and confirm
- Present the structured inference report (according to detection method step 4); add a note if the cache is used
- Wait for user confirmation
- Skip confirmation only when both commands come from
.cortex.yamlSkip confirmation only when both commands come from - If the same directory + commands have already been confirmed in this conversation, skip repeated confirmation
-
Execute build(if not skipped)
- Run the build command from the target directory
- Stream output in real time (do not silently buffer)
- Use
{ start=$(date +%s); <cmd>; echo $(($(date +%s)-start))s; }record elapsed time - When exit code ≠ 0: show the last 20 lines, report the failure, stop—do not run deployment
-
Execute deployment restart
- Run the restart command
- Record elapsed time in the same way
- When exit code ≠ 0: show the last 20 lines, report partial status, stop
-
Health check
- Docker Compose:
docker compose ps— confirm all containers showUp - systemd:
systemctl is-active <unit> - pm2:
pm2 list | grep <name> - supervisord:
supervisorctl status <program> - Record the result; if the health check command is unavailable, warn (but do not mark the skill run as failed)
- Docker Compose:
-
Output the 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 after success
- Write only when the build exit code is 0 (or skipped due to absorption), the deploy exit code is 0, and the health check passes
- If both commands come from
.cortex.yaml, skip writing (the cache adds no value) - Skip writing on partial success (deploy 0 but unhealthy)—do not cache a known problematic configuration
- If
.cortex/does not exist, create it (mode 0755), then write.cortex/redeploy-local.yaml, with the# Auto-generated by redeploy-local skill; do not edit by handheader - Record the current
skill_version, the resolved absoluteproject_path、ISO 8601 UTCwritten_at, the selected command with evidence, and every signal file read during inference along with its current mtime - On write failure (permission denied, disk full, concurrent write): log
cache write skipped: <reason>and continue—the deployment has already succeeded; the cache is only an optimization layer
Interaction policy
- Ask only when detection is ambiguous or fails
.cortex.yamlDo not ask when two commands are provided- A single confirmation prompt covers build and deploy; do not prompt repeatedly within the same run
- If the same directory and commands have already been confirmed in this conversation, skip re-confirmation
Input & Output
Input requirements
- Target directory (explicit path or CWD)
- Optional: containing
build_commandand/ordeploy_commandof.cortex.yaml
Output contract
Provide:
- Inference report listing each selected command and its evidence source (file + section/line + cross-reference)
- Build output live stream (or a "skipped" notice for Compose-only projects)
- Deploy output live stream
- Health check result
- Run report table (command / exit code / duration for each step)
- After a fully successful run (build + deploy + health check all pass), the inference cache writes to
.cortex/redeploy-local.yaml
Restrictions
Hard Boundaries
- Neverrun
rm -rfor destructive cleanup commands without explicit user configuration - Neverassume the service name for systemd, pm2, or supervisord—extract it from the config file or ask
- Nevercontinue after a step fails—report and stop
- Neverrun a deploy restart without a preceding successful build (exit code 0), except for Compose-only projects (where deploy absorbs the build)
- Neverinject or modify the target deployment's environment variables
- Neveradd
sudowithout evidence (e.g., the systemd unit file is owned by root while the current uid differs); ask the user to confirm privilege escalation before running
Failure modes
| Failure case | Behavior |
|---|---|
| Directory not found | Abort immediately; show the exact path checked |
| No build evidence | Stop before building; ask the user build_command or have them fill in .cortex.yaml |
| No deployment evidence | Stop before deploying; ask the user deploy_command or have them fill in .cortex.yaml |
| Conflicting evidence (e.g., Makefile inconsistent with CI) | Stop; show both options and their sources to the user; let the user choose |
| Name/unit extraction failed | Stop; ask the user to explicitly provide the name |
| Build exit code ≠ 0 | Show the last 20 lines; stop; do not run deployment |
| Deployment exit code ≠ 0 | Show the last 20 lines; stop; report partial status |
| Health check failed | Warn; do not mark the skill run as failed; do not write to cache |
| Cache file format error or missing required key | Treat as cache miss; fall back to full scan; do not delete; overwrite after a successful run |
| Recorded signal file deleted / mtime changed | Cache miss; rescan |
Cache skill_version differs from current |
Cache miss; rescan |
| Cache write failed (permissions / disk full) | Log a warning; do not mark the skill run as failed—deployment already succeeded |
Self-Check Checklist
Core success criteria
- Directory confirmed: the target directory was verified to exist before any command
- Detection traceable: every inferred command is attributed to specific evidence (file + line/section, preferably with cross-references)—nothing fabricated
- Avoid double build: Compose-only projects skip the build step;
--buildhandled in the deployment command - User confirmed: commands were shown and confirmed before first execution (skipped only when both commands come from
.cortex.yaml) - Build succeeded: exit code 0 was confirmed before the deployment run (or the build step was reasonably skipped)
- Deployment succeeded: exit code 0 was confirmed before the health check
- Health check ran: status was checked and recorded (warn if unavailable; do not fail)
- Run report output: the table covers each step's command / exit code / duration
- Cache respected or refreshed: read before scanning
.cortex/redeploy-local.yaml; write only after a fully successful run (build + deployment + health check all passed)
Process quality checks
- No silent buffering: build and deployment output streams in real time, without buffering
- Durations recorded: wall-clock time was measured for each step
- Name/unit from source: pm2/supervisord/systemd names are extracted from configuration files, not hardcoded
- Minimal interaction: no redundant prompts; skip re-confirmation when already confirmed in this conversation
Examples
Example 1: Node.js app + Docker Compose
Directory contents:package.json、pnpm-lock.yaml、Dockerfile、docker-compose.yml、README.md
Inference process:
- Read
package.jsonscripts: foundbuild、build:prod、test、lint - Read
Dockerfile:COPY dist/ /app/—the image expectsdist/to exist - Read
package.jsonscripts.build:tsc && vite build --outDir dist—producesdist/, matching scripts.build:prodisNODE_ENV=production npm run build—also usable, butbuildis the default referenced in the README- Detect package manager:
pnpm-lock.yaml→ pnpm
Inference report shown to the 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 confirmation, 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
Example 2: Go service + Makefile wrapper
Directory contents:go.mod、Makefile、deploy/myapp.service、.github/workflows/ci.yml
Inference process:
- Read
Makefiletargets:build、test、lint、release、install - Read
Makefileinbuildtarget body:go build -ldflags "-X main.Version=$(VERSION)" -o bin/myapp ./cmd/myapp—project-specific ldflags and output path; do not default togo build ./... - Cross-reference
.github/workflows/ci.yml: the build step runsmake build—confirmed as authoritative - Read
deploy/myapp.servicefilename → unitmyapp; check file owner (root) vs current uid → may needsudo systemctl restart myapp
Inference 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
Example 3: Compose-only project (skip build step)
Directory contents:docker-compose.yml、Dockerfile(no Makefile, package.json, or language manifest in the root directory)
Inference process:
- No build orchestration found outside the Docker context
Dockerfileindocker compose up --buildperforms the build during- Therefore: skip the standalone build; the deploy command absorbs the build
Run 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
Example 4: Configuration override via .cortex.yaml
.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 shown.
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
Example 5: Build failure (edge case)
Scenario:pnpm run build exits with exit 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.
Example 6: Cache hit for a previously deployed project
Directory contents: same as Example 1 (package.json、pnpm-lock.yaml、Dockerfile、docker-compose.yml、README.md), plus the .cortex/redeploy-local.yaml。
Detection process:
- Step 0: no
.cortex.yamloverride - Step 0.5: read
.cortex/redeploy-local.yaml; validation passed (same skill version, sameproject_path, 2 days ago, all 4signal_filesmtimes unchanged) - Skip steps 1–3 (full scan); go directly to step 4 with a cache note
Inference report shown to the 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 confirmation, run report (same commands as 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 after success with refreshed written_at and current signal file mtimes.