
Taskfile Setup
- 1 installs
- 21 repo stars
- Updated August 5, 2026
- joaquimscosta/arkhe-claude-plugins
Installs Taskfile and scaffolds or audits Taskfile.yml with ecosystem-aware templates for Node, JVM/Gradle, Python/uv, and Docker.
About
Installs the Task runner and scaffolds or audits Taskfile.yml using ecosystem-aware templates, auto-detecting Node, JVM, Python, and Docker. A developer uses it to add or standardize developer task automation in a project.
- Auto-detects ecosystems for template selection
- Recommends single-file vs multi-file includes patterns
Taskfile Setup by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,173 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joaquimscosta/arkhe-claude-plugins --skill taskfile-setupAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 5, 2026 |
| Repository | joaquimscosta/arkhe-claude-plugins ↗ |
What it does
Installs Taskfile and scaffolds or audits Taskfile.yml with ecosystem-aware templates for Node, JVM/Gradle, Python/uv, and Docker.
Files
Taskfile Setup
Install Taskfile and scaffold or audit Taskfile.yml configurations with ecosystem-aware templates.
Pre-flight
Run the detection script to understand current state:
python3 ${CLAUDE_SKILL_DIR}/scripts/detect_taskfile.py <project-root>Decision Flow
Run detector
|
├── task_binary.installed = false → Install Task first
|
├── taskfile.exists = true → Phase 1: Audit
|
└── taskfile.exists = false → Phase 2: ScaffoldPhase 1: Audit (Existing Taskfile)
1. Summarize findings — show a status table:
| Component | Status | Detail |
|---|---|---|
| task binary | installed/missing | version, path |
| Taskfile | found/not found | path, variant |
| Tasks | N tasks | count, has includes |
| Ecosystems | N detected | list |
| dotenv | configured/missing | .env files found |
2. Present audit violations grouped by severity (ERROR > WARNING > INFO):
- Show rule ID, message, task name, line number, fix hint
- Violations include: missing version, no preconditions on deploy tasks, no sources/generates on build tasks, missing desc, too many tasks in single file, no dotenv, hard-coded paths
3. Use `AskUserQuestion` (multiSelect: true) — ask which violations to fix
4. Apply selected fixes — see WORKFLOW.md for per-rule fix strategies
5. Re-run detector to verify fixes were applied
Phase 2: Scaffold (No Taskfile)
1. Install `task` if missing — show commands based on os field:
- macOS:
brew install go-task - Linux:
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin - Verify:
task --version
2. Review detected ecosystems — show what was found
3. Choose Taskfile pattern — use AskUserQuestion:
- Single-file (flat namespace with
:separators) — recommended for < 15 tasks - Multi-file (root +
taskfiles/with includes) — recommended for 15+ tasks or monorepos
4. Choose task groups — use AskUserQuestion (multiSelect: true):
| Ecosystem | Available Groups |
|---|---|
| Node.js/pnpm | dev, build, test, lint, format, check |
| JVM/Gradle | build, test, lint, check, run |
| Python/uv | dev, test, lint, format, check |
| Docker | up, down, logs, build, clean |
| Generic | setup, check, clean |
5. Generate Taskfile.yml with selected groups — always include:
version: "3"desc:on every task- Top-level
vars:for directory paths dotenv:if.envfiles exist
6. Verify — re-run detector, then run task --list
Key Rules
- Never overwrite existing Taskfile.yml without asking. Offer merge/replace/skip.
- Detect first — skip steps already configured.
- Use `AskUserQuestion` for every decision. Do not assume user preferences.
- Always use `version: "3"` — the only supported non-deprecated schema.
- Add `desc:` to every task for
task --listdiscoverability. - Wrap ecosystem tools (pnpm, gradlew, uv) — do not duplicate their logic.
- Use variables for directory paths instead of hard-coding.
- dotenv only in root — Taskfile does not support dotenv in included files.
Existing Runner Awareness
The detector reports existing runners (Makefile, justfile, package.json scripts, gradlew). When scaffolding:
- Note which runners exist and what they cover
- Suggest Taskfile as a unifying layer that wraps existing tools
- Do not duplicate what existing runners already do well
References
- Workflow: See WORKFLOW.md for detailed per-step flows and templates
- Examples: See EXAMPLES.md for example setup and audit sessions
- Troubleshooting: See TROUBLESHOOTING.md for common issues
- Detection Script: See scripts/detect_taskfile.py for detection logic
Examples: Taskfile Setup
Example 1: Greenfield Node.js Project (No Taskfile)
Detection Output
{
"task_binary": {"installed": true, "path": "/opt/homebrew/bin/task", "version": "3.40.1"},
"taskfile": {"exists": false},
"ecosystems": [
{"ecosystem": "node", "package_manager": "pnpm", "root": "."}
],
"env_files": [".env.local"],
"existing_runners": {"makefile": false, "justfile": false, "package_json_scripts": true, "gradlew": false},
"os": "macos"
}Scaffold Session
Status: Task installed, no Taskfile found, Node.js/pnpm detected.
User chooses: Single-file pattern, task groups: dev, build, test, lint, format, check, setup, clean.
Generated Taskfile.yml
version: "3"
dotenv: [".env.local", ".env"]
tasks:
setup:
desc: Install dependencies
run: once
cmds:
- pnpm install
dev:
desc: Start development server
deps: [setup]
cmds:
- pnpm dev
build:
desc: Build for production
deps: [setup]
cmds:
- pnpm build
test:
desc: Run tests
deps: [setup]
cmds:
- pnpm test
lint:
desc: Run ESLint
deps: [setup]
cmds:
- pnpm lint
format:
desc: Format code with Prettier
cmds:
- pnpm prettier --write .
check:
desc: Type-check with TypeScript
cmds:
- npx tsc --noEmit
clean:
desc: Clean build artifacts
cmds:
- rm -rf dist .next node_modules/.cache---
Example 2: Monorepo with JVM + Node.js (Multi-file)
Detection Output
{
"task_binary": {"installed": true, "path": "/opt/homebrew/bin/task", "version": "3.40.1"},
"taskfile": {"exists": false},
"ecosystems": [
{"ecosystem": "node", "package_manager": "pnpm", "root": "apps/web"},
{"ecosystem": "jvm", "build_tool": "gradle-kotlin", "root": "apps/api"},
{"ecosystem": "docker", "has_dockerfile": true, "has_compose": true, "root": "."}
],
"env_files": [],
"existing_runners": {"makefile": false, "justfile": false, "package_json_scripts": true, "gradlew": true},
"os": "macos"
}Scaffold Session
Status: Task installed, no Taskfile, 3 ecosystems detected (monorepo).
User chooses: Multi-file pattern (recommended for monorepo).
Generated Files
Taskfile.yml (root):
version: "3"
includes:
api:
taskfile: ./taskfiles/api.yml
dir: ./apps/api
web:
taskfile: ./taskfiles/web.yml
dir: ./apps/web
docker:
taskfile: ./taskfiles/docker.yml
tasks:
setup:
desc: Full project setup
cmds:
- task: api:setup
- task: web:setup
dev:
desc: Start all services in parallel
deps:
- api:dev
- web:dev
test:
desc: Run all tests
cmds:
- task: api:test
- task: web:test
lint:
desc: Run all linters
cmds:
- task: api:lint
- task: web:lint
clean:
desc: Clean all build artifacts
cmds:
- task: api:clean
- task: web:cleantaskfiles/api.yml:
version: "3"
tasks:
setup:
desc: API setup
cmds:
- echo "API dependencies managed by Gradle"
dev:
desc: Start API server
cmds:
- ./gradlew bootRun
test:
desc: Run API tests
cmds:
- ./gradlew test
lint:
desc: Run Kotlin linter
cmds:
- ./gradlew ktlintCheck
clean:
desc: Clean API build artifacts
cmds:
- ./gradlew cleantaskfiles/web.yml:
version: "3"
tasks:
setup:
desc: Install web dependencies
run: once
cmds:
- pnpm install
dev:
desc: Start web dev server
deps: [setup]
cmds:
- pnpm dev
test:
desc: Run web tests
deps: [setup]
cmds:
- pnpm test
lint:
desc: Run ESLint
deps: [setup]
cmds:
- pnpm lint
clean:
desc: Clean web build artifacts
cmds:
- rm -rf .next node_modules/.cachetaskfiles/docker.yml:
version: "3"
tasks:
up:
desc: Start containers
cmds:
- docker compose up -d
down:
desc: Stop containers
cmds:
- docker compose down
logs:
desc: Follow container logs
cmds:
- docker compose logs -f {{.CLI_ARGS}}
clean:
desc: Stop and remove volumes
cmds:
- docker compose down -v --remove-orphans---
Example 3: Existing Taskfile with Violations
Detection Output with Audit
{
"task_binary": {"installed": true, "path": "/opt/homebrew/bin/task", "version": "3.40.1"},
"taskfile": {
"exists": true, "path": "Taskfile.yml", "version": "3",
"task_count": 12, "has_includes": false, "include_count": 0, "has_dotenv": false,
"tasks": [
{"name": "build", "has_desc": true, "has_preconditions": false, "has_sources": false, "has_generates": false, "has_deps": true, "has_status": false, "line": 8},
{"name": "deploy", "has_desc": false, "has_preconditions": false, "has_sources": false, "has_generates": false, "has_deps": false, "has_status": false, "line": 15},
{"name": "test", "has_desc": false, "has_preconditions": false, "has_sources": false, "has_generates": false, "has_deps": false, "has_status": false, "line": 22}
]
},
"env_files": [".env", ".env.local"],
"audit": {
"violations": [
{"rule": "TF002", "severity": "WARNING", "message": "Task 'deploy' has no preconditions (safety-critical task)", "task": "deploy", "line": 15},
{"rule": "TF003", "severity": "WARNING", "message": "Task 'build' has no sources/generates (no up-to-date checks)", "task": "build", "line": 8},
{"rule": "TF005", "severity": "WARNING", "message": "Task 'deploy' is missing a 'desc:' field", "task": "deploy", "line": 15},
{"rule": "TF005", "severity": "WARNING", "message": "Task 'test' is missing a 'desc:' field", "task": "test", "line": 22},
{"rule": "TF006", "severity": "INFO", "message": "No 'dotenv:' config but 2 .env file(s) found"}
],
"summary": {"total": 5, "errors": 0, "warnings": 4, "info": 1}
}
}Audit Report
## Audit Results — 5 violations (4 warnings, 1 info)
### WARNINGS
| Rule | Message | Task | Line | Fix |
|-------|--------------------------------------|--------|------|----------------------------|
| TF002 | No preconditions on safety task | deploy | 15 | Add preconditions block |
| TF003 | No sources/generates on build task | build | 8 | Add sources/generates |
| TF005 | Missing desc field | deploy | 15 | Add desc |
| TF005 | Missing desc field | test | 22 | Add desc |
### INFO
| Rule | Message | Fix |
|-------|--------------------------------------|--------------------------------------|
| TF006 | No dotenv, 2 .env files found | Add dotenv: [".env.local", ".env"] |User selects: TF005 (both), TF006 to fix. Skips TF002 and TF003 for now.
After Fixes
- Added
desc: Deploy applicationto deploy task - Added
desc: Run teststo test task - Added
dotenv: [".env.local", ".env"]at top level - Re-ran detector: 2 violations remaining (TF002, TF003)
---
Example 4: Nosilha-Style Full-Stack Project
Context
Full-stack project with API (Spring Boot/Gradle), web (Next.js/pnpm), Docker Compose for PostgreSQL, and infrastructure (Terraform).
Detection Output
{
"taskfile": {"exists": false},
"ecosystems": [
{"ecosystem": "node", "package_manager": "pnpm", "root": "apps/web"},
{"ecosystem": "jvm", "build_tool": "gradle-kotlin", "root": "apps/api"},
{"ecosystem": "docker", "has_dockerfile": true, "has_compose": true, "root": "."}
],
"env_files": [],
"existing_runners": {"makefile": false, "justfile": false, "package_json_scripts": true, "gradlew": true}
}User Chooses Single-File Pattern
Generated Taskfile.yml
version: "3"
vars:
API_DIR: apps/api
WEB_DIR: apps/web
DOCKER_COMPOSE: infrastructure/docker/docker-compose.yml
tasks:
check:
desc: Check prerequisites — detect missing tools
silent: true
cmds:
- |
missing=0
check_tool() {
if command -v "$1" &> /dev/null; then
printf " OK %-10s %s\n" "$1" "$(eval "$3")"
else
printf " MISSING %-10s install with: %s\n" "$1" "$2"
missing=$((missing + 1))
fi
}
echo "Checking prerequisites..."
echo ""
check_tool "docker" "brew install --cask docker" "docker --version | head -1"
check_tool "node" "nvm install --lts" "node --version"
check_tool "pnpm" "npm install -g pnpm" "pnpm --version"
check_tool "java" "sdk install java" "java --version 2>&1 | head -1"
echo ""
if [ "$missing" -gt 0 ]; then
echo "$missing tool(s) missing."
exit 1
else
echo "All prerequisites installed."
fi
setup:
desc: Full-stack setup
cmds:
- task: setup:api
- task: setup:web
setup:api:
desc: API setup — copy env template if missing
cmds:
- |
if [ ! -f {{.API_DIR}}/.env.local ]; then
cp {{.API_DIR}}/.env.local.example {{.API_DIR}}/.env.local
echo "Created {{.API_DIR}}/.env.local from template"
else
echo "{{.API_DIR}}/.env.local already exists, skipping"
fi
setup:web:
desc: Web setup — install dependencies
dir: "{{.WEB_DIR}}"
cmds:
- pnpm install
dev:
desc: Start API + web in parallel
deps:
- dev:api
- dev:web
dev:api:
desc: Start API server (auto-starts database)
dir: "{{.API_DIR}}"
cmds:
- ./gradlew bootRun --args='--spring.profiles.active=local'
dev:web:
desc: Start web dev server
dir: "{{.WEB_DIR}}"
cmds:
- pnpm dev
dev:db:
desc: Start database only
cmds:
- docker compose -f {{.DOCKER_COMPOSE}} up -d
test:
desc: Run all tests
cmds:
- task: test:api
- task: test:web
test:api:
desc: Run API tests
dir: "{{.API_DIR}}"
cmds:
- ./gradlew test
test:web:
desc: Run web tests
dir: "{{.WEB_DIR}}"
cmds:
- pnpm test
lint:
desc: Run all linters
cmds:
- task: lint:api
- task: lint:web
lint:api:
desc: Run Kotlin linter
dir: "{{.API_DIR}}"
cmds:
- ./gradlew ktlintCheck
lint:web:
desc: Run ESLint
dir: "{{.WEB_DIR}}"
cmds:
- pnpm lint
stop:
desc: Stop database container
cmds:
- docker compose -f {{.DOCKER_COMPOSE}} down
clean:
desc: Clean build artifacts
cmds:
- task: clean:api
- task: clean:web
clean:api:
desc: Clean API build artifacts
dir: "{{.API_DIR}}"
cmds:
- ./gradlew clean
clean:web:
desc: Clean web build artifacts
dir: "{{.WEB_DIR}}"
cmds:
- rm -rf .next node_modules/.cache---
Example 5: Already-Perfect Taskfile
Detection Output
{
"task_binary": {"installed": true, "path": "/opt/homebrew/bin/task", "version": "3.40.1"},
"taskfile": {
"exists": true, "path": "Taskfile.yml", "version": "3",
"task_count": 15, "has_includes": false, "include_count": 0, "has_dotenv": true,
"tasks": [
{"name": "setup", "has_desc": true, "has_preconditions": false, "has_sources": false, "has_generates": false, "has_deps": false, "has_status": false, "line": 8},
{"name": "dev", "has_desc": true, "has_preconditions": false, "has_sources": false, "has_generates": false, "has_deps": true, "has_status": false, "line": 14},
{"name": "test", "has_desc": true, "has_preconditions": false, "has_sources": false, "has_generates": false, "has_deps": false, "has_status": false, "line": 22},
{"name": "build", "has_desc": true, "has_preconditions": false, "has_sources": true, "has_generates": true, "has_deps": false, "has_status": false, "line": 30}
]
},
"audit": {
"violations": [],
"summary": {"total": 0, "errors": 0, "warnings": 0, "info": 0}
}
}Result
Taskfile audit complete — no violations found.
Your Taskfile.yml follows best practices:
- version: '3' declared
- All 15 tasks have desc fields
- dotenv configured
- Build tasks have sources/generates
No changes needed.#!/usr/bin/env python3
"""
Detect Taskfile installation and audit Taskfile.yml configurations.
Outputs JSON report of installed tools, existing Taskfile, detected
ecosystems, and audit violations. Used by taskfile-setup skill.
Uses only standard library (no external dependencies).
Usage:
python3 detect_taskfile.py [project_root] [--no-audit]
"""
import argparse
import json
import os
import platform
import re
import shutil
import subprocess
import sys
from pathlib import Path
# ---------------------------------------------------------------------------
# Helpers (same pattern as detect_sops.py)
# ---------------------------------------------------------------------------
def run_cmd(cmd):
"""Run a command and return stdout, or None on failure."""
try:
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=10
)
if result.returncode == 0:
return result.stdout.strip()
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
pass
return None
def detect_os():
"""Detect operating system for install instructions."""
system = platform.system().lower()
if system == "darwin":
return "macos"
elif system == "linux":
return "linux"
return system
# ---------------------------------------------------------------------------
# Tool detection
# ---------------------------------------------------------------------------
def detect_tool(name):
"""Check if a tool is installed and get its version."""
path = shutil.which(name)
if not path:
return {"installed": False}
info = {"installed": True, "path": path}
version_output = run_cmd([name, "--version"])
if version_output:
match = re.search(r"(\d+\.\d+\.\d+)", version_output)
if match:
info["version"] = match.group(1)
else:
info["version_raw"] = version_output.splitlines()[0]
return info
# ---------------------------------------------------------------------------
# Taskfile detection + parsing
# ---------------------------------------------------------------------------
TASKFILE_VARIANTS = [
"Taskfile.yml", "taskfile.yml",
"Taskfile.yaml", "taskfile.yaml",
"Taskfile.dist.yml", "Taskfile.dist.yaml",
]
def _parse_taskfile(content):
"""Best-effort line-level YAML parser for Taskfile.yml.
Returns a dict with top-level keys and parsed task metadata.
No PyYAML required — uses indentation tracking.
"""
result = {
"version": None,
"has_includes": False,
"include_count": 0,
"has_dotenv": False,
"tasks": [],
}
lines = content.splitlines()
# Top-level key detection
current_section = None
current_task = None
task_indent = None
for i, line in enumerate(lines):
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
# Detect indentation level
indent = len(line) - len(line.lstrip())
# Top-level keys (indent == 0)
if indent == 0 and ":" in stripped:
key = stripped.split(":")[0].strip()
value = stripped.split(":", 1)[1].strip() if ":" in stripped else ""
if key == "version":
# Extract version string (remove quotes)
result["version"] = value.strip("'\"")
current_section = None
elif key == "includes":
result["has_includes"] = True
current_section = "includes"
elif key == "dotenv":
result["has_dotenv"] = True
current_section = None
elif key == "tasks":
current_section = "tasks"
else:
current_section = key
# Reset task context when changing sections
if key != "tasks" and current_section != "tasks":
current_task = None
task_indent = None
continue
# Count includes entries (indent == 2 under includes)
# Handles both expanded form (key:) and short form (key: ./path)
if current_section == "includes" and indent == 2 and ":" in stripped:
result["include_count"] += 1
continue
# Parse tasks section
if current_section == "tasks":
# Task name detection: indent == 2, ends with ':'
if indent == 2 and stripped.endswith(":") and not stripped.startswith("-"):
task_name = stripped[:-1].strip()
current_task = {
"name": task_name,
"has_desc": False,
"has_preconditions": False,
"has_sources": False,
"has_generates": False,
"has_deps": False,
"has_status": False,
"line": i + 1,
}
task_indent = 2
result["tasks"].append(current_task)
continue
# Task properties (indent == 4 under task)
if current_task and indent == 4 and ":" in stripped:
prop = stripped.split(":")[0].strip()
if prop == "desc":
current_task["has_desc"] = True
elif prop == "preconditions":
current_task["has_preconditions"] = True
elif prop == "sources":
current_task["has_sources"] = True
elif prop == "generates":
current_task["has_generates"] = True
elif prop == "deps":
current_task["has_deps"] = True
elif prop == "status":
current_task["has_status"] = True
return result
def detect_taskfile(project_root):
"""Check for Taskfile.yml (or variants) and parse it."""
root = Path(project_root)
for variant in TASKFILE_VARIANTS:
taskfile_path = root / variant
if taskfile_path.exists():
result = {"exists": True, "path": variant}
try:
content = taskfile_path.read_text()
parsed = _parse_taskfile(content)
result["version"] = parsed["version"]
result["task_count"] = len(parsed["tasks"])
result["has_includes"] = parsed["has_includes"]
result["include_count"] = parsed["include_count"]
result["has_dotenv"] = parsed["has_dotenv"]
result["tasks"] = parsed["tasks"]
except OSError:
result["read_error"] = True
return result
return {"exists": False}
# ---------------------------------------------------------------------------
# Ecosystem detection
# ---------------------------------------------------------------------------
def detect_ecosystems(project_root):
"""Detect project ecosystems from marker files."""
root = Path(project_root)
ecosystems = []
# Check root, one level deep, and two levels deep for monorepo patterns
dirs_to_check = [root]
monorepo_parents = {"apps", "packages", "libs", "services", "modules"}
try:
for entry in sorted(root.iterdir()):
if entry.is_dir() and not entry.name.startswith("."):
dirs_to_check.append(entry)
# Two levels deep for known monorepo container dirs
if entry.name in monorepo_parents:
try:
for sub in sorted(entry.iterdir()):
if sub.is_dir() and not sub.name.startswith("."):
dirs_to_check.append(sub)
except OSError:
pass
except OSError:
pass
seen = set()
for check_dir in dirs_to_check:
rel = str(check_dir.relative_to(root)) if check_dir != root else "."
# Node.js
if (check_dir / "package.json").exists() and ("node", rel) not in seen:
eco = {"ecosystem": "node", "root": rel}
if (check_dir / "pnpm-lock.yaml").exists():
eco["package_manager"] = "pnpm"
elif (check_dir / "yarn.lock").exists():
eco["package_manager"] = "yarn"
elif (check_dir / "package-lock.json").exists():
eco["package_manager"] = "npm"
else:
eco["package_manager"] = "unknown"
ecosystems.append(eco)
seen.add(("node", rel))
# JVM
if (check_dir / "build.gradle.kts").exists() and ("jvm", rel) not in seen:
ecosystems.append({
"ecosystem": "jvm", "build_tool": "gradle-kotlin", "root": rel
})
seen.add(("jvm", rel))
elif (check_dir / "build.gradle").exists() and ("jvm", rel) not in seen:
ecosystems.append({
"ecosystem": "jvm", "build_tool": "gradle-groovy", "root": rel
})
seen.add(("jvm", rel))
elif (check_dir / "pom.xml").exists() and ("jvm", rel) not in seen:
ecosystems.append({
"ecosystem": "jvm", "build_tool": "maven", "root": rel
})
seen.add(("jvm", rel))
# Python
if (check_dir / "pyproject.toml").exists() and ("python", rel) not in seen:
eco = {"ecosystem": "python", "root": rel}
if (check_dir / "uv.lock").exists():
eco["package_manager"] = "uv"
elif (check_dir / "Pipfile.lock").exists():
eco["package_manager"] = "pipenv"
elif (check_dir / "poetry.lock").exists():
eco["package_manager"] = "poetry"
else:
eco["package_manager"] = "pip"
ecosystems.append(eco)
seen.add(("python", rel))
elif (check_dir / "requirements.txt").exists() and ("python", rel) not in seen:
ecosystems.append({
"ecosystem": "python", "package_manager": "pip", "root": rel
})
seen.add(("python", rel))
# Docker (check root only)
compose_files = [
"docker-compose.yml", "docker-compose.yaml",
"compose.yml", "compose.yaml",
]
has_dockerfile = (root / "Dockerfile").exists()
has_compose = any((root / f).exists() for f in compose_files)
# Also check infrastructure/ and docker/ subdirs for compose files
for subdir in ["infrastructure", "infrastructure/docker", "docker"]:
sub = root / subdir
if sub.is_dir():
if any((sub / f).exists() for f in compose_files):
has_compose = True
if (sub / "Dockerfile").exists():
has_dockerfile = True
if has_dockerfile or has_compose:
ecosystems.append({
"ecosystem": "docker",
"has_dockerfile": has_dockerfile,
"has_compose": has_compose,
"root": ".",
})
return ecosystems
# ---------------------------------------------------------------------------
# Environment & runner detection
# ---------------------------------------------------------------------------
def detect_env_files(project_root):
"""Find .env* files in the project root."""
root = Path(project_root)
env_files = []
try:
for f in sorted(root.iterdir()):
if f.is_file() and f.name.startswith(".env") and f.name != ".env.example":
env_files.append(f.name)
except OSError:
pass
return env_files
def detect_existing_runners(project_root):
"""Check for existing task runner tools."""
root = Path(project_root)
has_scripts = False
pkg = root / "package.json"
if pkg.exists():
try:
content = pkg.read_text()
has_scripts = '"scripts"' in content
except OSError:
pass
return {
"makefile": (root / "Makefile").exists(),
"justfile": (root / "justfile").exists() or (root / "Justfile").exists(),
"package_json_scripts": has_scripts,
"gradlew": (root / "gradlew").exists(),
}
# ---------------------------------------------------------------------------
# Audit rules
# ---------------------------------------------------------------------------
DEPLOY_TASK_PATTERNS = re.compile(
r"^(deploy|publish|release|push|promote)", re.IGNORECASE
)
BUILD_TASK_PATTERNS = re.compile(
r"^(build|compile|package|assemble)", re.IGNORECASE
)
SEQUENTIAL_TASK_PATTERNS = re.compile(
r"^(ci|pipeline|all|full|release|deploy)", re.IGNORECASE
)
ABSOLUTE_PATH_PATTERN = re.compile(r"(?:^|\s)/(?:usr|home|opt|var|etc|Users)/")
def audit_taskfile(project_root, taskfile_info, env_files):
"""Run audit rules against parsed Taskfile data.
Returns a list of violation dicts.
"""
violations = []
if not taskfile_info.get("exists"):
return violations
# TF001: Missing version: '3'
version = taskfile_info.get("version")
if not version or not version.startswith("3"):
violations.append({
"rule": "TF001",
"severity": "ERROR",
"message": "Missing or non-'3' version declaration",
"fix_hint": "Add 'version: \"3\"' at the top of your Taskfile",
})
tasks = taskfile_info.get("tasks", [])
for task in tasks:
name = task["name"]
line = task["line"]
# TF002: No preconditions on deploy/publish tasks
if DEPLOY_TASK_PATTERNS.match(name) and not task["has_preconditions"]:
violations.append({
"rule": "TF002",
"severity": "WARNING",
"message": f"Task '{name}' has no preconditions (safety-critical task)",
"task": name,
"line": line,
"fix_hint": "Add preconditions to validate environment, credentials, or branch",
})
# TF003: No sources/generates on build tasks
if BUILD_TASK_PATTERNS.match(name) and not task["has_sources"] and not task["has_generates"]:
violations.append({
"rule": "TF003",
"severity": "WARNING",
"message": f"Task '{name}' has no sources/generates (no up-to-date checks)",
"task": name,
"line": line,
"fix_hint": "Add sources/generates for incremental build caching",
})
# TF005: Missing desc
if not task["has_desc"]:
violations.append({
"rule": "TF005",
"severity": "WARNING",
"message": f"Task '{name}' is missing a 'desc:' field",
"task": name,
"line": line,
"fix_hint": f"Add 'desc: ...' to improve 'task --list' discoverability",
})
# TF008: deps used where sequential ordering is likely intended
if task["has_deps"] and SEQUENTIAL_TASK_PATTERNS.match(name):
violations.append({
"rule": "TF008",
"severity": "INFO",
"message": f"Task '{name}' uses deps (parallel) — consider cmds with task: for sequential ordering",
"task": name,
"line": line,
"fix_hint": "Replace deps: [...] with cmds: [{task: x}, {task: y}] if order matters",
})
# TF004: Too many tasks in single file
task_count = taskfile_info.get("task_count", 0)
has_includes = taskfile_info.get("has_includes", False)
if task_count > 20 and not has_includes:
violations.append({
"rule": "TF004",
"severity": "INFO",
"message": f"Single file with {task_count} tasks (consider splitting via includes)",
"fix_hint": "Split into taskfiles/ directory with per-concern Taskfile includes",
})
# TF006: No dotenv when .env files exist
has_dotenv = taskfile_info.get("has_dotenv", False)
if env_files and not has_dotenv:
violations.append({
"rule": "TF006",
"severity": "INFO",
"message": f"No 'dotenv:' config but {len(env_files)} .env file(s) found",
"fix_hint": "Add 'dotenv: [\".env.local\", \".env\"]' for automatic env loading",
})
# TF007: Requires reading raw file content
taskfile_path = Path(project_root) / taskfile_info["path"]
try:
content = taskfile_path.read_text()
_audit_file_content(content, violations)
except OSError:
pass
# Build summary
summary = {"total": len(violations), "errors": 0, "warnings": 0, "info": 0}
for v in violations:
sev = v["severity"].lower()
if sev in summary:
summary[sev] += 1
return {"violations": violations, "summary": summary}
def _audit_file_content(content, violations):
"""Audit rules that require raw file content."""
lines = content.splitlines()
for i, line in enumerate(lines):
# TF007: Hard-coded absolute paths
if ABSOLUTE_PATH_PATTERN.search(line) and not line.strip().startswith("#"):
violations.append({
"rule": "TF007",
"severity": "WARNING",
"message": f"Hard-coded absolute path on line {i + 1}",
"line": i + 1,
"fix_hint": "Use variables ({{.ROOT_DIR}}) or relative paths instead",
})
# ---------------------------------------------------------------------------
# Main detection
# ---------------------------------------------------------------------------
def detect(project_root, audit=True):
"""Run all detection checks and return structured results."""
taskfile_info = detect_taskfile(project_root)
env_files = detect_env_files(project_root)
result = {
"task_binary": detect_tool("task"),
"taskfile": taskfile_info,
"ecosystems": detect_ecosystems(project_root),
"env_files": env_files,
"existing_runners": detect_existing_runners(project_root),
"os": detect_os(),
}
if audit and taskfile_info.get("exists"):
result["audit"] = audit_taskfile(project_root, taskfile_info, env_files)
return result
def main():
parser = argparse.ArgumentParser(
description="Detect Taskfile installation and audit Taskfile.yml configurations"
)
parser.add_argument(
"project_root", nargs="?", default=os.getcwd(),
help="Path to the project root directory"
)
parser.add_argument(
"--no-audit", action="store_true",
help="Skip audit checks (detection only)"
)
args = parser.parse_args()
root = Path(args.project_root).resolve()
if not root.is_dir():
json.dump(
{"error": "not_a_directory", "path": str(root)},
sys.stdout, indent=2,
)
sys.exit(1)
result = detect(str(root), audit=not args.no_audit)
json.dump(result, sys.stdout, indent=2)
print()
if __name__ == "__main__":
main()
Troubleshooting: Taskfile Setup
Installation Issues
brew install task installs Taskwarrior instead of Taskfile
Symptom: Running task shows a TODO manager instead of a task runner.
Cause: brew install task installs Taskwarrior, not Taskfile.
Fix: Uninstall and reinstall with the correct formula:
brew uninstall task
brew install go-taskVerify: task --version should show something like Task version: v3.x.x.
task command not found after install
Symptom: command not found: task after running install script.
Cause: The binary was installed to a directory not in your PATH.
Fix: 1. Check where it was installed: ls ~/.local/bin/task or ls /usr/local/bin/task 2. Add to PATH in your shell profile:
# In ~/.zshrc or ~/.bashrc
export PATH="$HOME/.local/bin:$PATH"3. Restart your terminal or run source ~/.zshrc
Version too old for version: '3'
Symptom: task: Failed to parse error when running tasks.
Cause: Task version 2.x does not support version: '3' schema.
Fix: Upgrade to Task 3.x:
# macOS
brew upgrade go-task
# Linux (install script)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin---
Detection Issues
Ecosystems not detected
Symptom: Detector returns empty ecosystems array despite project files existing.
Causes:
- Running from wrong directory — pass explicit project root
- Deeply nested project structure (detector only checks root + one level deep)
Fix:
# Specify the project root explicitly
python3 ${CLAUDE_SKILL_DIR}/scripts/detect_taskfile.py /path/to/projectAudit reports false positives
TF008 (deps for sequential): The detector flags deps: usage as INFO-level. However, deps: is intentionally parallel in many cases (e.g., dev: running API + web simultaneously). This is an informational hint, not a bug.
TF005 (missing desc): Internal tasks (prefixed with underscore like _helper) conventionally omit desc: to hide from task --list. The detector currently flags these. Skip these violations when reviewing.
TF007 (absolute paths): Comments containing file paths may trigger this rule. The detector skips comment lines starting with #, but inline comments are not filtered.
YAML parsing misses tasks
Symptom: Detector reports fewer tasks than expected.
Cause: The stdlib-only parser uses line-level indentation tracking. Complex YAML constructs may not be recognized:
- YAML anchors and aliases (
<<: *defaults) - Flow-style mappings (
{key: value}) - Multi-line strings with unusual indentation
Workaround: The audit results are best-effort. Review the full Taskfile manually for any missed tasks. The detector's primary purpose is ecosystem detection and common-pattern auditing.
---
Scaffold Issues
Generated Taskfile has wrong tool paths
Symptom: task dev fails because pnpm or ./gradlew is not at the expected location.
Cause: The scaffold used default directory names that don't match your project structure.
Fix: Edit the vars: section in your Taskfile.yml:
vars:
API_DIR: backend # Was: apps/api
WEB_DIR: frontend # Was: apps/webInclude files not found
Symptom: task: Taskfile "taskfiles/api.yml" not found.
Cause: The included Taskfile path is relative to the root Taskfile's location.
Fix: Ensure paths are correct relative to the root Taskfile:
includes:
api:
taskfile: ./taskfiles/api.yml # Relative to root Taskfile
dir: ./apps/api # Working directory for tasksdotenv not loading in included Taskfiles
Symptom: Environment variables from .env are not available in included Taskfile tasks.
Cause: This is a known Taskfile limitation — dotenv: declarations only work in the root Taskfile. They cannot be declared in included Taskfiles.
Fix: Define dotenv: only at the root level:
# Root Taskfile.yml — this works
version: "3"
dotenv: [".env.local", ".env"]
includes:
api:
taskfile: ./taskfiles/api.yml
# env vars from dotenv ARE available in included tasks---
Runtime Issues
task: command not found in CI
Symptom: CI pipeline fails because task is not installed.
Cause: Task is not installed in the CI environment by default.
Fix for GitHub Actions:
- name: Install Task
uses: go-task/setup-task@v1
with:
version: '3.x'Fix for other CI: Add install step:
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/binTasks run multiple times
Symptom: A setup task runs every time even though it only needs to run once.
Cause: Missing run: once on idempotent tasks.
Fix: Add run: once to tasks that should only execute once per invocation:
tasks:
setup:
run: once
desc: Install dependencies
cmds:
- pnpm installVariables not available in included Taskfiles
Symptom: {{.MY_VAR}} is empty in an included Taskfile's tasks.
Cause: Variables from the root Taskfile don't automatically propagate to includes.
Fix: Pass variables explicitly:
includes:
api:
taskfile: ./taskfiles/api.yml
dir: ./apps/api
vars:
PROFILE: "{{.PROFILE}}"
ENV: "{{.ENV}}"Or define variables in the included Taskfile itself.
Workflow: Taskfile Setup
Decision Flow
USER invokes taskfile-setup
|
v
Run detect_taskfile.py on project root
|
├── task_binary.installed = false?
│ └── Go to "Install Task"
|
├── taskfile.exists = true?
│ └── Go to "Phase 1: Audit"
│
└── taskfile.exists = false?
└── Go to "Phase 2: Scaffold"---
Install Task
Show commands based on os field from detector:
| OS | Command |
|---|---|
| macOS | brew install go-task |
| Linux (binary) | sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin |
| Linux (apt) | sudo snap install task --classic |
Important: On macOS, brew install task may install taskwarrior instead. Always use brew install go-task.
After install, verify:
task --versionRe-run detector to confirm installation.
---
Phase 1: Audit
Step 1: Present Status Table
Display detection results as a formatted table:
| Component | Status | Detail |
|-----------------|------------|---------------------------------|
| task binary | installed | v3.40.1 at /opt/homebrew/bin/task |
| Taskfile | found | Taskfile.yml (version: 3) |
| Tasks | 18 tasks | no includes |
| Ecosystems | 3 detected | node (pnpm), jvm (gradle), docker |
| dotenv | not config | 2 .env files found |
| Existing runners| 2 found | package.json scripts, gradlew |Step 2: Present Violations
Group by severity (ERROR first, then WARNING, then INFO):
## Audit Results
### ERRORS
| Rule | Message | Task | Line | Fix |
|------|---------|------|------|-----|
| TF001 | Missing version: '3' | — | — | Add version declaration |
### WARNINGS
| Rule | Message | Task | Line | Fix |
|------|---------|------|------|-----|
| TF002 | No preconditions | deploy | 42 | Add preconditions |
| TF005 | Missing desc | build | 12 | Add desc field |
### INFO
| Rule | Message | Task | Line | Fix |
|------|---------|------|------|-----|
| TF006 | No dotenv config | — | — | Add dotenv declaration |Step 3: Offer Fixes
Use AskUserQuestion (multiSelect: true) listing each violation with its fix. User selects which to apply.
Step 4: Per-Rule Fix Strategies
TF001 — Missing version: '3' Add at the top of the file:
version: "3"TF002 — No preconditions on deploy/publish tasks Add preconditions block:
tasks:
deploy:
desc: Deploy to production
preconditions:
- sh: "test -n \"$ENV\""
msg: "ENV must be set (staging or production)"
- sh: "git diff --quiet"
msg: "Uncommitted changes — commit or stash first"
cmds:
- # existing commandsTF003 — No sources/generates on build tasks Add sources and generates:
tasks:
build:
desc: Build the application
sources:
- "src/**/*"
generates:
- "dist/**/*"
cmds:
- # existing commandsTF004 — Too many tasks in single file Propose splitting into includes. Show the suggested structure:
Taskfile.yml # Root with includes + global vars
taskfiles/
api.yml # API/backend tasks
web.yml # Frontend tasks
docker.yml # Docker/compose tasks
db.yml # Database tasksConvert the root Taskfile:
version: "3"
includes:
api:
taskfile: ./taskfiles/api.yml
dir: ./api
web:
taskfile: ./taskfiles/web.yml
dir: ./web
docker:
taskfile: ./taskfiles/docker.yml
tasks:
dev:
desc: Start all services in parallel
deps:
- api:dev
- web:devTF005 — Missing desc field Generate a desc from the task name and commands:
tasks:
lint:
desc: Run linters
cmds:
- # existing commandsTF006 — No dotenv config when .env files exist Add dotenv declaration at top level:
version: "3"
dotenv: [".env.local", ".env"]Note: dotenv is only supported in the root Taskfile, not in included files.
TF007 — Hard-coded absolute paths Replace with variables:
# Before
tasks:
deploy:
cmds:
- /usr/local/bin/kubectl apply -f /home/user/k8s/
# After
vars:
KUBECTL: kubectl
K8S_DIR: ./k8s
tasks:
deploy:
cmds:
- "{{.KUBECTL}} apply -f {{.K8S_DIR}}/"TF008 — deps used for sequential tasks This is INFO-level because deps: may be intentionally parallel. If the user confirms they want sequential ordering:
# Before (parallel — may cause issues if order matters)
tasks:
ci:
deps: [lint, test, build]
# After (sequential — guaranteed order)
tasks:
ci:
desc: Run CI pipeline
cmds:
- task: lint
- task: test
- task: buildStep 5: Verify
Re-run detector to confirm violations are resolved.
---
Phase 2: Scaffold
Step 1: Choose Pattern
Use AskUserQuestion to present options:
Single-file (recommended for < 15 tasks):
- All tasks in one
Taskfile.yml - Flat namespace with
:separators (e.g.,dev:api,test:web) - Top-level
vars:for directory paths - Easy to discover all tasks at a glance
- Follows the Nosilha project pattern
Multi-file (recommended for 15+ tasks or monorepos):
- Root
Taskfile.ymlwithincludes: - Per-concern files in
taskfiles/directory - Better separation of concerns
- Avoids namespace collisions in large projects
Step 2: Choose Task Groups
Based on detected ecosystems, offer task groups via AskUserQuestion (multiSelect: true). Pre-select groups matching detected ecosystems.
Step 3: Generate Taskfile
Single-File Pattern (Nosilha-style)
version: "3"
vars:
API_DIR: apps/api # Adjust based on detected project structure
WEB_DIR: apps/web
tasks:
check:
desc: Check prerequisites — detect missing tools
silent: true
cmds:
- |
missing=0
check_tool() {
if command -v "$1" &> /dev/null; then
printf " OK %s\n" "$1"
else
printf " MISSING %s — install with: %s\n" "$1" "$2"
missing=$((missing + 1))
fi
}
echo "Checking prerequisites..."
# Add check_tool calls per detected ecosystem
if [ "$missing" -gt 0 ]; then
echo "$missing tool(s) missing."
exit 1
else
echo "All prerequisites installed."
fi
setup:
desc: Full project setup
cmds:
- task: setup:deps
# Add per-ecosystem setup subtasks
setup:deps:
desc: Install project dependencies
cmds:
- # Per-ecosystem install commands
dev:
desc: Start development servers
deps:
# Per-ecosystem dev tasks run in parallel
test:
desc: Run all tests
cmds:
# Per-ecosystem test tasks run sequentially
lint:
desc: Run all linters
cmds:
# Per-ecosystem lint tasks run sequentially
clean:
desc: Clean build artifacts
cmds:
# Per-ecosystem clean tasksMulti-File Pattern
Root Taskfile.yml:
version: "3"
includes:
api:
taskfile: ./taskfiles/api.yml
dir: ./apps/api
web:
taskfile: ./taskfiles/web.yml
dir: ./apps/web
docker:
taskfile: ./taskfiles/docker.yml
tasks:
check:
desc: Check prerequisites
cmds:
- task: api:check
- task: web:check
setup:
desc: Full project setup
cmds:
- task: api:setup
- task: web:setup
dev:
desc: Start all services in parallel
deps:
- api:dev
- web:dev
test:
desc: Run all tests
cmds:
- task: api:test
- task: web:test
lint:
desc: Run all linters
cmds:
- task: api:lint
- task: web:lint
clean:
desc: Clean all build artifacts
cmds:
- task: api:clean
- task: web:clean---
Ecosystem Templates
Node.js / pnpm
# For single-file, prefix with namespace (e.g., dev:web, test:web)
# For multi-file, these are standalone tasks in taskfiles/web.yml
tasks:
dev:
desc: Start dev server
cmds:
- pnpm dev
build:
desc: Build for production
cmds:
- pnpm build
test:
desc: Run tests
cmds:
- pnpm test
lint:
desc: Run ESLint
cmds:
- pnpm lint
format:
desc: Format code with Prettier
cmds:
- pnpm prettier --write .
check:
desc: Type-check with TypeScript
cmds:
- npx tsc --noEmitAdjust commands for detected package manager (yarn, npm).
JVM / Gradle
tasks:
build:
desc: Build the project
cmds:
- ./gradlew build
test:
desc: Run tests
cmds:
- ./gradlew test
lint:
desc: Run linter checks
cmds:
- ./gradlew ktlintCheck # Kotlin
# or: ./gradlew checkstyleMain # Java
check:
desc: Run all checks
cmds:
- ./gradlew check
run:
desc: Run the application
cmds:
- ./gradlew bootRun # Spring Boot
# or: ./gradlew run # Plain application
clean:
desc: Clean build artifacts
cmds:
- ./gradlew cleanFor Maven, replace ./gradlew with ./mvnw (or mvn).
Python / uv
tasks:
setup:
desc: Install dependencies
run: once
cmds:
- uv sync --all-extras --dev
dev:
desc: Run development server
deps: [setup]
cmds:
- uv run python -m app
test:
desc: Run tests with coverage
deps: [setup]
cmds:
- uv run pytest -v --cov=src
lint:
desc: Lint and format code
deps: [setup]
cmds:
- uv run ruff check --fix .
- uv run ruff format .
check:
desc: Type-check with mypy
deps: [setup]
cmds:
- uv run mypy src
clean:
desc: Clean Python artifacts
cmds:
- rm -rf dist/ build/ *.egg-info .pytest_cache .mypy_cacheFor pipenv/poetry, adjust commands accordingly.
Docker / Docker Compose
vars:
COMPOSE_FILE: docker-compose.yml # Adjust path if needed
tasks:
up:
desc: Start containers
cmds:
- docker compose -f {{.COMPOSE_FILE}} up -d
down:
desc: Stop containers
cmds:
- docker compose -f {{.COMPOSE_FILE}} down
logs:
desc: Follow container logs
cmds:
- docker compose -f {{.COMPOSE_FILE}} logs -f {{.CLI_ARGS}}
ps:
desc: List running containers
cmds:
- docker compose -f {{.COMPOSE_FILE}} ps
restart:
desc: Restart containers
cmds:
- docker compose -f {{.COMPOSE_FILE}} restart {{.CLI_ARGS}}
clean:
desc: Stop and remove volumes
cmds:
- docker compose -f {{.COMPOSE_FILE}} down -v --remove-orphans
build:
desc: Build Docker images
cmds:
- docker compose -f {{.COMPOSE_FILE}} buildGeneric (Always Available)
tasks:
setup:
desc: Full project setup — install tools and dependencies
cmds:
- echo "Add setup steps for your project"
check:
desc: Check prerequisites
silent: true
cmds:
- |
echo "Checking prerequisites..."
# Add checks here
clean:
desc: Clean build artifacts
cmds:
- echo "Add clean steps for your project"---
Verification
After scaffold or audit fixes:
1. Re-run detector:
python3 ${CLAUDE_SKILL_DIR}/scripts/detect_taskfile.py <project-root>2. List tasks:
task --list3. Show confirmation summary:
| Step | Action | Result |
|------|--------|--------|
| Tool | task v3.40.1 | installed |
| File | Taskfile.yml | created |
| Tasks | 12 tasks | generated |
| dotenv | .env.local, .env | configured |4. Suggest next steps:
task --listto see all available taskstask checkto verify prerequisitestask setupto run initial setup- Commit
Taskfile.yml(andtaskfiles/if multi-file) to git